1 /* 2 * Copyright (c) 2007 Mockito contributors 3 * This program is made available under the terms of the MIT License. 4 */ 5 package org.mockito.internal.exceptions.stacktrace; 6 7 import java.io.Serializable; 8 import java.lang.reflect.Method; 9 import java.util.ArrayList; 10 import java.util.List; 11 12 import org.mockito.exceptions.stacktrace.StackTraceCleaner; 13 import org.mockito.internal.configuration.plugins.Plugins; 14 15 public class StackTraceFilter implements Serializable { 16 17 static final long serialVersionUID = -5499819791513105700L; 18 19 private static final StackTraceCleaner CLEANER = 20 Plugins.getStackTraceCleanerProvider() 21 .getStackTraceCleaner(new DefaultStackTraceCleaner()); 22 23 private static Object JAVA_LANG_ACCESS; 24 private static Method GET_STACK_TRACE_ELEMENT; 25 26 static { 27 try { 28 JAVA_LANG_ACCESS = 29 Class.forName("sun.misc.SharedSecrets") 30 .getMethod("getJavaLangAccess") 31 .invoke(null); 32 GET_STACK_TRACE_ELEMENT = 33 Class.forName("sun.misc.JavaLangAccess") 34 .getMethod("getStackTraceElement", Throwable.class, int.class); 35 } catch (Exception ignored) { 36 // Use the slow computational path for filtering stacktraces if fast path does not exist 37 // in JVM 38 } 39 } 40 41 /** 42 * Example how the filter works (+/- means good/bad): 43 * [a+, b+, c-, d+, e+, f-, g+] -> [a+, b+, d+, e+, g+] 44 * Basically removes all bad from the middle. 45 * <strike>If any good are in the middle of bad those are also removed.</strike> 46 */ filter(StackTraceElement[] target, boolean keepTop)47 public StackTraceElement[] filter(StackTraceElement[] target, boolean keepTop) { 48 // TODO: profile 49 // TODO: investigate "keepTop" commit history - no effect! 50 final List<StackTraceElement> filtered = new ArrayList<>(); 51 for (StackTraceElement element : target) { 52 if (CLEANER.isIn(element)) { 53 filtered.add(element); 54 } 55 } 56 StackTraceElement[] result = new StackTraceElement[filtered.size()]; 57 return filtered.toArray(result); 58 } 59 60 /** 61 * This filtering strategy makes use of a fast-path computation to retrieve stackTraceElements 62 * from a Stacktrace of a Throwable. It does so, by taking advantage of {@link 63 * sun.misc.SharedSecrets} and {@link sun.misc.JavaLangAccess}. 64 * 65 * <p>The {@link sun.misc.SharedSecrets} provides a method to obtain an instance of an {@link 66 * sun.misc.JavaLangAccess}. The latter class has a method to fast-path into {@link 67 * Throwable#getStackTrace()} and retrieve a single {@link StackTraceElement}. This prevents the 68 * JVM from having to generate a full stacktrace, which could potentially be expensive if 69 * stacktraces become very large. 70 * 71 * @param target The throwable target to find the first {@link StackTraceElement} that should 72 * not be filtered out per {@link StackTraceFilter#CLEANER}. 73 * @return The first {@link StackTraceElement} outside of the {@link StackTraceFilter#CLEANER} 74 */ filterFirst(Throwable target, boolean isInline)75 public StackTraceElement filterFirst(Throwable target, boolean isInline) { 76 boolean shouldSkip = isInline; 77 78 if (GET_STACK_TRACE_ELEMENT != null) { 79 int i = 0; 80 81 // The assumption here is that the CLEANER filter will not filter out every single 82 // element. However, since we don't want to compute the full length of the stacktrace, 83 // we don't know the upper boundary. Therefore, simply increment the counter and go as 84 // far as we have to go, assuming that we get there. If, in the rare occasion, we 85 // don't, we fall back to the old slow path. 86 while (true) { 87 try { 88 StackTraceElement stackTraceElement = 89 (StackTraceElement) 90 GET_STACK_TRACE_ELEMENT.invoke(JAVA_LANG_ACCESS, target, i); 91 92 if (CLEANER.isIn(stackTraceElement)) { 93 if (shouldSkip) { 94 shouldSkip = false; 95 } else { 96 return stackTraceElement; 97 } 98 } 99 } catch (Exception e) { 100 // Fall back to slow path 101 break; 102 } 103 i++; 104 } 105 } 106 107 // If we can't use the fast path of retrieving stackTraceElements, use the slow path by 108 // iterating over the actual stacktrace 109 for (StackTraceElement stackTraceElement : target.getStackTrace()) { 110 if (CLEANER.isIn(stackTraceElement)) { 111 if (shouldSkip) { 112 shouldSkip = false; 113 } else { 114 return stackTraceElement; 115 } 116 } 117 } 118 return null; 119 } 120 121 /** 122 * Finds the source file of the target stack trace. 123 * Returns the default value if source file cannot be found. 124 */ findSourceFile(StackTraceElement[] target, String defaultValue)125 public String findSourceFile(StackTraceElement[] target, String defaultValue) { 126 for (StackTraceElement e : target) { 127 if (CLEANER.isIn(e)) { 128 return e.getFileName(); 129 } 130 } 131 return defaultValue; 132 } 133 } 134