• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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