From c57a9a3c255bb00cced1295ddcf012abc4b6e6ba Mon Sep 17 00:00:00 2001 From: "igor%mir2.org" Date: Wed, 16 Feb 2005 19:57:03 +0000 Subject: [PATCH] Implementing bug 274467: Add JavaScript stack trace to exceptions The changes are based on that patch from Attila Szegedi, szegedia@freemail.hu. --- .../src/org/mozilla/javascript/Context.java | 10 +- .../javascript/InterpretedFunction.java | 11 +- .../org/mozilla/javascript/Interpreter.java | 202 +++++++++++++++--- .../mozilla/javascript/RhinoException.java | 36 ++++ .../mozilla/javascript/xmlimpl/XMLName.java | 2 +- 5 files changed, 226 insertions(+), 35 deletions(-) diff --git a/js/rhino/src/org/mozilla/javascript/Context.java b/js/rhino/src/org/mozilla/javascript/Context.java index 10e592daabb..5704f44bf2e 100644 --- a/js/rhino/src/org/mozilla/javascript/Context.java +++ b/js/rhino/src/org/mozilla/javascript/Context.java @@ -2271,7 +2271,7 @@ public class Context Context cx = getCurrentContext(); if (cx == null) return null; - if (cx.interpreterLineCounting != null) { + if (cx.lastInterpreterFrame != null) { return Interpreter.getSourcePositionFromStack(cx, linep); } /** @@ -2434,8 +2434,12 @@ public class Context */ Hashtable activationNames; - // For the interpreter to indicate line/source for error reports. - Object interpreterLineCounting; + // For the interpreter to store the last frame for error reports etc. + Object lastInterpreterFrame; + + // For the interpreter to store information about previous invocations + // interpreter invocations + ObjArray previousInterpreterInvocations; // For instruction counting (interpreter only) int instructionCount; diff --git a/js/rhino/src/org/mozilla/javascript/InterpretedFunction.java b/js/rhino/src/org/mozilla/javascript/InterpretedFunction.java index 430da98f72e..7296c27d689 100644 --- a/js/rhino/src/org/mozilla/javascript/InterpretedFunction.java +++ b/js/rhino/src/org/mozilla/javascript/InterpretedFunction.java @@ -141,6 +141,9 @@ final class InterpretedFunction extends NativeFunction implements Script public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + if (!ScriptRuntime.hasTopCall(cx)) { + return ScriptRuntime.doTopCall(this, cx, scope, thisObj, args); + } return Interpreter.interpret(this, cx, scope, thisObj, args); } @@ -150,7 +153,13 @@ final class InterpretedFunction extends NativeFunction implements Script // Can only be applied to scripts throw new IllegalStateException(); } - return call(cx, scope, scope, ScriptRuntime.emptyArgs); + if (!ScriptRuntime.hasTopCall(cx)) { + // It will go through "call" path. but they are equivalent + return ScriptRuntime.doTopCall( + this, cx, scope, scope, ScriptRuntime.emptyArgs); + } + return Interpreter.interpret( + this, cx, scope, scope, ScriptRuntime.emptyArgs); } public String getEncodedSource() diff --git a/js/rhino/src/org/mozilla/javascript/Interpreter.java b/js/rhino/src/org/mozilla/javascript/Interpreter.java index 1c2a1e25ab3..65b914f3658 100644 --- a/js/rhino/src/org/mozilla/javascript/Interpreter.java +++ b/js/rhino/src/org/mozilla/javascript/Interpreter.java @@ -41,10 +41,12 @@ package org.mozilla.javascript; -import java.io.*; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.Serializable; -import org.mozilla.javascript.debug.*; import org.mozilla.javascript.continuations.Continuation; +import org.mozilla.javascript.debug.DebugFrame; public class Interpreter { @@ -2073,9 +2075,64 @@ public class Interpreter return presentLines.getKeys(); } + static void captureInterpreterStackInfo(RhinoException ex) + { + Context cx = Context.getCurrentContext(); + if (cx == null || cx.lastInterpreterFrame == null) { + // No interpreter invocations + ex.interpreterStackInfo = null; + ex.interpreterLineData = null; + return; + } + // has interpreter frame on the stack + CallFrame[] array; + if (cx.previousInterpreterInvocations == null + || cx.previousInterpreterInvocations.size() == 0) + { + array = new CallFrame[1]; + } else { + int previousCount = cx.previousInterpreterInvocations.size(); + if (cx.previousInterpreterInvocations.peek() + == cx.lastInterpreterFrame) + { + // It can happen if exception was generated after + // frame was pushed to cx.previousInterpreterInvocations + // but before assignment to cx.lastInterpreterFrame. + // In this case frames has to be ignored. + --previousCount; + } + array = new CallFrame[previousCount + 1]; + cx.previousInterpreterInvocations.toArray(array); + } + array[array.length - 1] = (CallFrame)cx.lastInterpreterFrame; + + int interpreterFrameCount = 0; + for (int i = 0; i != array.length; ++i) { + interpreterFrameCount += 1 + array[i].frameIndex; + } + + int[] linePC = new int[interpreterFrameCount]; + // Fill linePC with pc positions from all interpreter frames. + // Start from the most nested frame + int linePCIndex = interpreterFrameCount; + for (int i = array.length; i != 0;) { + --i; + CallFrame frame = array[i]; + while (frame != null) { + --linePCIndex; + linePC[linePCIndex] = frame.pcSourceLineStart; + frame = frame.parentFrame; + } + } + if (linePCIndex != 0) Kit.codeBug(); + + ex.interpreterStackInfo = array; + ex.interpreterLineData = linePC; + } + static String getSourcePositionFromStack(Context cx, int[] linep) { - CallFrame frame = (CallFrame)cx.interpreterLineCounting; + CallFrame frame = (CallFrame)cx.lastInterpreterFrame; InterpreterData idata = frame.idata; if (frame.pcSourceLineStart >= 0) { linep[0] = getIndex(idata.itsICode, frame.pcSourceLineStart); @@ -2085,6 +2142,61 @@ public class Interpreter return idata.itsSourceFile; } + static String getPatchedStack(RhinoException ex, + String nativeStackTrace) + { + String tag = "org.mozilla.javascript.Interpreter.interpretLoop"; + StringBuffer sb = new StringBuffer(nativeStackTrace.length() + 1000); + String lineSeparator = System.getProperty("line.separator"); + + CallFrame[] array = (CallFrame[])ex.interpreterStackInfo; + int[] linePC = ex.interpreterLineData; + int arrayIndex = array.length; + int linePCIndex = linePC.length; + int offset = 0; + while (arrayIndex != 0) { + --arrayIndex; + int pos = nativeStackTrace.indexOf(tag, offset); + if (pos < 0) { + break; + } + + // Skip tag length + pos += tag.length(); + // Skip until the end of line + for (; pos != nativeStackTrace.length(); ++pos) { + char c = nativeStackTrace.charAt(pos); + if (c == '\n' || c == '\r') { + break; + } + } + sb.append(nativeStackTrace.substring(offset, pos)); + offset = pos; + + CallFrame frame = array[arrayIndex]; + while (frame != null) { + if (linePCIndex == 0) Kit.codeBug(); + --linePCIndex; + InterpreterData idata = frame.idata; + sb.append(lineSeparator); + sb.append("\tat script"); + if (idata.itsName != null && idata.itsName.length() != 0) { + sb.append('.'); + sb.append(idata.itsName); + } + sb.append('('); + sb.append(idata.itsSourceFile); + sb.append(':'); + sb.append(getIndex(idata.itsICode, linePC[linePCIndex])); + sb.append(')'); + frame = frame.parentFrame; + } + } + sb.append(nativeStackTrace.substring(offset)); + + return sb.toString(); + } + static String getEncodedSource(InterpreterData idata) { if (idata.encodedSource == null) { @@ -2107,9 +2219,8 @@ public class Interpreter Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { - if (!ScriptRuntime.hasTopCall(cx)) { - return ScriptRuntime.doTopCall(ifun, cx, scope, thisObj, args); - } + if (!ScriptRuntime.hasTopCall(cx)) Kit.codeBug(); + if (cx.interpreterSecurityDomain != ifun.securityDomain) { Object savedDomain = cx.interpreterSecurityDomain; cx.interpreterSecurityDomain = ifun.securityDomain; @@ -2125,15 +2236,7 @@ public class Interpreter initFrame(cx, scope, thisObj, args, null, 0, args.length, ifun, null, frame); - Object result; - try { - result = interpret(cx, frame, null); - } finally { - // Always clenup interpreterLineCounting to avoid memory leaks - // throgh stored in Context frame - cx.interpreterLineCounting = null; - } - return result; + return interpretLoop(cx, frame, null); } public static Object restartContinuation(Continuation c, Context cx, @@ -2159,11 +2262,11 @@ public class Interpreter ContinuationJump cjump = new ContinuationJump(c, null); cjump.result = arg; - return interpret(cx, null, cjump); + return interpretLoop(cx, null, cjump); } - private static Object interpret(Context cx, CallFrame frame, - Object throwable) + private static Object interpretLoop(Context cx, CallFrame frame, + Object throwable) { // throwable holds exception object to rethrow or catch // It is also used for continuation restart in which case @@ -2182,6 +2285,15 @@ public class Interpreter String stringReg = null; int indexReg = -1; + if (cx.lastInterpreterFrame != null) { + // save the top frame from the previous interpreterLoop + // invocation on the stack + if (cx.previousInterpreterInvocations == null) { + cx.previousInterpreterInvocations = new ObjArray(); + } + cx.previousInterpreterInvocations.push(cx.lastInterpreterFrame); + } + // When restarting continuation throwable is not null and to jump // to the code that rewind continuation state indexReg should be set // to -1. @@ -2197,6 +2309,9 @@ public class Interpreter } } + Object interpreterResult = null; + double interpreterResultDbl = 0.0; + StateLoop: for (;;) { withoutExceptions: try { @@ -2289,6 +2404,10 @@ public class Interpreter setCallResult(frame, cjump.result, cjump.resultDbl); // restart the execution } + + // Should be already cleared + if (throwable != null) Kit.codeBug(); + } else { if (frame.frozen) Kit.codeBug(); } @@ -2308,8 +2427,8 @@ public class Interpreter // function calls and normal returns. int stackTop = frame.savedStackTop; - // Point line counting to the new frame - cx.interpreterLineCounting = frame; + // Store new frame in cx which is used for error reporting etc. + cx.lastInterpreterFrame = frame; Loop: for (;;) { @@ -3402,19 +3521,19 @@ switch (op) { } // end of Loop: for exitFrame(cx, frame, null); - Object callResult = frame.result; - double callResultDbl = frame.resultDbl; + interpreterResult = frame.result; + interpreterResultDbl = frame.resultDbl; if (frame.parentFrame != null) { frame = frame.parentFrame; if (frame.frozen) { frame = frame.cloneFrozen(); } - setCallResult(frame, callResult, callResultDbl); + setCallResult( + frame, interpreterResult, interpreterResultDbl); + interpreterResult = null; // Help GC continue StateLoop; } - - return (callResult != DBL_MRK) - ? callResult : ScriptRuntime.wrapNumber(callResultDbl); + break StateLoop; } // end of interpreter withoutExceptions: try catch (Throwable ex) { @@ -3524,17 +3643,40 @@ switch (op) { continue StateLoop; } // Return continuation result to the caller - return (cjump.result != DBL_MRK) - ? cjump.result : ScriptRuntime.wrapNumber(cjump.resultDbl); + interpreterResult = cjump.result; + interpreterResultDbl = cjump.resultDbl; + throwable = null; } + break StateLoop; + + } // end of StateLoop: for(;;) + + // Do cleanups/restorations before the final return or throw + + if (cx.previousInterpreterInvocations != null + && cx.previousInterpreterInvocations.size() != 0) + { + cx.lastInterpreterFrame + = cx.previousInterpreterInvocations.pop(); + } else { + // It was the last interpreter frame on the stack + cx.lastInterpreterFrame = null; + // Force GC of the value cx.previousInterpreterInvocations + cx.previousInterpreterInvocations = null; + } + + if (throwable != null) { if (throwable instanceof RuntimeException) { throw (RuntimeException)throwable; } else { // Must be instance of Error or code bug throw (Error)throwable; } + } - } // end of StateLoop: for(;;) + return (interpreterResult != DBL_MRK) + ? interpreterResult + : ScriptRuntime.wrapNumber(interpreterResultDbl); } private static void initFrame(Context cx, Scriptable callerScope, @@ -3710,7 +3852,7 @@ switch (op) { if (frame.debuggerFrame != null) { try { if (throwable instanceof Throwable) { - frame.debuggerFrame.onExit(cx, true, (Throwable)throwable); + frame.debuggerFrame.onExit(cx, true, throwable); } else { Object result; ContinuationJump cjump = (ContinuationJump)throwable; diff --git a/js/rhino/src/org/mozilla/javascript/RhinoException.java b/js/rhino/src/org/mozilla/javascript/RhinoException.java index 341bc1b5e56..b281972f0aa 100644 --- a/js/rhino/src/org/mozilla/javascript/RhinoException.java +++ b/js/rhino/src/org/mozilla/javascript/RhinoException.java @@ -37,6 +37,10 @@ package org.mozilla.javascript; +import java.io.CharArrayWriter; +import java.io.PrintStream; +import java.io.PrintWriter; + /** * The class of exceptions thrown by the JavaScript engine. */ @@ -44,11 +48,13 @@ public abstract class RhinoException extends RuntimeException { RhinoException() { + Interpreter.captureInterpreterStackInfo(this); } RhinoException(String details) { super(details); + Interpreter.captureInterpreterStackInfo(this); } public final String getMessage() @@ -191,8 +197,38 @@ public abstract class RhinoException extends RuntimeException } } + private String generateStackTrace() + { + // Get stable reference to work properly with concurrent access + CharArrayWriter writer = new CharArrayWriter(); + super.printStackTrace(new PrintWriter(writer)); + String origStackTrace = writer.toString(); + return Interpreter.getPatchedStack(this, origStackTrace); + } + + public void printStackTrace(PrintWriter s) + { + if (interpreterStackInfo == null) { + super.printStackTrace(s); + } else { + s.print(generateStackTrace()); + } + } + + public void printStackTrace(PrintStream s) + { + if (interpreterStackInfo == null) { + super.printStackTrace(s); + } else { + s.print(generateStackTrace()); + } + } + private String sourceName; private int lineNumber; private String lineSource; private int columnNumber; + + Object interpreterStackInfo; + int[] interpreterLineData; } diff --git a/js/rhino/xmlimplsrc/org/mozilla/javascript/xmlimpl/XMLName.java b/js/rhino/xmlimplsrc/org/mozilla/javascript/xmlimpl/XMLName.java index cd9e8fd7394..e2a11c23afd 100644 --- a/js/rhino/xmlimplsrc/org/mozilla/javascript/xmlimpl/XMLName.java +++ b/js/rhino/xmlimplsrc/org/mozilla/javascript/xmlimpl/XMLName.java @@ -66,7 +66,7 @@ class XMLName extends Ref { return new XMLName(uri, localName); } - + void initXMLObject(XMLObjectImpl xmlObject) { if (xmlObject == null) throw new IllegalArgumentException();