/*
* Copyright (c) 2015, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are licensed under
* GNU General Public License, version 2, with the Classpath Exception
*
* http://openjdk.java.net/legal/gplv2+ce.html
*/
package co.paralleluniverse.xst;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
//import java.lang.reflect.Executable;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
/**
* Represents a captured stack trace which contains more information than that returned by {@link Throwable#getStackTrace()}.
* The extended information is captured on a best-effort basis, and depends on the JVM used.
*
* @author pron
*/
public class ExtendedStackTrace implements Iterable<ExtendedStackTraceElement> {
/**
* Returns a stack trace with extended information for the given {@code Throwable}.
* @param t
*/
public static ExtendedStackTrace of(Throwable t) {
if (t == null)
return null;
try {
return new ExtendedStackTraceHotSpot(t);
} catch (Throwable e) {
return new ExtendedStackTrace(t);
}
}
/**
* Returns a stack trace for the current execution point.
*/
public static ExtendedStackTrace here() {
try {
return new ExtendedStackTraceHotSpot(new Exception("Stack trace"));
} catch (Throwable e) {
return new ExtendedStackTraceClassContext();
}
}
protected final Throwable t;
private ExtendedStackTraceElement[] est;
// private transient Map<Class<?>, Member[]> methods; // cache
protected ExtendedStackTrace(Throwable t) {
this.t = t;
}
@Override
public Iterator<ExtendedStackTraceElement> iterator() {
return Arrays.asList(get()).iterator();
}
/**
* Returns an array of {@link ExtendedStackTraceElement}s representing the captured stack trace.
*/
public ExtendedStackTraceElement[] get() {
synchronized (this) {
if (est == null) {
StackTraceElement[] st = t.getStackTrace();
if (st != null) {
est = new ExtendedStackTraceElement[st.length];
for (int i = 0; i < st.length; i++)
est[i] = new BasicExtendedStackTraceElement(st[i]);
}
}
return est;
}
}
protected /*Executable*/ Member getMethod(final ExtendedStackTraceElement este) {
if (este.getDeclaringClass() == null)
return null;
Member[] ms = getMethods(este.getDeclaringClass());
Member method = null;
for (Member m : ms) {
if (este.getMethodName().equals(m.getName())) {
if (method == null)
method = m;
else {
method = null; // more than one match
break;
}
}
}
if (method == null && este.getLineNumber() >= 0) {
try {
final AtomicReference<String> descriptor = new AtomicReference<>();
ASMUtil.accept(este.getDeclaringClass(), ClassReader.SKIP_FRAMES, new ClassVisitor(Opcodes.ASM5) {
@Override
public MethodVisitor visitMethod(int access, String name, final String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if (descriptor.get() == null && este.getMethodName().equals(name)) {
mv = new MethodVisitor(api, mv) {
int minLine = Integer.MAX_VALUE, maxLine = Integer.MIN_VALUE;
@Override
public void visitLineNumber(int line, Label start) {
if (line < minLine)
minLine = line;
if (line > maxLine)
maxLine = line;
}
@Override
public void visitEnd() {
if (minLine <= este.getLineNumber() && maxLine >= este.getLineNumber())
descriptor.set(desc);
super.visitEnd();
}
};
}
return mv;
}
});
if (descriptor.get() != null) {
final String desc = descriptor.get();
for (Member m : ms) {
if (este.getMethodName().equals(getName(m)) && desc.equals(getDescriptor(m))) {
method = m;
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return method;
}
protected static final String getName(Member m) {
if (m instanceof Constructor)
return "<init>";
return ((Method) m).getName();
}
protected static final String getDescriptor(Member m) {
if (m instanceof Constructor)
return Type.getConstructorDescriptor((Constructor) m);
return Type.getMethodDescriptor((Method) m);
}
protected final Member[] getMethods(Class<?> clazz) {
// synchronized (this) {
Member[] es;
// if (methods == null)
// methods = new HashMap<>();
// es = methods.get(clazz);
// if (es == null) {
Method[] ms = clazz.getDeclaredMethods();
Constructor[] cs = clazz.getDeclaredConstructors();
es = new Member[ms.length + cs.length];
System.arraycopy(cs, 0, es, 0, cs.length);
System.arraycopy(ms, 0, es, cs.length, ms.length);
// methods.put(clazz, es);
// }
return es;
// }
}
protected class BasicExtendedStackTraceElement extends ExtendedStackTraceElement {
protected BasicExtendedStackTraceElement(StackTraceElement ste, Class<?> clazz, Method method, int bci) {
super(ste, clazz, method, bci);
}
protected BasicExtendedStackTraceElement(StackTraceElement ste, Class<?> clazz) {
super(ste, clazz, null, -1);
}
protected BasicExtendedStackTraceElement(StackTraceElement ste) {
super(ste, null, null, -1);
}
@Override
public Member getMethod() {
if (method == null) {
method = ExtendedStackTrace.this.getMethod(this);
if (method != null && !getMethodName().equals(getName(method))) {
throw new IllegalStateException("Method name mismatch: " + getMethodName() + ", " + method.getName());
// method = null;
}
}
return method;
}
@Override
public Class<?> getDeclaringClass() {
if (clazz == null) {
try {
clazz = Class.forName(getClassName());
} catch (ClassNotFoundException e) {
}
}
return clazz;
}
}
//<editor-fold defaultstate="collapsed" desc="Printing">
/////////// Printing ///////////////////////////////////
/*
* Additional copyright for the printing section,
* which is based on OpenJDK code taken from java/lang/Throwable.java:
*/
/*
* Copyright (c) 1994, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* Prints this stack trace to {@link System#err}.
*/
public void printStackTrace() {
printStackTrace(System.err);
}
/**
* Prints this stack trace to the given print stream.
*/
public void printStackTrace(PrintStream s) {
printStackTrace(new WrappedPrintStream(s));
}
/**
* Prints this stack trace to the given print writer.
*/
public void printStackTrace(PrintWriter s) {
printStackTrace(new WrappedPrintWriter(s));
}
private void printStackTrace(PrintStreamOrWriter s) {
synchronized (s.lock()) {
// Guard against malicious overrides of Throwable.equals by using a Set with identity equality semantics.
printStackTrace(s, null, "", "", Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>()));
}
}
private void printStackTrace(PrintStreamOrWriter s,
ExtendedStackTraceElement[] enclosingTrace,
String caption,
String prefix,
Set<Throwable> dejaVu) {
assert Thread.holdsLock(s.lock());
if (dejaVu.contains(t)) {
s.println("\t[CIRCULAR REFERENCE:" + this + "]");
} else {
dejaVu.add(t);
final ExtendedStackTraceElement[] trace = get();
final int unique = countUniqueFrames(trace, enclosingTrace);
// Print our stack trace
s.println(prefix + caption + this);
for (int i = 0; i < unique; i++)
s.println(prefix + "\tat " + trace[i]);
final int framesInCommon = trace.length - unique;
if (framesInCommon != 0)
s.println(prefix + "\t... " + framesInCommon + " more");
// Print suppressed exceptions, if any
for (Throwable se : t.getSuppressed())
ExtendedStackTrace.of(se).printStackTrace(s, trace, SUPPRESSED_CAPTION, prefix + "\t", dejaVu);
// Print cause, if any
final ExtendedStackTrace ourCause = ExtendedStackTrace.of(t.getCause());
if (ourCause != null)
ourCause.printStackTrace(s, trace, CAUSE_CAPTION, prefix, dejaVu);
}
}
private static int countUniqueFrames(ExtendedStackTraceElement[] trace, ExtendedStackTraceElement[] enclosingTrace) {
int m = trace.length - 1;
if (enclosingTrace != null) {
int n = enclosingTrace.length - 1;
while (m >= 0 && n >= 0 && trace[m].equals(enclosingTrace[n])) {
m--;
n--;
}
}
return m + 1;
}
private static final String CAUSE_CAPTION = "Caused by: ";
private static final String SUPPRESSED_CAPTION = "Suppressed: ";
/**
* Wrapper class for PrintStream and PrintWriter to enable a single implementation of printStackTrace.
*/
private abstract static class PrintStreamOrWriter {
abstract Object lock();
abstract void println(Object o);
}
private static class WrappedPrintStream extends PrintStreamOrWriter {
private final PrintStream printStream;
WrappedPrintStream(PrintStream printStream) {
this.printStream = printStream;
}
Object lock() {
return printStream;
}
void println(Object o) {
printStream.println(o);
}
}
private static class WrappedPrintWriter extends PrintStreamOrWriter {
private final PrintWriter printWriter;
WrappedPrintWriter(PrintWriter printWriter) {
this.printWriter = printWriter;
}
Object lock() {
return printWriter;
}
void println(Object o) {
printWriter.println(o);
}
}
//</editor-fold>
}