* Get webkit source (~30 minutes) `git clone --depth 1 https://github.com/WebKit/webkit.git` `cd webkit` * Apply tmm1's patch (found at end of this document) `patch -p1 < tmm1.patch` * Build webkit (~2 hours) `Tools/Scripts/build-webkit` # tmm1's patch ``` diff --git a/LayoutTests/fast/js/exception-properties-expected.txt b/LayoutTests/fast/js/exception-properties-expected.txt index 36d7e84..573ef10 100644 --- a/LayoutTests/fast/js/exception-properties-expected.txt +++ b/LayoutTests/fast/js/exception-properties-expected.txt @@ -4,7 +4,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE PASS enumerableProperties(error) is [] -PASS enumerableProperties(nativeError) is ["line", "sourceId", "sourceURL"] +PASS enumerableProperties(nativeError) is ["line", "sourceId", "sourceURL", "stack"] PASS Object.getPrototypeOf(nativeError).name is "RangeError" PASS Object.getPrototypeOf(nativeError).message is "" PASS successfullyParsed is true diff --git a/LayoutTests/fast/js/script-tests/exception-properties.js b/LayoutTests/fast/js/script-tests/exception-properties.js index 30789a0..f7c2871 100644 --- a/LayoutTests/fast/js/script-tests/exception-properties.js +++ b/LayoutTests/fast/js/script-tests/exception-properties.js @@ -16,7 +16,7 @@ try { var error = new Error("message"); shouldBe('enumerableProperties(error)', '[]'); - shouldBe('enumerableProperties(nativeError)', '["line", "sourceId", "sourceURL"]'); + shouldBe('enumerableProperties(nativeError)', '["line", "sourceId", "sourceURL", "jscStack"]'); shouldBe('Object.getPrototypeOf(nativeError).name', '"RangeError"'); shouldBe('Object.getPrototypeOf(nativeError).message', '""'); diff --git a/LayoutTests/platform/chromium/test_expectations.txt b/LayoutTests/platform/chromium/test_expectations.txt index f6e35e0..e6d0b90 100644 --- a/LayoutTests/platform/chromium/test_expectations.txt +++ b/LayoutTests/platform/chromium/test_expectations.txt @@ -518,6 +518,10 @@ WONTFIX SKIP : fast/frames/cross-site-this.html = FAIL // throw. V8 follows the spec. WONTFIX SKIP : fast/js/reparsing-semicolon-insertion.html = FAIL +// This tests stack-traces that are generated by JSC. This test should +// fail since it is specific to jsc. +WONTFIX SKIP : fast/js/stack-trace.html = FAIL + // Rubber-banding is currently a CG only feature. WONTFIX : platform/chromium/rubberbanding = FAIL WONTFIX : platform/chromium/compositing/rubberbanding = IMAGE diff --git a/Source/JavaScriptCore/JavaScriptCore.exp b/Source/JavaScriptCore/JavaScriptCore.exp index 4bf3ed9..1d11aea 100644 --- a/Source/JavaScriptCore/JavaScriptCore.exp +++ b/Source/JavaScriptCore/JavaScriptCore.exp @@ -117,6 +117,7 @@ __ZN3JSC10JSFunction6s_infoE __ZN3JSC10JSFunctionC1EPNS_9ExecStateEPNS_14JSGlobalObjectEPNS_9StructureE __ZN3JSC10throwErrorEPNS_9ExecStateENS_7JSValueE __ZN3JSC10throwErrorEPNS_9ExecStateEPNS_8JSObjectE +__ZN3JSC11Interpreter13getStackTraceEPNS_12JSGlobalDataEiRN3WTF6VectorINS_10StackFrameELm0EEE __ZN3JSC11JSByteArray10putByIndexEPNS_6JSCellEPNS_9ExecStateEjNS_7JSValueE __ZN3JSC11JSByteArray15createStructureERNS_12JSGlobalDataEPNS_14JSGlobalObjectENS_7JSValueEPKNS_9ClassInfoE __ZN3JSC11JSByteArray18getOwnPropertySlotEPNS_6JSCellEPNS_9ExecStateERKNS_10IdentifierERNS_12PropertySlotE diff --git a/Source/JavaScriptCore/JavaScriptCore.vcproj/JavaScriptCore/JavaScriptCore.def b/Source/JavaScriptCore/JavaScriptCore.vcproj/JavaScriptCore/JavaScriptCore.def index 5095d5c..13ee907 100644 --- a/Source/JavaScriptCore/JavaScriptCore.vcproj/JavaScriptCore/JavaScriptCore.def +++ b/Source/JavaScriptCore/JavaScriptCore.vcproj/JavaScriptCore/JavaScriptCore.def @@ -208,6 +208,7 @@ EXPORTS ?getPropertyDescriptor@JSObject@JSC@@QAE_NPAVExecState@2@ABVIdentifier@2@AAVPropertyDescriptor@2@@Z ?getPropertyNames@JSObject@JSC@@SAXPAV12@PAVExecState@2@AAVPropertyNameArray@2@W4EnumerationMode@2@@Z ?getSlice@ArgList@JSC@@QBEXHAAV12@@Z + ?getStackTrace@Interpreter@JSC@@SAXPAVJSGlobalData@2@HAAV?$Vector@UStackFrame@JSC@@$0A@@WTF@@@Z ?getString@JSCell@JSC@@QBE?AVUString@2@PAVExecState@2@@Z ?getString@JSCell@JSC@@QBE_NPAVExecState@2@AAVUString@2@@Z ?getter@PropertyDescriptor@JSC@@QBE?AVJSValue@2@XZ diff --git a/Source/JavaScriptCore/interpreter/Interpreter.cpp b/Source/JavaScriptCore/interpreter/Interpreter.cpp index 4194901..b9d07dd 100644 --- a/Source/JavaScriptCore/interpreter/Interpreter.cpp +++ b/Source/JavaScriptCore/interpreter/Interpreter.cpp @@ -45,7 +45,6 @@ #include "JSActivation.h" #include "JSArray.h" #include "JSByteArray.h" -#include "JSFunction.h" #include "JSNotAnObject.h" #include "JSPropertyNameIterator.h" #include "LiteralParser.h" @@ -790,6 +789,95 @@ static void appendSourceToError(CallFrame* callFrame, ErrorInstance* exception, exception->putDirect(*globalData, globalData->propertyNames->message, jsString(globalData, message)); } +static void getCallerLine(JSGlobalData* globalData, CallFrame* callFrame, int& lineNumber) +{ + (void)globalData; + unsigned bytecodeOffset; + lineNumber = -1; + callFrame = callFrame->removeHostCallFrameFlag(); + + if (callFrame->callerFrame() == CallFrame::noCaller() || callFrame->callerFrame()->hasHostCallFrameFlag()) + return; + + CodeBlock* callerCodeBlock = callFrame->callerFrame()->removeHostCallFrameFlag()->codeBlock(); + +#if ENABLE(INTERPRETER) + if (!globalData->canUseJIT()) + bytecodeOffset = callerCodeBlock->bytecodeOffset(callFrame->returnVPC()); +#if ENABLE(JIT) + else + bytecodeOffset = callerCodeBlock->bytecodeOffset(callFrame->returnPC()); +#endif +#else + bytecodeOffset = callerCodeBlock->bytecodeOffset(callFrame->returnPC()); +#endif + + lineNumber = callerCodeBlock->lineNumberForBytecodeOffset(bytecodeOffset - 1); +} + +static ALWAYS_INLINE const UString getSourceURLFromCallFrame(CallFrame* callFrame) +{ + if (callFrame->hasHostCallFrameFlag()) + return UString(); +#if ENABLE(INTERPRETER) + if (!callFrame->globalData().canUseJIT()) + return callFrame->codeBlock()->source()->url(); +#if ENABLE(JIT) + return callFrame->codeBlock()->ownerExecutable()->sourceURL(); +#endif +#else + return callFrame->codeBlock()->ownerExecutable()->sourceURL(); +#endif +} + +static StackFrameCodeType getStackFrameCodeType(CallFrame* callFrame) +{ + if (callFrame->hasHostCallFrameFlag()) + return StackFrameNativeCode; + + switch (callFrame->codeBlock()->codeType()) { + case EvalCode: + return StackFrameEvalCode; + case FunctionCode: + return StackFrameFunctionCode; + case GlobalCode: + return StackFrameGlobalCode; + } + ASSERT_NOT_REACHED(); + return StackFrameGlobalCode; +} + +void Interpreter::getStackTrace(JSGlobalData* globalData, int line, Vector& results) +{ + int stackLimit = 32; + CallFrame* callFrame = globalData->topCallFrame->removeHostCallFrameFlag(); + if (!callFrame || callFrame == CallFrame::noCaller() || !callFrame->codeBlock()) + return; + UString sourceURL; + UString traceLevel; + + for (int i = 0; i < stackLimit; ++i) { + if (!callFrame || callFrame == CallFrame::noCaller()) + break; + if (callFrame->codeBlock()) { + sourceURL = getSourceURLFromCallFrame(callFrame); + + StackFrame s = { + Strong(*globalData, callFrame->callee()), + Strong(*globalData, callFrame), + getStackFrameCodeType(callFrame), + Strong(*globalData, callFrame->codeBlock()->ownerExecutable()), + line, + sourceURL + }; + + results.append(s); + } + getCallerLine(globalData, callFrame, line); + callFrame = callFrame->callerFrame()->removeHostCallFrameFlag(); + } +} + NEVER_INLINE HandlerInfo* Interpreter::throwException(CallFrame*& callFrame, JSValue& exceptionValue, unsigned bytecodeOffset) { CodeBlock* codeBlock = callFrame->codeBlock(); @@ -808,7 +896,9 @@ NEVER_INLINE HandlerInfo* Interpreter::throwException(CallFrame*& callFrame, JSV // FIXME: should only really be adding these properties to VM generated exceptions, // but the inspector currently requires these for all thrown objects. - addErrorInfo(callFrame, exception, codeBlock->lineNumberForBytecodeOffset(bytecodeOffset), codeBlock->ownerExecutable()->source()); + Vector stackTrace; + getStackTrace(&callFrame->globalData(), codeBlock->lineNumberForBytecodeOffset(bytecodeOffset), stackTrace); + addErrorInfo(callFrame, exception, codeBlock->lineNumberForBytecodeOffset(bytecodeOffset), codeBlock->ownerExecutable()->source(), stackTrace); } isInterrupt = isInterruptedExecutionException(exception) || isTerminatedExecutionException(exception); diff --git a/Source/JavaScriptCore/interpreter/Interpreter.h b/Source/JavaScriptCore/interpreter/Interpreter.h index 6dfd331..748e8fb 100644 --- a/Source/JavaScriptCore/interpreter/Interpreter.h +++ b/Source/JavaScriptCore/interpreter/Interpreter.h @@ -31,10 +31,12 @@ #include "ArgList.h" #include "JSCell.h" +#include "JSFunction.h" #include "JSValue.h" #include "JSObject.h" #include "Opcode.h" #include "RegisterFile.h" +#include "StrongInlines.h" #include @@ -42,8 +44,8 @@ namespace JSC { class CodeBlock; class EvalExecutable; + class ExecutableBase; class FunctionExecutable; - class JSFunction; class JSGlobalObject; class ProgramExecutable; class Register; @@ -62,16 +64,79 @@ namespace JSC { WillExecuteStatement }; + enum StackFrameCodeType { + StackFrameGlobalCode, + StackFrameEvalCode, + StackFrameFunctionCode, + StackFrameNativeCode + }; + + struct StackFrame { + Strong callee; + Strong callFrame; + StackFrameCodeType codeType; + Strong executable; + int line; + UString sourceURL; + UString toString() const + { + bool hasSourceURLInfo = !sourceURL.isNull() && !sourceURL.isEmpty(); + bool hasLineInfo = line > -1; + String traceLine; + String sourceInfo; + JSObject* stackFrameCallee = callee.get(); + UString functionName = ""; + + if (hasSourceURLInfo) + sourceInfo = hasLineInfo ? String::format("%s:%d", sourceURL.ascii().data(), line) + : String::format("%s", sourceURL.ascii().data()); + + if (stackFrameCallee && stackFrameCallee->inherits(&JSFunction::s_info)) + functionName = asFunction(stackFrameCallee)->name(callFrame.get()); + + switch (codeType) { + case StackFrameEvalCode: + if (hasSourceURLInfo && !functionName.isEmpty()) + traceLine = String::format("at eval at %s (%s)", functionName.ascii().data(), sourceInfo.ascii().data()); + else if (hasSourceURLInfo) + traceLine = String::format("at eval at (%s)", sourceInfo.ascii().data()); + else if (!functionName.isEmpty()) + traceLine = String::format("at eval at %s", functionName.ascii().data()); + else + traceLine = String::format("at eval"); + break; + case StackFrameNativeCode: + if (!functionName.isEmpty()) + traceLine = String::format("at %s (native)", functionName.ascii().data()); + else + traceLine = "at (native)"; + break; + case StackFrameFunctionCode: + case StackFrameGlobalCode: + if (hasSourceURLInfo && !functionName.isEmpty()) + traceLine = String::format("at %s (%s)", functionName.ascii().data(), sourceInfo.ascii().data()); + else if (hasSourceURLInfo) + traceLine = String::format("at %s", sourceInfo.ascii().data()); + else if (!functionName.isEmpty()) + traceLine = String::format("at %s", functionName.ascii().data()); + else + traceLine = String::format("at unknown source"); + break; + } + return traceLine.impl(); + } + }; + class TopCallFrameSetter { public: TopCallFrameSetter(JSGlobalData& global, CallFrame* callFrame) : globalData(global) - , oldCallFrame(global.topCallFrame) + , oldCallFrame(global.topCallFrame) { global.topCallFrame = callFrame; } - - ~TopCallFrameSetter() + + ~TopCallFrameSetter() { globalData.topCallFrame = oldCallFrame; } @@ -141,6 +206,8 @@ namespace JSC { NEVER_INLINE HandlerInfo* throwException(CallFrame*&, JSValue&, unsigned bytecodeOffset); NEVER_INLINE void debug(CallFrame*, DebugHookID, int firstLine, int lastLine); + static const UString getTraceLine(CallFrame*, StackFrameCodeType, const UString&, int); + static void getStackTrace(JSGlobalData*, int line, Vector& results); void dumpSampleData(ExecState* exec); void startSampling(); diff --git a/Source/JavaScriptCore/jsc.cpp b/Source/JavaScriptCore/jsc.cpp index 47ec8c6..6d82f54 100644 --- a/Source/JavaScriptCore/jsc.cpp +++ b/Source/JavaScriptCore/jsc.cpp @@ -27,6 +27,7 @@ #include "CurrentTime.h" #include "ExceptionHelpers.h" #include "InitializeThreading.h" +#include "Interpreter.h" #include "JSArray.h" #include "JSFunction.h" #include "JSLock.h" @@ -78,6 +79,7 @@ static bool fillBufferWithContentsOfFile(const UString& fileName, Vector& static EncodedJSValue JSC_HOST_CALL functionPrint(ExecState*); static EncodedJSValue JSC_HOST_CALL functionDebug(ExecState*); +static EncodedJSValue JSC_HOST_CALL functionJSCStack(ExecState*); static EncodedJSValue JSC_HOST_CALL functionGC(ExecState*); #ifndef NDEBUG static EncodedJSValue JSC_HOST_CALL functionReleaseExecutableMemory(ExecState*); @@ -184,6 +186,7 @@ protected: addFunction(globalData, "run", functionRun, 1); addFunction(globalData, "load", functionLoad, 1); addFunction(globalData, "checkSyntax", functionCheckSyntax, 1); + addFunction(globalData, "jscStack", functionJSCStack, 1); addFunction(globalData, "readline", functionReadline, 0); addFunction(globalData, "preciseTime", functionPreciseTime, 0); #if ENABLE(SAMPLING_FLAGS) @@ -252,6 +255,22 @@ EncodedJSValue JSC_HOST_CALL functionDebug(ExecState* exec) return JSValue::encode(jsUndefined()); } +EncodedJSValue JSC_HOST_CALL functionJSCStack(ExecState* exec) +{ + String trace = "--> Stack trace:\n"; + Vector stackTrace; + Interpreter::getStackTrace(&exec->globalData(), -1, stackTrace); + int i = 0; + + for (Vector::iterator iter = stackTrace.begin(); iter < stackTrace.end(); iter++) { + StackFrame level = *iter; + trace += String::format(" %i %s\n", i, level.toString().utf8().data()); + i++; + } + fprintf(stderr, "%s", trace.utf8().data()); + return JSValue::encode(jsUndefined()); +} + EncodedJSValue JSC_HOST_CALL functionGC(ExecState* exec) { JSLock lock(SilenceAssertionsOnly); diff --git a/Source/JavaScriptCore/parser/Parser.h b/Source/JavaScriptCore/parser/Parser.h index f3d96ff..27dafae 100644 --- a/Source/JavaScriptCore/parser/Parser.h +++ b/Source/JavaScriptCore/parser/Parser.h @@ -1016,7 +1016,7 @@ PassRefPtr Parser::parse(JSGlobalObject* lexicalGlobalObj else if (isEvalNode()) *exception = createSyntaxError(lexicalGlobalObject, errMsg); else - *exception = addErrorInfo(&lexicalGlobalObject->globalData(), createSyntaxError(lexicalGlobalObject, errMsg), errLine, *m_source); + *exception = addErrorInfo(&lexicalGlobalObject->globalData(), createSyntaxError(lexicalGlobalObject, errMsg), errLine, *m_source, Vector()); } if (debugger && !ParsedNode::scopeIsFunction) diff --git a/Source/JavaScriptCore/runtime/CommonIdentifiers.h b/Source/JavaScriptCore/runtime/CommonIdentifiers.h index 08d8644..ce11730 100644 --- a/Source/JavaScriptCore/runtime/CommonIdentifiers.h +++ b/Source/JavaScriptCore/runtime/CommonIdentifiers.h @@ -52,6 +52,7 @@ macro(input) \ macro(isArray) \ macro(isPrototypeOf) \ + macro(stack) \ macro(length) \ macro(message) \ macro(multiline) \ diff --git a/Source/JavaScriptCore/runtime/Error.cpp b/Source/JavaScriptCore/runtime/Error.cpp index c3f2878..9b4af6f 100644 --- a/Source/JavaScriptCore/runtime/Error.cpp +++ b/Source/JavaScriptCore/runtime/Error.cpp @@ -27,6 +27,7 @@ #include "ConstructData.h" #include "ErrorConstructor.h" #include "FunctionPrototype.h" +#include "JSArray.h" #include "JSFunction.h" #include "JSGlobalObject.h" #include "JSObject.h" @@ -39,6 +40,8 @@ namespace JSC { static const char* linePropertyName = "line"; static const char* sourceIdPropertyName = "sourceId"; static const char* sourceURLPropertyName = "sourceURL"; +static const char* stackPropertyName = "stack"; +static const char* messagePropertyName = "message"; JSObject* createError(JSGlobalObject* globalObject, const UString& message) { @@ -117,7 +120,7 @@ JSObject* createURIError(ExecState* exec, const UString& message) return createURIError(exec->lexicalGlobalObject(), message); } -JSObject* addErrorInfo(JSGlobalData* globalData, JSObject* error, int line, const SourceCode& source) +JSObject* addErrorInfo(JSGlobalData* globalData, JSObject* error, int line, const SourceCode& source, const Vector& stackTrace) { intptr_t sourceID = source.provider()->asID(); const UString& sourceURL = source.provider()->url(); @@ -128,13 +131,29 @@ JSObject* addErrorInfo(JSGlobalData* globalData, JSObject* error, int line, cons error->putDirect(*globalData, Identifier(globalData, sourceIdPropertyName), jsNumber((double)sourceID), ReadOnly | DontDelete); if (!sourceURL.isNull()) error->putDirect(*globalData, Identifier(globalData, sourceURLPropertyName), jsString(globalData, sourceURL), ReadOnly | DontDelete); + if (!stackTrace.isEmpty()) { + String trace = String::format("%s: ", JSObject::className(error).ascii().data()); + trace += asString(error->getDirect(*globalData, Identifier(globalData, messagePropertyName)))->tryGetValue().ascii().data(); + trace += "\n"; + //JSArray* stackTraceArray = JSArray::create(*globalData, globalData->dynamicGlobalObject->arrayStructure()); + for (unsigned i = 0; i < stackTrace.size(); i++) { + UString stackLevel = stackTrace[i].toString(); + trace += " "; + trace += stackLevel.ascii().data(); + if (i < stackTrace.size() - 1) + trace += "\n"; + //stackTraceArray->push(globalData->topCallFrame, jsString(globalData, stackLevel)); + } + //error->putDirect(*globalData, Identifier(globalData, stackPropertyName), stackTraceArray, ReadOnly | DontDelete); + error->putDirect(*globalData, Identifier(globalData, stackPropertyName), jsString(globalData, UString(trace.impl())), ReadOnly | DontDelete); + } return error; } -JSObject* addErrorInfo(ExecState* exec, JSObject* error, int line, const SourceCode& source) +JSObject* addErrorInfo(ExecState* exec, JSObject* error, int line, const SourceCode& source, const Vector& stackTrace) { - return addErrorInfo(&exec->globalData(), error, line, source); + return addErrorInfo(&exec->globalData(), error, line, source, stackTrace); } bool hasErrorInfo(ExecState* exec, JSObject* error) diff --git a/Source/JavaScriptCore/runtime/Error.h b/Source/JavaScriptCore/runtime/Error.h index 88b540a..59b3949 100644 --- a/Source/JavaScriptCore/runtime/Error.h +++ b/Source/JavaScriptCore/runtime/Error.h @@ -24,6 +24,7 @@ #define Error_h #include "InternalFunction.h" +#include "Interpreter.h" #include "JSObject.h" #include @@ -56,9 +57,9 @@ namespace JSC { // Methods to add bool hasErrorInfo(ExecState*, JSObject* error); - JSObject* addErrorInfo(JSGlobalData*, JSObject* error, int line, const SourceCode&); + JSObject* addErrorInfo(JSGlobalData*, JSObject* error, int line, const SourceCode&, const Vector&); // ExecState wrappers. - JSObject* addErrorInfo(ExecState*, JSObject* error, int line, const SourceCode&); + JSObject* addErrorInfo(ExecState*, JSObject* error, int line, const SourceCode&, const Vector&); // Methods to throw Errors. JS_EXPORT_PRIVATE JSValue throwError(ExecState*, JSValue); ```