mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 06:11:37 +00:00
Implementing bug 274467: Add JavaScript stack trace to exceptions
The changes are based on that patch from Attila Szegedi, szegedia@freemail.hu.
This commit is contained in:
parent
4634c663fa
commit
344cf0f06e
@ -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;
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ class XMLName extends Ref
|
||||
{
|
||||
return new XMLName(uri, localName);
|
||||
}
|
||||
|
||||
|
||||
void initXMLObject(XMLObjectImpl xmlObject)
|
||||
{
|
||||
if (xmlObject == null) throw new IllegalArgumentException();
|
||||
|
Loading…
Reference in New Issue
Block a user