finally blocks should not set the exception stack trace when re-throwing the exception.
https://bugs.webkit.org/show_bug.cgi?id=145525
Reviewed by Geoffrey Garen.
Source/JavaScriptCore:
How exceptions presently work:
=============================
1. op_throw can throw any JSValue.
2. the VM tries to capture the stack at the throw point and propagate that as needed.
3. finally blocks are implemented using op_catch to catch the thrown value, and throws it again using op_throw.
What's wrong with how it presently works:
========================================
1. finally's makes for bad exception throw line numbers in the Inspector console.
The op_throw in finally will throw the value anew i.e. it captures a stack from the re-throw point.
As a result, the Inspector sees the finally block as the throw point. The original stack is lost.
2. finally's breaks the Inspector's "Breaks on Uncaught Exception"
This is because finally blocks are indistinguishable from catch blocks. As a result, a try-finally,
which should break in the Inspector on the throw, does not because the Inspector thought the
exception was "caught".
3. finally's yields confusing break points when the Inspector "Breaks on All Exceptions"
a. In a try-finally scenario, the Inspector breaks 2 times: 1 at the throw, 1 at the finally.
b. In a for-of loop (which has synthesized finallys), the Inspector will do another break.
Similarly for other cases of JS code which synthesize finallys.
c. At VM re-entry boundaries (e.g. js throws & returns to native code, which returns to js),
the Inspector will do another break if there's an uncaught exception.
How this patch fixes the issues:
===============================
1. We introduce an Exception object that wraps the thrown value and the exception stack.
When throwing an exception, the VM will check if the thrown value is an Exception
object or not. If it is not an Exception object, then we must be throwing a new
exception. The VM will create an Exception object to wrap the thrown value and
capture the current stack for it.
If the thrown value is already an Exception object, then the requested throw operation
must be a re-throw. The VM will not capture a new stack for it.
2. op_catch will now populate 2 locals: 1 for the Exception, 1 for the thrown JSValue.
The VM is aware of the Exception object and uses it for rethrows in finally blocks.
JS source code is never aware of the Exception object.
JS code is aware of the thrown value. If it throws the caught thrown value, that
constitutes a new throw, and a new Exception object will be created for it.
3. The VM no longer tracks the thrown JSValue and the exception stack. It will only
track a m_exception field which is an Exception*.
4. The BytecodeGenerator has already been updated in a prior patch to distinguish
between Catch, Finally, and SynthesizedFinally blocks. The interpreter runtime will
now report to the debugger whether we have a Catch handler, not just any handlers.
The debugger will use this detail to determine whether to break or not. "Break on
uncaught exceptions" will only break if no Catch handler was found.
This solves the issue of the debugger breaking at finally blocks, and for-of statements.
5. The Exception object will also have a flag to indicate whether the debugger has been
notified of the Exception being thrown. Once the Interpreter notifies the debugger
of the Exception object, it will mark this flag and not repeat the notify the debugger
again of the same Exception.
This solves the issue of the debugger breaking at VM re-entry points due to uncaught
exceptions.
6. The life-cycle of the captured exception stack trace will now follow the life-cycle
of the Exception object.
Other changes:
7. Change all clients of the VM::exception() to expect an Exception* instead of JSValue.
8. Fixed a few bugs where thrown exceptions are not cleared before exiting the VM.
9. Also renamed some variables and classes to better describe what they are.
* API/JSBase.cpp:
(JSEvaluateScript):
(JSCheckScriptSyntax):
* API/JSObjectRef.cpp:
(handleExceptionIfNeeded):
- The functions below all do the same exception check. Added this helper
to simplify the code.
(JSClassCreate):
(JSObjectMakeFunction):
(JSObjectMakeArray):
(JSObjectMakeDate):
(JSObjectMakeError):
(JSObjectMakeRegExp):
(JSObjectGetProperty):
(JSObjectSetProperty):
(JSObjectGetPropertyAtIndex):
(JSObjectSetPropertyAtIndex):
(JSObjectDeleteProperty):
(JSObjectCallAsFunction):
(JSObjectCallAsConstructor):
* API/JSScriptRef.cpp:
* API/JSValue.mm:
(JSContainerConvertor::take):
(reportExceptionToInspector):
* API/JSValueRef.cpp:
(handleExceptionIfNeeded):
- The functions below all do the same exception check. Added this helper
to simplify the code.
(evernoteHackNeeded):
(JSValueIsEqual):
(JSValueIsInstanceOfConstructor):
(JSValueCreateJSONString):
(JSValueToNumber):
(JSValueToStringCopy):
(JSValueToObject):
* CMakeLists.txt:
* JavaScriptCore.vcxproj/JavaScriptCore.vcxproj:
* JavaScriptCore.vcxproj/JavaScriptCore.vcxproj.filters:
* JavaScriptCore.xcodeproj/project.pbxproj:
- Added new files Exception.h and Exception.cpp.
* bindings/ScriptFunctionCall.cpp:
(Deprecated::ScriptFunctionCall::call):
* bindings/ScriptFunctionCall.h:
* bytecode/BytecodeList.json:
- op_catch now had 2 operands: the exception register, and the thrown value register.
* bytecode/BytecodeUseDef.h:
(JSC::computeDefsForBytecodeOffset):
* bytecode/CodeBlock.cpp:
(JSC::CodeBlock::dumpBytecode):
(JSC::CodeBlock::handlerForBytecodeOffset):
* bytecode/CodeBlock.h:
- handlerForBytecodeOffset() now can look for just Catch handlers only.
* bytecode/HandlerInfo.h:
- Cleaned up some white space I accidentally added in a previous patch.
* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::pushTry):
(JSC::BytecodeGenerator::popTryAndEmitCatch):
(JSC::BytecodeGenerator::emitThrowReferenceError):
(JSC::BytecodeGenerator::emitEnumeration):
* bytecompiler/BytecodeGenerator.h:
(JSC::BytecodeGenerator::emitThrow):
* bytecompiler/NodesCodegen.cpp:
(JSC::TryNode::emitBytecode):
- Adding support for op_catch's 2 operands.
* debugger/Debugger.cpp:
(JSC::Debugger::hasBreakpoint):
(JSC::Debugger::pauseIfNeeded):
(JSC::Debugger::exception):
* debugger/Debugger.h:
* debugger/DebuggerCallFrame.cpp:
(JSC::DebuggerCallFrame::thisValue):
(JSC::DebuggerCallFrame::evaluate):
* debugger/DebuggerCallFrame.h:
(JSC::DebuggerCallFrame::isValid):
* inspector/InjectedScriptManager.cpp:
(Inspector::InjectedScriptManager::createInjectedScript):
* inspector/InspectorEnvironment.h:
* inspector/JSGlobalObjectInspectorController.cpp:
(Inspector::JSGlobalObjectInspectorController::appendAPIBacktrace):
(Inspector::JSGlobalObjectInspectorController::reportAPIException):
* inspector/JSGlobalObjectInspectorController.h:
* inspector/JSGlobalObjectScriptDebugServer.h:
* inspector/JSJavaScriptCallFrame.cpp:
(Inspector::JSJavaScriptCallFrame::evaluate):
* inspector/JavaScriptCallFrame.h:
(Inspector::JavaScriptCallFrame::vmEntryGlobalObject):
(Inspector::JavaScriptCallFrame::thisValue):
(Inspector::JavaScriptCallFrame::evaluate):
* inspector/ScriptCallStackFactory.cpp:
(Inspector::extractSourceInformationFromException):
(Inspector::createScriptCallStackFromException):
* inspector/ScriptCallStackFactory.h:
* inspector/ScriptDebugServer.cpp:
(Inspector::ScriptDebugServer::evaluateBreakpointAction):
(Inspector::ScriptDebugServer::handleBreakpointHit):
(Inspector::ScriptDebugServer::handleExceptionInBreakpointCondition):
* inspector/ScriptDebugServer.h:
* interpreter/CallFrame.h:
(JSC::ExecState::clearException):
(JSC::ExecState::exception):
(JSC::ExecState::hadException):
(JSC::ExecState::atomicStringTable):
(JSC::ExecState::propertyNames):
(JSC::ExecState::clearSupplementaryExceptionInfo): Deleted.
* interpreter/Interpreter.cpp:
(JSC::unwindCallFrame):
(JSC::Interpreter::stackTraceAsString):
(JSC::GetCatchHandlerFunctor::GetCatchHandlerFunctor):
(JSC::GetCatchHandlerFunctor::operator()):
(JSC::Interpreter::unwind):
- Added a check for didNotifyInspectorOfThrow() here to prevent duplicate reports
of the same Exception to the debugger.
(JSC::GetExceptionHandlerFunctor::GetExceptionHandlerFunctor): Deleted.
(JSC::GetExceptionHandlerFunctor::operator()): Deleted.
- Renamed GetExceptionHandlerFunctor to GetCatchHandlerFunctor since the debugger
is only interested in knowing whether we have Catch handlers.
* interpreter/Interpreter.h:
(JSC::SuspendExceptionScope::SuspendExceptionScope):
(JSC::SuspendExceptionScope::~SuspendExceptionScope):
(JSC::Interpreter::sampler):
(JSC::ClearExceptionScope::ClearExceptionScope): Deleted.
(JSC::ClearExceptionScope::~ClearExceptionScope): Deleted.
- Renamed ClearExceptionScope to SuspendExceptionScope because "clear" implies that
we're purging the exception. Instead, we're merely suspending any handling of
that exception for a period defined by the scope.
* jit/AssemblyHelpers.cpp:
(JSC::AssemblyHelpers::emitExceptionCheck):
* jit/JITExceptions.cpp:
(JSC::genericUnwind):
- Removed the exception argument. It is always the value in VM::exception() anyway.
genericUnwind() can just get it from the VM, and save everyone some work.
* jit/JITExceptions.h:
* jit/JITOpcodes.cpp:
(JSC::JIT::emit_op_catch):
* jit/JITOpcodes32_64.cpp:
(JSC::JIT::privateCompileCTINativeCall):
(JSC::JIT::emit_op_catch):
- Add support for the new op_catch operands.
* jit/JITOperations.cpp:
* jit/ThunkGenerators.cpp:
(JSC::nativeForGenerator):
* jsc.cpp:
(functionRun):
(functionLoad):
(runWithScripts):
(runInteractive):
* llint/LLIntOffsetsExtractor.cpp:
* llint/LLIntSlowPaths.cpp:
(JSC::LLInt::LLINT_SLOW_PATH_DECL):
* llint/LowLevelInterpreter32_64.asm:
* llint/LowLevelInterpreter64.asm:
- Add support for the new op_catch operands. Also update the code to handle
VM::m_exception being an Exception pointer, not a JSValue.
* parser/NodeConstructors.h:
(JSC::TryNode::TryNode):
* parser/Nodes.h:
* runtime/CallData.cpp:
(JSC::call):
* runtime/CallData.h:
* runtime/Completion.cpp:
(JSC::evaluate):
* runtime/Completion.h:
(JSC::evaluate):
- Change evaluate() to take a reference to the returned exception value instead
of a pointer. In all but 2 or 3 cases, we want the returned exception anyway.
Might as well simplify the code by requiring the reference.
* runtime/Error.h:
(JSC::throwVMError):
(JSC::throwVMTypeError):
* runtime/Exception.cpp: Added.
(JSC::Exception::create):
(JSC::Exception::destroy):
(JSC::Exception::createStructure):
(JSC::Exception::visitChildren):
(JSC::Exception::Exception):
(JSC::Exception::~Exception):
* runtime/Exception.h: Added.
(JSC::Exception::valueOffset):
(JSC::Exception::cast):
(JSC::Exception::value):
(JSC::Exception::stack):
(JSC::Exception::didNotifyInspectorOfThrow):
(JSC::Exception::setDidNotifyInspectorOfThrow):
* runtime/ExceptionHelpers.cpp:
(JSC::createTerminatedExecutionException):
(JSC::isTerminatedExecutionException):
(JSC::createStackOverflowError):
* runtime/ExceptionHelpers.h:
* runtime/GetterSetter.cpp:
(JSC::callGetter):
* runtime/IteratorOperations.cpp:
(JSC::iteratorClose):
* runtime/JSObject.cpp:
* runtime/JSPromiseConstructor.cpp:
(JSC::constructPromise):
* runtime/JSPromiseDeferred.cpp:
(JSC::updateDeferredFromPotentialThenable):
(JSC::abruptRejection):
* runtime/JSPromiseReaction.cpp:
(JSC::ExecutePromiseReactionMicrotask::run):
* runtime/VM.cpp:
(JSC::VM::VM):
(JSC::VM::releaseExecutableMemory):
(JSC::VM::throwException):
(JSC::VM::setStackPointerAtVMEntry):
(JSC::VM::getExceptionInfo): Deleted.
(JSC::VM::setExceptionInfo): Deleted.
(JSC::VM::clearException): Deleted.
(JSC::clearExceptionStack): Deleted.
* runtime/VM.h:
(JSC::VM::targetMachinePCForThrowOffset):
(JSC::VM::clearException):
(JSC::VM::setException):
(JSC::VM::exception):
(JSC::VM::addressOfException):
(JSC::VM::exceptionStack): Deleted.
* runtime/VMEntryScope.cpp:
(JSC::VMEntryScope::VMEntryScope):
(JSC::VMEntryScope::setEntryScopeDidPopListener):
Source/WebCore:
Update to use the new JSC::Exception object.
Test: inspector/debugger/break-on-exceptions.html
* ForwardingHeaders/runtime/Exception.h: Added.
* bindings/js/JSCallbackData.cpp:
(WebCore::JSCallbackData::invokeCallback):
* bindings/js/JSCustomXPathNSResolver.cpp:
(WebCore::JSCustomXPathNSResolver::lookupNamespaceURI):
* bindings/js/JSDOMBinding.cpp:
(WebCore::jsArray):
(WebCore::reportException):
(WebCore::reportCurrentException):
* bindings/js/JSDOMBinding.h:
* bindings/js/JSErrorHandler.cpp:
(WebCore::JSErrorHandler::handleEvent):
* bindings/js/JSEventListener.cpp:
(WebCore::JSEventListener::handleEvent):
* bindings/js/JSMainThreadExecState.cpp:
(WebCore::JSMainThreadExecState::didLeaveScriptContext):
(WebCore::functionCallHandlerFromAnyThread):
(WebCore::evaluateHandlerFromAnyThread):
* bindings/js/JSMainThreadExecState.h:
(WebCore::JSMainThreadExecState::currentState):
(WebCore::JSMainThreadExecState::call):
(WebCore::JSMainThreadExecState::evaluate):
(WebCore::JSMainThreadExecState::runTask):
* bindings/js/JSMediaDevicesCustom.cpp:
(WebCore::JSMediaDevices::getUserMedia):
- Fixed a bug where the exception was not cleared before entering the VM to
call JS code.
* bindings/js/JSMutationCallback.cpp:
(WebCore::JSMutationCallback::call):
* bindings/js/ReadableJSStream.cpp:
(WebCore::getPropertyFromObject):
(WebCore::callFunction):
(WebCore::ReadableJSStream::Source::start):
* bindings/js/ScheduledAction.cpp:
(WebCore::ScheduledAction::executeFunctionInContext):
* bindings/js/ScriptController.cpp:
(WebCore::ScriptController::evaluateInWorld):
* bindings/js/SerializedScriptValue.cpp:
(WebCore::SerializedScriptValue::create):
(WebCore::SerializedScriptValue::deserialize):
* bindings/js/WorkerScriptController.cpp:
(WebCore::WorkerScriptController::evaluate):
(WebCore::WorkerScriptController::setException):
(WebCore::WorkerScriptController::scheduleExecutionTermination):
* bindings/js/WorkerScriptController.h:
(WebCore::WorkerScriptController::workerGlobalScopeWrapper):
* bindings/js/WorkerScriptDebugServer.cpp:
(WebCore::WorkerScriptDebugServer::runEventLoopWhilePaused):
(WebCore::WorkerScriptDebugServer::reportException):
* bindings/js/WorkerScriptDebugServer.h:
* bindings/objc/WebScriptObject.mm:
(WebCore::createJSWrapper):
(WebCore::addExceptionToConsole):
(-[WebScriptObject callWebScriptMethod:withArguments:]):
(-[WebScriptObject evaluateWebScript:]):
- Changed to call a version of JSMainThreadExecState::evaluate() that provides
a stub returnedException because evaluateWebScript: doesn't need the exception.
* inspector/PageScriptDebugServer.cpp:
(WebCore::PageScriptDebugServer::isContentScript):
(WebCore::PageScriptDebugServer::reportException):
* inspector/PageScriptDebugServer.h:
* workers/WorkerGlobalScope.cpp:
(WebCore::WorkerGlobalScope::importScripts):
Source/WebKit/mac:
* WebView/WebView.mm:
(+[WebView _reportException:inContext:]):
(WebKitInitializeApplicationCachePathIfNecessary):
- Changed to use the new Exception object.
Source/WebKit/win:
* WebView.cpp:
(WebView::reportException):
- Changed to use the new Exception object.
Source/WebKit2:
* WebProcess/InjectedBundle/InjectedBundle.cpp:
(WebKit::InjectedBundle::reportException):
- Changed to use the new Exception object.
LayoutTests:
* TestExpectations:
- Skip the new tests until webkit.org/b/145090 is fixed.
* fast/dom/regress-131530-expected.txt:
- Rebased results because we now have a proper line number.
* http/tests/inspector/inspector-test.js:
(InspectorTestProxy.clearResults):
(InspectorTestProxy.reportUncaughtException):
- Add the feature to sanitize the url reported by reportUncaughtException() since
we can have tests that do expect uncaught exceptions, and we need the test
results to be invariant. Sanitization of the url, in this case means, stripping
off the preceding path.
* inspector/debugger/break-on-exception-expected.txt: Added.
* inspector/debugger/break-on-exception.html: Added.
* inspector/debugger/break-on-exception-catch-expected.txt: Added.
* inspector/debugger/break-on-exception-catch.html: Added.
* inspector/debugger/break-on-exception-finally-expected.txt: Added.
* inspector/debugger/break-on-exception-finally.html: Added.
* inspector/debugger/break-on-exception-native-expected.txt: Added.
* inspector/debugger/break-on-exception-native.html: Added.
* inspector/debugger/break-on-exception-throw-in-promise-expected.txt: Added.
* inspector/debugger/break-on-exception-throw-in-promise.html: Added.
* inspector/debugger/break-on-exception-throw-in-promise-with-catch-expected.txt: Added.
* inspector/debugger/break-on-exception-throw-in-promise-with-catch.html: Added.
* inspector/debugger/break-on-exception-throw-in-promise-then-expected.txt: Added.
* inspector/debugger/break-on-exception-throw-in-promise-then.html: Added.
* inspector/debugger/break-on-exception-throw-in-promise-then-with-catch-expected.txt: Added.
* inspector/debugger/break-on-exception-throw-in-promise-then-with-catch.html: Added.
* inspector/debugger/break-on-exception-throw-in-promise-rethrow-in-catch-expected.txt: Added.
* inspector/debugger/break-on-exception-throw-in-promise-rethrow-in-catch.html: Added.
* inspector/debugger/break-on-exception-window-onerror-expected.txt: Added.
* inspector/debugger/break-on-exception-window-onerror.html: Added.
* inspector/debugger/break-on-uncaught-exception-expected.txt: Added.
* inspector/debugger/break-on-uncaught-exception.html: Added.
* inspector/debugger/break-on-uncaught-exception-catch-expected.txt: Added.
* inspector/debugger/break-on-uncaught-exception-catch.html: Added.
* inspector/debugger/break-on-uncaught-exception-finally-expected.txt: Added.
* inspector/debugger/break-on-uncaught-exception-finally.html: Added.
* inspector/debugger/break-on-uncaught-exception-native-expected.txt: Added.
* inspector/debugger/break-on-uncaught-exception-native.html: Added.
* inspector/debugger/break-on-uncaught-exception-throw-in-promise-expected.txt: Added.
* inspector/debugger/break-on-uncaught-exception-throw-in-promise.html: Added.
* inspector/debugger/break-on-uncaught-exception-throw-in-promise-with-catch-expected.txt: Added.
* inspector/debugger/break-on-uncaught-exception-throw-in-promise-with-catch.html: Added.
* inspector/debugger/break-on-uncaught-exception-throw-in-promise-then-expected.txt: Added.
* inspector/debugger/break-on-uncaught-exception-throw-in-promise-then.html: Added.
* inspector/debugger/break-on-uncaught-exception-throw-in-promise-then-with-catch-expected.txt: Added.
* inspector/debugger/break-on-uncaught-exception-throw-in-promise-then-with-catch.html: Added.
* inspector/debugger/break-on-uncaught-exception-throw-in-promise-rethrow-in-catch-expected.txt: Added.
* inspector/debugger/break-on-uncaught-exception-throw-in-promise-rethrow-in-catch.html: Added.
* inspector/debugger/break-on-uncaught-exception-window-onerror-expected.txt: Added.
* inspector/debugger/break-on-uncaught-exception-window-onerror.html: Added.
* inspector/debugger/resources/break-on-exception-tests.js: Added.
(doThrow):
(testCatch):
(testFinally):
(testThrowingThruNativeCode):
(testThrowingInPromise):
(testThrowingInPromiseWithCatch):
(testThrowingInPromiseThen):
(testThrowingInPromiseThenWithCatch):
(testThrowingInPromiseWithRethrowInCatch):
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@185259 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations
index fc68001..99851d2 100644
--- a/LayoutTests/TestExpectations
+++ b/LayoutTests/TestExpectations
@@ -525,3 +525,25 @@
webkit.org/b/145007 js/regress-141098.html [ Skip ]
webkit.org/b/145390 storage/indexeddb/deleteIndex-bug110792.html [ Pass Failure ]
+
+webkit.org/b/145090 inspector/debugger/break-on-exception.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-exception-catch.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-exception-finally.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-exception-native.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-exception-throw-in-promise.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-exception-throw-in-promise-with-catch.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-exception-throw-in-promise-then.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-exception-throw-in-promise-then-with-catch.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-exception-throw-in-promise-rethrow-in-catch.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-exception-window-onerror.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-uncaught-exception.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-uncaught-exception-catch.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-uncaught-exception-finally.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-uncaught-exception-native.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-uncaught-exception-throw-in-promise.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-uncaught-exception-throw-in-promise-with-catch.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-uncaught-exception-throw-in-promise-then.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-uncaught-exception-throw-in-promise-then-with-catch.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-uncaught-exception-throw-in-promise-rethrow-in-catch.html [ Skip ]
+webkit.org/b/145090 inspector/debugger/break-on-uncaught-exception-window-onerror.html [ Skip ]
+