org.mockito.internal.exceptions.stacktrace.StackTraceFilter Maven / Gradle / Ivy
Show all versions of mockito-core Show documentation
/*
* Copyright (c) 2007 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.internal.exceptions.stacktrace;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.mockito.exceptions.stacktrace.StackTraceCleaner;
import org.mockito.internal.configuration.plugins.Plugins;
public class StackTraceFilter implements Serializable {
static final long serialVersionUID = -5499819791513105700L;
private static final StackTraceCleaner CLEANER =
Plugins.getStackTraceCleanerProvider()
.getStackTraceCleaner(new DefaultStackTraceCleaner());
private static Object JAVA_LANG_ACCESS;
private static Method GET_STACK_TRACE_ELEMENT;
static {
try {
JAVA_LANG_ACCESS =
Class.forName("sun.misc.SharedSecrets")
.getMethod("getJavaLangAccess")
.invoke(null);
GET_STACK_TRACE_ELEMENT =
Class.forName("sun.misc.JavaLangAccess")
.getMethod("getStackTraceElement", Throwable.class, int.class);
} catch (Exception ignored) {
// Use the slow computational path for filtering stacktraces if fast path does not exist
// in JVM
}
}
/**
* Example how the filter works (+/- means good/bad):
* [a+, b+, c-, d+, e+, f-, g+] -> [a+, b+, d+, e+, g+]
* Basically removes all bad from the middle.
* If any good are in the middle of bad those are also removed.
*/
public StackTraceElement[] filter(StackTraceElement[] target, boolean keepTop) {
// TODO: profile
// TODO: investigate "keepTop" commit history - no effect!
final List filtered = new ArrayList<>();
for (StackTraceElement element : target) {
if (CLEANER.isIn(element)) {
filtered.add(element);
}
}
StackTraceElement[] result = new StackTraceElement[filtered.size()];
return filtered.toArray(result);
}
/**
* This filtering strategy makes use of a fast-path computation to retrieve stackTraceElements
* from a Stacktrace of a Throwable. It does so, by taking advantage of {@link
* sun.misc.SharedSecrets} and {@link sun.misc.JavaLangAccess}.
*
* The {@link sun.misc.SharedSecrets} provides a method to obtain an instance of an {@link
* sun.misc.JavaLangAccess}. The latter class has a method to fast-path into {@link
* Throwable#getStackTrace()} and retrieve a single {@link StackTraceElement}. This prevents the
* JVM from having to generate a full stacktrace, which could potentially be expensive if
* stacktraces become very large.
*
* @param target The throwable target to find the first {@link StackTraceElement} that should
* not be filtered out per {@link StackTraceFilter#CLEANER}.
* @return The first {@link StackTraceElement} outside of the {@link StackTraceFilter#CLEANER}
*/
public StackTraceElement filterFirst(Throwable target, boolean isInline) {
boolean shouldSkip = isInline;
if (GET_STACK_TRACE_ELEMENT != null) {
int i = 0;
// The assumption here is that the CLEANER filter will not filter out every single
// element. However, since we don't want to compute the full length of the stacktrace,
// we don't know the upper boundary. Therefore, simply increment the counter and go as
// far as we have to go, assuming that we get there. If, in the rare occasion, we
// don't, we fall back to the old slow path.
while (true) {
try {
StackTraceElement stackTraceElement =
(StackTraceElement)
GET_STACK_TRACE_ELEMENT.invoke(JAVA_LANG_ACCESS, target, i);
if (CLEANER.isIn(stackTraceElement)) {
if (shouldSkip) {
shouldSkip = false;
} else {
return stackTraceElement;
}
}
} catch (Exception e) {
// Fall back to slow path
break;
}
i++;
}
}
// If we can't use the fast path of retrieving stackTraceElements, use the slow path by
// iterating over the actual stacktrace
for (StackTraceElement stackTraceElement : target.getStackTrace()) {
if (CLEANER.isIn(stackTraceElement)) {
if (shouldSkip) {
shouldSkip = false;
} else {
return stackTraceElement;
}
}
}
return null;
}
/**
* Finds the source file of the target stack trace.
* Returns the default value if source file cannot be found.
*/
public String findSourceFile(StackTraceElement[] target, String defaultValue) {
for (StackTraceElement e : target) {
if (CLEANER.isIn(e)) {
return e.getFileName();
}
}
return defaultValue;
}
}