org.jruby.runtime.backtrace.BacktraceData Maven / Gradle / Ivy
package org.jruby.runtime.backtrace;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.jruby.Ruby;
import org.jruby.compiler.JITCompiler;
import org.jruby.util.JavaNameMangler;
public class BacktraceData implements Serializable {
private RubyStackTraceElement[] backtraceElements;
private final StackTraceElement[] javaTrace;
private final BacktraceElement[] rubyTrace;
private final boolean fullTrace;
private final boolean maskNative;
private final TraceType.Gather gather;
public BacktraceData(StackTraceElement[] javaTrace, BacktraceElement[] rubyTrace, boolean fullTrace, boolean maskNative, TraceType.Gather gather) {
this.javaTrace = javaTrace;
this.rubyTrace = rubyTrace;
this.fullTrace = fullTrace;
this.maskNative = maskNative;
this.gather = gather;
}
public static final BacktraceData EMPTY = new BacktraceData(
new StackTraceElement[0],
new BacktraceElement[0],
false,
false,
TraceType.Gather.NORMAL);
public RubyStackTraceElement[] getBacktrace(Ruby runtime) {
if (backtraceElements == null) {
backtraceElements = transformBacktrace(runtime.getBoundMethods());
}
return backtraceElements;
}
private RubyStackTraceElement[] transformBacktrace(Map boundMethods) {
List trace = new ArrayList(javaTrace.length);
// used for duplicating the previous Ruby frame when masking native calls
boolean dupFrame = false;
String dupFrameName = null;
// a running index into the Ruby backtrace stack, incremented for each
// interpreter frame we encounter in the Java backtrace.
int rubyFrameIndex = rubyTrace == null ? -1 : rubyTrace.length - 1;
// no Java trace, can't generate hybrid trace
// TODO: Perhaps just generate the interpreter trace? Is this path ever hit?
if (javaTrace == null) {
return null;
}
for (int i = 0; i < javaTrace.length; i++) {
StackTraceElement element = javaTrace[i];
if (
element.getFileName() != null &&
(element.getFileName().endsWith(".rb")
|| element.getFileName().equals("-e")
|| element.getClassName().startsWith(JITCompiler.RUBY_JIT_PREFIX + ".")
|| element.getMethodName().contains("$RUBY$")
|| element.getMethodName().contains("__file__"))) {
if (element.getLineNumber() == -1) {
continue;
}
String methodName = element.getMethodName();
String className = element.getClassName();
// FIXME: Formalize jitted method structure so this isn't quite as hacky
if (className.startsWith(JITCompiler.RUBY_JIT_PREFIX)) {
// pull out and demangle the method name
String classAndMethod[] = className.substring(JITCompiler.RUBY_JIT_PREFIX.length() + 1, className.lastIndexOf("_")).split("#");
className = classAndMethod[0];
methodName = JavaNameMangler.demangleMethodName(classAndMethod[1]);
RubyStackTraceElement rubyElement = new RubyStackTraceElement(className, methodName, element.getFileName(), element.getLineNumber(), false);
// dup if masking native and previous frame was native
if (maskNative && dupFrame) {
dupFrame = false;
trace.add(new RubyStackTraceElement(rubyElement.getClassName(), dupFrameName, rubyElement.getFileName(), rubyElement.getLineNumber(), rubyElement.isBinding()));
}
trace.add(rubyElement);
// if it's a synthetic call, use it but gobble up parent calls
// TODO: need to formalize this better
if (element.getMethodName().contains("$RUBY$SYNTHETIC")) {
// gobble up at least one parent, and keep going if there's more synthetic frames
while (element.getMethodName().indexOf("$RUBY$SYNTHETIC") != -1 && ++i < javaTrace.length) {
element = javaTrace[i];
}
}
continue;
}
int RUBYindex = methodName.indexOf("$RUBY$");
if (RUBYindex >= 0) {
// if it's a synthetic call, use it but gobble up parent calls
// TODO: need to formalize this better
methodName = methodName.substring(RUBYindex);
if (methodName.startsWith("$RUBY$SYNTHETIC")) {
methodName = methodName.substring("$RUBY$SYNTHETIC".length());
methodName = JavaNameMangler.demangleMethodName(methodName);
if (methodName.equals("__file__")) {
methodName = "(root)";
}
RubyStackTraceElement rubyElement = new RubyStackTraceElement(className, methodName, element.getFileName(), element.getLineNumber(), false);
// dup if masking native and previous frame was native
if (maskNative && dupFrame) {
dupFrame = false;
trace.add(new RubyStackTraceElement(rubyElement.getClassName(), dupFrameName, rubyElement.getFileName(), rubyElement.getLineNumber(), rubyElement.isBinding()));
}
trace.add(rubyElement);
// gobble up at least one parent, and keep going if there's more synthetic frames
while (element.getMethodName().indexOf("$RUBY$SYNTHETIC") != -1 && ++i < javaTrace.length) {
element = javaTrace[i];
}
continue;
}
methodName = methodName.substring("$RUBY$".length());
methodName = JavaNameMangler.demangleMethodName(methodName);
RubyStackTraceElement rubyElement = new RubyStackTraceElement(className, methodName, element.getFileName(), element.getLineNumber(), false);
// dup if masking native and previous frame was native
if (maskNative && dupFrame) {
dupFrame = false;
trace.add(new RubyStackTraceElement(rubyElement.getClassName(), dupFrameName, rubyElement.getFileName(), rubyElement.getLineNumber(), rubyElement.isBinding()));
}
trace.add(rubyElement);
continue;
}
if (methodName.equals("__file__") && !element.getFileName().endsWith("AbstractScript.java")) {
methodName = "(root)";
RubyStackTraceElement rubyElement = new RubyStackTraceElement(className, methodName, element.getFileName(), element.getLineNumber(), false);
// dup if masking native and previous frame was native
if (maskNative && dupFrame) {
dupFrame = false;
trace.add(new RubyStackTraceElement(rubyElement.getClassName(), dupFrameName, rubyElement.getFileName(), rubyElement.getLineNumber(), rubyElement.isBinding()));
}
trace.add(rubyElement);
continue;
}
}
String dotClassMethod = element.getClassName() + "." + element.getMethodName();
String rubyName = null;
if (fullTrace || (rubyName = boundMethods.get(dotClassMethod)) != null) {
String filename = element.getFileName();
// stick package on the beginning
if (filename == null) {
filename = element.getClassName().replaceAll("\\.", "/");
} else {
int lastDot = element.getClassName().lastIndexOf('.');
if (lastDot != -1) {
filename = element.getClassName().substring(0, lastDot + 1).replaceAll("\\.", "/") + filename;
}
}
if (maskNative) {
// for Kernel#caller, don't show .java frames in the trace
dupFrame = true;
dupFrameName = rubyName;
continue;
} else {
if (rubyName == null) rubyName = element.getMethodName();
trace.add(new RubyStackTraceElement(element.getClassName(), rubyName, filename, element.getLineNumber(), false));
}
// if not full trace, we're done; don't check interpreted marker
if (!fullTrace) {
continue;
}
}
String classMethod = element.getClassName() + "." + element.getMethodName();
FrameType frameType = FrameType.INTERPRETED_FRAMES.get(classMethod);
if (frameType != null && rubyFrameIndex >= 0) {
// Frame matches one of our markers for "interpreted" calls
BacktraceElement rubyFrame = rubyTrace[rubyFrameIndex];
RubyStackTraceElement rubyElement = new RubyStackTraceElement(rubyFrame.klass, rubyFrame.method, rubyFrame.filename, rubyFrame.line + 1, false);
// dup if masking native and previous frame was native
if (maskNative && dupFrame) {
dupFrame = false;
trace.add(new RubyStackTraceElement(rubyElement.getClassName(), dupFrameName, rubyElement.getFileName(), rubyElement.getLineNumber(), rubyElement.isBinding()));
}
trace.add(rubyElement);
rubyFrameIndex--;
continue;
}
}
RubyStackTraceElement[] rubyStackTrace = new RubyStackTraceElement[trace.size()];
return (RubyStackTraceElement[]) trace.toArray(rubyStackTrace);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy