Skip to content
Browse files

For JRUBY-5504: Improve default backtraces

* New exception format for exceptions propagated all the way out
* Refactored TraceType into Gather and Format phases
* Trace type is per-runtime now
* New backtrace style "mri" for old C Ruby format
  • Loading branch information...
1 parent a99e8b8 commit 597a16d05d31de1aff150f26a087fe3a30c503ec @headius headius committed Feb 15, 2011
View
2 src/org/jruby/Main.java
@@ -399,7 +399,7 @@ private static int handleRaiseException(RaiseException rj) {
return 0;
}
} else {
- System.err.print(RubyInstanceConfig.TRACE_TYPE.printBacktrace(raisedException));
+ System.err.print(runtime.getInstanceConfig().getTraceType().printBacktrace(raisedException));
return 1;
}
}
View
2 src/org/jruby/Ruby.java
@@ -2419,7 +2419,7 @@ public void printError(RubyException excp) {
}
PrintStream errorStream = getErrorStream();
- errorStream.print(RubyInstanceConfig.TRACE_TYPE.printBacktrace(excp));
+ errorStream.print(config.getTraceType().printBacktrace(excp));
}
public void loadFile(String scriptName, InputStream in, boolean wrap) {
View
2 src/org/jruby/RubyException.java
@@ -148,7 +148,7 @@ public void setBacktraceElements(ThreadContext.RubyStackTraceElement[] backtrace
public void prepareBacktrace(ThreadContext context, boolean nativeException) {
// if it's null, build a backtrace
if (backtraceElements == null) {
- backtraceElements = RubyInstanceConfig.TRACE_TYPE.getBacktrace(context, nativeException);
+ backtraceElements = context.runtime.getInstanceConfig().getTraceType().getBacktrace(context, nativeException);
}
}
View
10 src/org/jruby/RubyInstanceConfig.java
@@ -322,7 +322,7 @@ public boolean shouldPrecompileAll() {
public static final boolean CAN_SET_ACCESSIBLE = SafePropertyAccessor.getBoolean("jruby.ji.setAccessible", true);
- public static TraceType TRACE_TYPE =
+ private TraceType traceType =
TraceType.traceTypeFor(SafePropertyAccessor.getProperty("jruby.backtrace.style", "ruby_framed"));
public static interface LoadServiceCreator {
@@ -1773,4 +1773,12 @@ public boolean isDisableGems() {
public void setDisableGems(boolean dg) {
this.disableGems = dg;
}
+
+ public TraceType getTraceType() {
+ return traceType;
+ }
+
+ public void setTraceType(TraceType traceType) {
+ this.traceType = traceType;
+ }
}
View
217 src/org/jruby/runtime/TraceType.java
@@ -3,6 +3,7 @@
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
+import java.util.List;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyClass;
@@ -11,61 +12,97 @@
import org.jruby.runtime.ThreadContext.RubyStackTraceElement;
import org.jruby.runtime.builtin.IRubyObject;
-public enum TraceType {
- /**
- * Full raw backtraces with all Java frames included.
- */
- RAW {
- public RubyStackTraceElement[] getBacktrace(ThreadContext context, boolean nativeException) {
- return ThreadContext.gatherRawBacktrace(context.runtime, Thread.currentThread().getStackTrace());
- }
+public class TraceType {
+ private final Gather gather;
+ private final Format format;
- public String printBacktrace(RubyException exception) {
- return printBacktraceMRI(exception);
- }
- },
-
- /**
- * A backtrace with interpreted frames intact, but don't remove Java frames.
- */
- FULL {
- public RubyStackTraceElement[] getBacktrace(ThreadContext context, boolean nativeException) {
- return getBacktrace(context, nativeException, true);
- }
+ public TraceType(Gather gather, Format format) {
+ this.gather = gather;
+ this.format = format;
+ }
- public String printBacktrace(RubyException exception) {
- return printBacktraceMRI(exception);
- }
- },
-
- /**
- * Normal Ruby-style backtrace, showing only Ruby and core class methods.
- */
- RUBY_FRAMED {
- public RubyStackTraceElement[] getBacktrace(ThreadContext context, boolean nativeException) {
- return getBacktrace(context, nativeException, false);
- }
+ public RubyStackTraceElement[] getBacktrace(ThreadContext context, boolean nativeException) {
+ return gather.getBacktrace(context, nativeException);
+ }
- public String printBacktrace(RubyException exception) {
- return printBacktraceMRI(exception);
- }
- },
-
- /**
- * Normal backtrace with Ruby and core methods, but Rubinius-style rendering.
- */
- RUBINIUS {
- public RubyStackTraceElement[] getBacktrace(ThreadContext context, boolean nativeException) {
- return getBacktrace(context, nativeException, false);
- }
+ public String printBacktrace(RubyException exception) {
+ return format.printBacktrace(exception);
+ }
- public String printBacktrace(RubyException exception) {
- return printBacktraceRubinius(exception);
- }
- };
+ public static TraceType traceTypeFor(String style) {
+ if (style.equalsIgnoreCase("raw")) return new TraceType(Gather.RAW, Format.JRUBY);
+ else if (style.equalsIgnoreCase("ruby_framed")) return new TraceType(Gather.NORMAL, Format.JRUBY);
+ else if (style.equalsIgnoreCase("rubinius")) return new TraceType(Gather.NORMAL, Format.RUBINIUS);
+ else if (style.equalsIgnoreCase("full")) return new TraceType(Gather.FULL, Format.JRUBY);
+ else if (style.equalsIgnoreCase("mri")) return new TraceType(Gather.NORMAL, Format.MRI);
+ else return new TraceType(Gather.NORMAL, Format.JRUBY);
+ }
+
+ public enum Gather {
+ /**
+ * Full raw backtraces with all Java frames included.
+ */
+ RAW {
+ public RubyStackTraceElement[] getBacktrace(ThreadContext context, boolean nativeException) {
+ return ThreadContext.gatherRawBacktrace(context.runtime, Thread.currentThread().getStackTrace());
+ }
+ },
+
+ /**
+ * A backtrace with interpreted frames intact, but don't remove Java frames.
+ */
+ FULL {
+ public RubyStackTraceElement[] getBacktrace(ThreadContext context, boolean nativeException) {
+ return TraceType.getBacktrace(context, nativeException, true);
+ }
+ },
+
+ /**
+ * Normal Ruby-style backtrace, showing only Ruby and core class methods.
+ */
+ NORMAL {
+ public RubyStackTraceElement[] getBacktrace(ThreadContext context, boolean nativeException) {
+ return TraceType.getBacktrace(context, nativeException, false);
+ }
+ };
+
+ public abstract RubyStackTraceElement[] getBacktrace(ThreadContext context, boolean nativeException);
+ }
+
+ public enum Format {
+ /**
+ * Formatting like C Ruby
+ */
+ MRI {
+ public String printBacktrace(RubyException exception) {
+ return printBacktraceMRI(exception);
+ }
+ },
+
+ /**
+ * New JRuby formatting
+ */
+ JRUBY {
+ public RubyStackTraceElement[] getBacktrace(ThreadContext context, boolean nativeException) {
+ return TraceType.getBacktrace(context, nativeException, true);
+ }
+
+ public String printBacktrace(RubyException exception) {
+ return printBacktraceJRuby(exception);
+ }
+ },
+
+ /**
+ * Rubinius-style formatting
+ */
+ RUBINIUS {
+ public String printBacktrace(RubyException exception) {
+ return printBacktraceRubinius(exception);
+ }
+ };
- public abstract RubyStackTraceElement[] getBacktrace(ThreadContext context, boolean nativeException);
- public abstract String printBacktrace(RubyException exception);
+ public abstract String printBacktrace(RubyException exception);
+ }
protected static String printBacktraceMRI(RubyException exception) {
Ruby runtime = exception.getRuntime();
@@ -143,11 +180,12 @@ protected static String printBacktraceMRI(RubyException exception) {
protected static String printBacktraceRubinius(RubyException exception) {
Ruby runtime = exception.getRuntime();
- ThreadContext.RubyStackTraceElement[] frames = exception.getBacktraceElements();
+ RubyStackTraceElement[] frames = exception.getBacktraceElements();
+ if (frames == null) frames = new RubyStackTraceElement[0];
ArrayList firstParts = new ArrayList();
int longestFirstPart = 0;
- for (ThreadContext.RubyStackTraceElement frame : frames) {
+ for (RubyStackTraceElement frame : frames) {
String firstPart = frame.getClassName() + "#" + frame.getMethodName();
if (firstPart.length() > longestFirstPart) longestFirstPart = firstPart.length();
firstParts.add(firstPart);
@@ -176,7 +214,7 @@ protected static String printBacktraceRubinius(RubyException exception) {
.append("Backtrace:\n");
int i = 0;
- for (ThreadContext.RubyStackTraceElement frame : frames) {
+ for (RubyStackTraceElement frame : frames) {
String firstPart = (String)firstParts.get(i);
String secondPart = frame.getFileName() + ":" + frame.getLineNumber();
@@ -202,6 +240,71 @@ protected static String printBacktraceRubinius(RubyException exception) {
return buffer.toString();
}
+ protected static String printBacktraceJRuby(RubyException exception) {
+ Ruby runtime = exception.getRuntime();
+ RubyStackTraceElement[] frames = exception.getBacktraceElements();
+ if (frames == null) frames = new RubyStackTraceElement[0];
+
+ List<String> lineNumbers = new ArrayList(frames.length);
+
+ // find longest filename and line number
+ int longestFileName = 0;
+ int longestLineNumber = 0;
+ for (RubyStackTraceElement frame : frames) {
+ String lineNumber = String.valueOf(frame.getLineNumber());
+ lineNumbers.add(lineNumber);
+
+ longestFileName = Math.max(longestFileName, frame.getFileName().length());
+ longestLineNumber = Math.max(longestLineNumber, String.valueOf(frame.getLineNumber()).length());
+ }
+
+ StringBuilder buffer = new StringBuilder();
+
+ // exception line
+ String message = exception.message(runtime.getCurrentContext()).toString();
+ if (exception.getMetaClass() == runtime.getRuntimeError() && message.length() == 0) {
+ message = "No current exception";
+ }
+ buffer
+ .append(exception.getMetaClass().getName())
+ .append(": ")
+ .append(message)
+ .append('\n');
+
+ // backtrace lines
+ int i = 0;
+ for (RubyStackTraceElement frame : frames) {
+ buffer.append(" ");
+
+ // file and line, centered on :
+ String fileName = frame.getFileName();
+ String lineNumber = lineNumbers.get(i);
+ for (int j = 0; j < longestFileName - fileName.length(); j++) {
+ buffer.append(' ');
+ }
+ buffer
+ .append(fileName)
+ .append(":")
+ .append(lineNumber);
+
+ // padding to center remainder on "in"
+ for (int l = 0; l < longestLineNumber - lineNumber.length(); l++) {
+ buffer.append(' ');
+ }
+
+ // method name
+ buffer
+ .append(' ')
+ .append("in ")
+ .append(frame.getMethodName())
+ .append('\n');
+
+ i++;
+ }
+
+ return buffer.toString();
+ }
+
public static IRubyObject generateMRIBacktrace(Ruby runtime, RubyStackTraceElement[] trace) {
if (trace == null) {
return runtime.getNil();
@@ -239,12 +342,4 @@ private static void printErrorPos(ThreadContext context, PrintStream errorStream
}
}
}
-
- public static TraceType traceTypeFor(String style) {
- if (style.equalsIgnoreCase("raw")) return TraceType.RAW;
- else if (style.equalsIgnoreCase("ruby_framed")) return TraceType.RUBY_FRAMED;
- else if (style.equalsIgnoreCase("rubinius")) return TraceType.RUBINIUS;
- else if (style.equalsIgnoreCase("full")) return TraceType.FULL;
- else return TraceType.RUBY_FRAMED;
- }
}
View
4 test/org/jruby/test/TestRuby.java
@@ -46,6 +46,7 @@
import org.jruby.RubyString;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.Block;
+import org.jruby.runtime.TraceType;
import org.jruby.runtime.builtin.IRubyObject;
/**
@@ -110,6 +111,7 @@ public void testPrintErrorShouldPrintErrorMessageAndStacktraceWhenBacktraceIsPre
final ByteArrayOutputStream err = new ByteArrayOutputStream();
RubyInstanceConfig config = new RubyInstanceConfig() {{
setInput(System.in); setOutput(System.out); setError(new PrintStream(err)); setObjectSpaceEnabled(false);
+ setTraceType(TraceType.traceTypeFor("mri"));
}};
Ruby ruby = Ruby.newInstance(config);
RubyException exception = (RubyException)runtime.getClass("NameError").newInstance(ruby.getCurrentContext(), new IRubyObject[]{ruby.newString("A message")}, Block.NULL_BLOCK);
@@ -125,8 +127,10 @@ public void testPrintErrorShouldPrintErrorMessageAndStacktraceWhenBacktraceIsPre
public void testPrintErrorShouldOnlyPrintErrorMessageWhenBacktraceIsNil() {
final ByteArrayOutputStream err = new ByteArrayOutputStream();
+ // use MRI formatting, since JRuby formatting is a bit different
RubyInstanceConfig config = new RubyInstanceConfig() {{
setInput(System.in); setOutput(System.out); setError(new PrintStream(err)); setObjectSpaceEnabled(false);
+ setTraceType(TraceType.traceTypeFor("mri"));
}};
Ruby ruby = Ruby.newInstance(config);
RubyException exception = (RubyException)runtime.getClass("NameError").newInstance(ruby.getCurrentContext(), new IRubyObject[]{ruby.newString("A message")}, Block.NULL_BLOCK);
View
7 test/test_backtraces.rb
@@ -284,10 +284,9 @@ def test_exception_from_thread_with_abort_on_exception_true
t.join
}
- assert_match(
- /test_backtraces.rb:#{@offset + 2}.*DUMMY_MSG.*RuntimeError/,
- $stderr.string
- )
+ assert_match /RuntimeError/, $stderr.string
+ assert_match /DUMMY_MSG/, $stderr.string
+ assert_match /test_backtraces.rb:#{@offset + 2}/m, $stderr.string
assert_equal(SystemExit, ex.class)

0 comments on commit 597a16d

Please sign in to comment.
Something went wrong with that request. Please try again.