package org.jruby.runtime.backtrace;
import com.headius.backport9.stack.StackWalker;
import org.jruby.Ruby;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.JavaNameMangler;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public class BacktraceData implements Serializable {
public static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
private RubyStackTraceElement[] backtraceElements;
private final Stream<StackWalker.StackFrame> stackStream;
private final Stream<BacktraceElement> rubyTrace;
private final boolean fullTrace;
private final boolean maskNative;
private final boolean includeNonFiltered;
public BacktraceData(Stream<StackWalker.StackFrame> stackStream, Stream<BacktraceElement> rubyTrace, boolean fullTrace, boolean maskNative, boolean includeNonFiltered) {
this.stackStream = stackStream;
this.rubyTrace = rubyTrace;
this.fullTrace = fullTrace;
this.maskNative = maskNative;
this.includeNonFiltered = includeNonFiltered;
}
public static final BacktraceData EMPTY = new BacktraceData(
Stream.empty(),
Stream.empty(),
false,
false,
false);
public final RubyStackTraceElement[] getBacktrace(Ruby runtime) {
if (backtraceElements == null) {
backtraceElements = constructBacktrace(runtime.getBoundMethods());
}
return backtraceElements;
}
public final RubyStackTraceElement[] getPartialBacktrace(Ruby runtime, int level) {
if (backtraceElements == null) {
backtraceElements = constructBacktrace(runtime.getBoundMethods(), level);
}
return backtraceElements;
}
@SuppressWarnings("unchecked")
public RubyStackTraceElement[] getBacktraceWithoutRuby() {
return constructBacktrace(Collections.EMPTY_MAP);
}
private RubyStackTraceElement[] constructBacktrace(Map<String, Map<String, String>> boundMethods) {
return constructBacktrace(boundMethods, Integer.MAX_VALUE);
}
private RubyStackTraceElement[] constructBacktrace(Map<String, Map<String, String>> boundMethods, int count) {
ArrayList<RubyStackTraceElement> trace = new ArrayList<>();
boolean dupFrame = false; String dupFrameName = null;
Iterator<StackWalker.StackFrame> stackIter = stackStream.iterator();
Iterator<BacktraceElement> backIter = rubyTrace.iterator();
while (stackIter.hasNext() && trace.size() < count) {
StackWalker.StackFrame element = stackIter.next();
int line = element.getLineNumber();
String filename = element.getFileName();
String methodName = element.getMethodName();
String className = element.getClassName();
if (filename != null) {
if (!filename.endsWith(".java")) {
List<String> mangledTuple = JavaNameMangler.decodeMethodTuple(methodName);
if (mangledTuple != null) {
FrameType type = JavaNameMangler.decodeFrameTypeFromMangledName(mangledTuple.get(1));
String decodedName = JavaNameMangler.decodeMethodName(type, mangledTuple);
if (decodedName != null) {
RubyStackTraceElement rubyElement = new RubyStackTraceElement(className, decodedName, filename, line, false, type);
if (maskNative && dupFrame) {
dupFrame = false;
trace.add(new RubyStackTraceElement(className, dupFrameName, filename, line, false, type));
}
trace.add(rubyElement);
continue;
}
}
}
}
String rubyName = methodName;
if ( fullTrace ||
( rubyName = getBoundMethodName(boundMethods, className, methodName) ) != null ) {
filename = packagedFilenameFromElement(filename, className);
if (maskNative) {
dupFrame = true; dupFrameName = rubyName; continue;
}
trace.add(new RubyStackTraceElement(className, rubyName, filename, line, false));
if ( ! fullTrace ) continue;
}
final FrameType frameType;
if ( backIter.hasNext() && (frameType = FrameType.getInterpreterFrame(className, methodName)) != null ) {
BacktraceElement rubyFrame = backIter.next();
final String newName;
switch (frameType) {
case METHOD: newName = rubyFrame.method; break;
case BLOCK: newName = rubyFrame.method; break;
case CLASS: newName = "<class:" + rubyFrame.method + '>'; break;
case MODULE: newName = "<module:" + rubyFrame.method + '>'; break;
case METACLASS: newName = "singleton class"; break;
case ROOT: newName = "<main>"; break;
case EVAL:
newName = rubyFrame.method == null || rubyFrame.method.isEmpty() ? "<main>" : rubyFrame.method;
break;
default: newName = rubyFrame.method;
}
RubyStackTraceElement rubyElement = new RubyStackTraceElement("RUBY", newName, rubyFrame.filename, rubyFrame.line + 1, false, frameType);
if (maskNative && dupFrame) {
dupFrame = false;
trace.add(new RubyStackTraceElement(rubyElement.getClassName(), dupFrameName, rubyElement.getFileName(), rubyElement.getLineNumber(), rubyElement.isBinding(), rubyElement.getFrameType()));
}
trace.add(rubyElement);
continue;
}
if (includeNonFiltered && !isFilteredClass(className)) {
filename = packagedFilenameFromElement(filename, className);
trace.add(new RubyStackTraceElement(className, methodName, filename, line, false));
}
}
return trace.toArray(RubyStackTraceElement.EMPTY_ARRAY);
}
public static String getBoundMethodName(Map<String,Map<String,String>> boundMethods, String className, String methodName) {
Map<String, String> javaToRuby = boundMethods.get(className);
return javaToRuby == null ? null : javaToRuby.get(methodName);
}
private static String packagedFilenameFromElement(final String filename, final String className) {
if (filename == null) return className.replace('.', '/');
int lastDot = className.lastIndexOf('.');
if (lastDot == -1) return filename;
final String pkgPath = className.substring(0, lastDot + 1).replace('.', '/');
if (filename.indexOf('/') > -1 && filename.startsWith(pkgPath)) return filename;
return pkgPath + filename;
}
private static boolean isFilteredClass(final String className) {
if ( className.startsWith("sun.reflect.") ) return true;
final String org_jruby_ = "org.jruby.";
if ( className.startsWith(org_jruby_) ) {
final int dot = className.indexOf('.', org_jruby_.length());
if ( dot == -1 ) return false;
final String subPackage = className.substring(org_jruby_.length(), dot);
switch ( subPackage ) {
case "anno" : return true;
case "ast" : return true;
case "exceptions" : return true;
case "gen" : return true;
case "ir" : return true;
case "internal" : return true;
case "java" : return true;
case "parser" : return true;
case "platform" : return true;
case "runtime" : return true;
case "util" : return true;
}
}
return false;
}
}