Bug 121108 - Web Inspector gets paused twice when there is an exception in host function
Summary: Web Inspector gets paused twice when there is an exception in host function
Status: RESOLVED DUPLICATE of bug 145525
Alias: None
Product: WebKit
Classification: Unclassified
Component: JavaScriptCore (show other bugs)
Version: 528+ (Nightly build)
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Mark Lam
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2013-09-10 11:49 PDT by Joseph Pecoraro
Modified: 2015-06-03 13:14 PDT (History)
4 users (show)

See Also:


Attachments
[TEST] Manual Test Case (473 bytes, text/html)
2013-09-10 11:49 PDT, Joseph Pecoraro
no flags Details
Patch (6.25 KB, patch)
2015-03-12 02:38 PDT, Peter Wang
no flags Details | Formatted Diff | Diff
A new test case. (498 bytes, text/html)
2015-03-18 03:28 PDT, Peter Wang
no flags Details
Patch (8.96 KB, patch)
2015-03-20 02:16 PDT, Peter Wang
mark.lam: review-
buildbot: commit-queue-
Details | Formatted Diff | Diff
Archive of layout-test-results from ews206 for win-future (14.71 MB, application/zip)
2015-03-20 12:24 PDT, Build Bot
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Joseph Pecoraro 2013-09-10 11:49:22 PDT
Created attachment 211217 [details]
[TEST] Manual Test Case

See attached test case. The Inspector gets paused twice, as a result of two JSC::Debugger::exception calls. Once as expected at the throw statement, then once again, unexpectedly at the Array.prototype.map call site.

* STEPS TO REPRODUCE
1. Inspect attached [TEST]
2. Open Debugger Navigation Sidebar
3. Enable Breakpoint on "All Exceptions"
4. Click <button> on the page => inspector will pause
5. Click Continue in Web Inspector's Debugger sidebar
  => unexpectedly pause again

* NOTES
(lldb) bt
* thread #1: tid = 0x96660e, 0x000000010f3bb3f7 WebCore`WebCore::ScriptDebugServer::exception(this=0x00007ffdabc43e80, debuggerCallFrame=0x00007fff547edb38, sourceID=1, lineNumber=27, columnNumber=0, hasHandler=true) + 39 at ScriptDebugServer.cpp:529, queue = 'com.apple.main-thread, stop reason = breakpoint 1.1
    frame #0: 0x000000010f3bb3f7 WebCore`WebCore::ScriptDebugServer::exception(this=0x00007ffdabc43e80, debuggerCallFrame=0x00007fff547edb38, sourceID=1, lineNumber=27, columnNumber=0, hasHandler=true) + 39 at ScriptDebugServer.cpp:529
    frame #1: 0x000000010cdec89d JavaScriptCore`JSC::Interpreter::unwind(this=0x00007ffdadb09ab0, callFrame=0x00007fff547edc10, exceptionValue=0x00007fff547edc08, bytecodeOffset=27) + 909 at Interpreter.cpp:668
    frame #2: 0x000000010ce0d4ae JavaScriptCore`JSC::genericUnwind(vm=0x00007ffdae005e00, callFrame=0x0000000115ea5190, exceptionValue=JSValue at 0x00007fff547edc08, vPCIndex=27) + 110 at JITExceptions.cpp:73
    frame #3: 0x000000010cf1ad18 JavaScriptCore`doThrow(exec=0x0000000115ea5190, pc=0x00007ffdb6eb0010) + 136 at LLIntExceptions.cpp:51
    frame #4: 0x000000010cf1ac7d JavaScriptCore`JSC::LLInt::returnToThrow(exec=0x0000000115ea5190, pc=0x00007ffdb6eb0010) + 29 at LLIntExceptions.cpp:60
    frame #5: 0x000000010cf21ddd JavaScriptCore`llint_slow_path_throw(exec=0x0000000115ea5190, pc=0x00007ffdb6eb0010) + 173 at LLIntSlowPaths.cpp:1222
    frame #6: 0x000000010cf29abb JavaScriptCore`llint_op_throw + 15

(lldb) bt
* thread #1: tid = 0x96660e, 0x000000010f3bb3f7 WebCore`WebCore::ScriptDebugServer::exception(this=0x00007ffdabc43e80, debuggerCallFrame=0x00007fff547ee018, sourceID=1, lineNumber=26, columnNumber=0, hasHandler=true) + 39 at ScriptDebugServer.cpp:529, queue = 'com.apple.main-thread, stop reason = breakpoint 1.1
    frame #0: 0x000000010f3bb3f7 WebCore`WebCore::ScriptDebugServer::exception(this=0x00007ffdabc43e80, debuggerCallFrame=0x00007fff547ee018, sourceID=1, lineNumber=26, columnNumber=0, hasHandler=true) + 39 at ScriptDebugServer.cpp:529
    frame #1: 0x000000010cdec89d JavaScriptCore`JSC::Interpreter::unwind(this=0x00007ffdadb09ab0, callFrame=0x00007fff547ee0f0, exceptionValue=0x00007fff547ee0e8, bytecodeOffset=46) + 909 at Interpreter.cpp:668
    frame #2: 0x000000010ce0d4ae JavaScriptCore`JSC::genericUnwind(vm=0x00007ffdae005e00, callFrame=0x0000000115ea50b8, exceptionValue=JSValue at 0x00007fff547ee0e8, vPCIndex=46) + 110 at JITExceptions.cpp:73
    frame #3: 0x000000010ce0d5ec JavaScriptCore`JSC::jitThrowNew(vm=0x00007ffdae005e00, callFrame=0x0000000115ea50b8, exceptionValue=JSValue at 0x00007fff547ee138) + 60 at JITExceptions.cpp:96
    frame #4: 0x000000010ce2d8e2 JavaScriptCore`cti_vm_handle_exception(callFrame=0x0000000115ea50b8) + 178 at JITStubs.cpp:2189
    frame #5: 0x000000010ce23d29 JavaScriptCore`ctiVMHandleException + 8
Comment 1 Joseph Pecoraro 2013-09-10 14:01:50 PDT
This affects some LayoutTests landed in: <http://trac.webkit.org/changeset/155471>

When committing a fix for this, you should remove the workarounds added to these tests, and update their expected results:
LayoutTests/inspector-protocol/debugger/setPauseOnExceptions-all.html
LayoutTests/inspector-protocol/debugger/setPauseOnExceptions-uncaught.html

That also means you don't necessarily have to add a specific test for this fix, since it would be covered by these existing tests!
Comment 2 Peter Wang 2015-03-12 02:38:55 PDT
Created attachment 248504 [details]
Patch
Comment 3 Peter Wang 2015-03-12 02:46:58 PDT
By my investigation, if the exception is not truly cleared in VM, it means that exception is thrown to the caller. So, as long as the 'Debugger::m_currentException" is saving the right status of the exception in VM, we can use it to check if it's the same exception sent to debugger.
Comment 4 Peter Wang 2015-03-14 07:22:29 PDT
Excuse me, is here anybody can help to review?
Comment 5 Joseph Pecoraro 2015-03-15 00:49:19 PDT
Comment on attachment 248504 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=248504&action=review

This seems fine to me. Mark Lam would be a better reviewer.

> Source/JavaScriptCore/debugger/Debugger.cpp:158
> +    , m_currentException(JSValue())

I don't think this is necessary, since JSValue is a class, the default constructor would get called, and be exactly this.

> Source/JavaScriptCore/debugger/Debugger.cpp:633
> +    if (m_currentException != JSValue() && m_vm->exception() == JSValue()
> +        && !m_hasHandlerForExceptionCallback)

Likewise these comparison to JSValue(). Can they just be "!foo" since JSCJSValue has operator UnspecifiedBoolType?

  if (m_currentException && !m_vm->exception() && !m_hasHandlerForExceptionCallback)
      m_currentException = JSValue();

> Source/JavaScriptCore/debugger/Debugger.cpp:698
> +    if (m_isPaused && m_currentException != JSValue())

Likewise, could this just be "&& m_currentException".
Comment 6 Mark Lam 2015-03-16 13:52:54 PDT
Comment on attachment 248504 [details]
Patch

I agree with Joe's comments in terms of code styling.  That said, I'm not convinced that this fix is correct.  To get a feel for why this fix is needed, I tried running the tests (with the test changes applied) without the Debugger.cpp changes, and I don't see any failures.  Hence, it appears  that the original issue that necessitated those FIXMEs in the tests no longer exists.

Peter, are you seeing an issue that still manifests without your fix?  If so, can you please provide a test that demonstrates that issue?
Comment 7 Peter Wang 2015-03-17 07:28:50 PDT
Thank you for your comments, Joseph and Mark. Let me check it again and update the patch.
Comment 8 Peter Wang 2015-03-18 03:28:02 PDT
Created attachment 248922 [details]
A new test case.

The patch r164139 (SHA 4cbc701b) has re-implemented "Array.prototype.map" using JS in file "JavaScriptCore/builtins/Array.prototype.js", so, it's not "host function" now.
So we have to change the case to use "Map.prototype.forEach" instead.
Comment 9 Peter Wang 2015-03-20 02:16:16 PDT
Created attachment 249100 [details]
Patch
Comment 10 Peter Wang 2015-03-20 04:05:45 PDT
Hi Joseph and Mark, the new patch has been uploaded. 

There 3 modifications included:
(1) Change the Layout test case, since the new implementation (SHA 4cbc701b) of "Array.prototype.map" can workaround this bug.
(2) Correct the coding problem of the old patch.
(3) Take a missed case into consideration: when VM is unwinding CallFrames it will clear the exception temporarily.

The root cause of this bug is that the exception, thrown form the JS callback of host function, will be handled twice. So if the 'Debugger::m_currentException' can save the real state of exception of VM, we can use it to justify if it's a same exception triggering the debugger in second time.

Please help to review, thank you very much.
Comment 11 Build Bot 2015-03-20 12:24:05 PDT
Comment on attachment 249100 [details]
Patch

Attachment 249100 [details] did not pass win-ews (win):
Output: http://webkit-queues.appspot.com/results/4629479943045120

Number of test failures exceeded the failure limit.
Comment 12 Build Bot 2015-03-20 12:24:08 PDT
Created attachment 249126 [details]
Archive of layout-test-results from ews206 for win-future

The attached test failures were seen while running run-webkit-tests on the win-ews.
Bot: ews206  Port: win-future  Platform: CYGWIN_NT-6.1-WOW64-1.7.20-0.266-5-3-i686-32bit
Comment 13 Joseph Pecoraro 2015-03-20 16:37:16 PDT
Comment on attachment 249100 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=249100&action=review

Definitely needs Mark to review, since you're changing the Interpreter.

> LayoutTests/ChangeLog:8
> +        Remove the workarounds in accroding test cases.

Typo: "accroding" => "associated"?
Comment 14 Mark Lam 2015-03-23 23:35:11 PDT
(In reply to comment #10)
> Hi Joseph and Mark, the new patch has been uploaded. 
> 
> There 3 modifications included:
> (1) Change the Layout test case, since the new implementation (SHA 4cbc701b)
> of "Array.prototype.map" can workaround this bug.
> (2) Correct the coding problem of the old patch.
> (3) Take a missed case into consideration: when VM is unwinding CallFrames
> it will clear the exception temporarily.
> 
> The root cause of this bug is that the exception, thrown form the JS
> callback of host function, will be handled twice. So if the
> 'Debugger::m_currentException' can save the real state of exception of VM,
> we can use it to justify if it's a same exception triggering the debugger in
> second time.

This information about the root cause is helpful in understanding what is being fixed.  It should be included in the ChangeLog.

> Please help to review, thank you very much.

That said, the change in Debugger::exception() that removes the clearing of m_currentException still doesn’t feel right to me.  Let me think about the patch a bit more, and I’ll get back to you with some feedback soon.
Comment 15 Mark Lam 2015-03-24 13:32:28 PDT
Comment on attachment 249100 [details]
Patch

Thank you for your effort on this patch, but after analyzing the issue, I've concluded that this patch is not appropriate for the following reasons:

1. The patch is complicated and makes the code difficult to understand and reason about.  Specifically, I'm referring to all the interaction between Debugger::m_currentException, m_vm->exception(), m_unwindingCallFrame, and m_hasHandlerForExceptionCallback.

2. The patch results in a bug.  I wrote a simple test file:

<script>
function foo(x) {
    throw "Stuff";
}
function goo() {
    foo();
}
goo();
</script>

With this patch, the WebInspector with "Break on Uncaught Exceptions" enabled, will only break the first time you reload the webpage.  On all subsequent reloads of the webpage, the Inspector will no longer break on the uncaught exception.  This is a regression from how ToT works.

3. The ChangeLog is also not clear about what the issue is.  I finally understood only after I wrote some more test code and tested ToT against other browsers.

The real issue here is that the existing implementation will break once (when "Break on Uncaught Exceptions" is enabled) for every entry / re-entry into the VM.  Consider the following stack of calls (where this stack grows down):

      frame 0: JS function j0()
      frame 1: JS function j1()
      frame 2: native C++ function n2()
      frame 3: JS function j3()
      frame 4: JS function j4()         <==== Uncaught exception thrown here 

... and there are no catch clauses in any of those functions.  With the existing implementation, the Inspector will break in frame 4 and frame 1.  The desired behavior is to only break in frame 4.

Now knowing this, this is how I think the fix should be implemented:

1. Change Debugger::exception() to Debugger::beginExceptionHandling().  It should set m_hasHandlerForExceptionCallback and m_currentException but not clear them.

2. Implement a Debugger::endExceptionHandling() which clears m_hasHandlerForExceptionCallback and m_currentException.  endExceptionHandling() should be called in 2 places:
    a. The place where the catch clause is handled.
        This is the case where the exception is caught and handled, and the Inspector should break on any exceptions thrown after this.
    b. When the Interpreter exits the outer most VMEntryScope.
        This is the case where the exception is unhandled, and we've unwound out of all JS code.  The Inspector should also break on any exceptions thrown in new JS code executed after this.

3. Implement some way to prevent the Interpreter from calling beginExceptionHandling() again if the debugger is already in the process of handling an exception.  One way is to add a isHandlingException() query to Debugger like so:

    bool isHandlingException() const { return !!m_currentException; }

... and add that in Interpreter::unwind() as one of the conditions for allowing debugger->beginExceptionHandling() to be called.

Because we're changing how long m_currentException is set, there might be assertions in Debugger that needs to be updated.  I would do some more testing with a debug build.  At minimum:
1. run all inspector layout tests with a debug build.
2. manually use the debug build Inspector to debug some JS code (which throw exceptions, uncaught and caught) to make sure everything is working as expected and nothing broke.

Part 2 above may be a little more challenging for someone who is not familiar with the VM, but some grepping and debugging should get you there.  If not, I can take a look into implementing the fix myself some time this weekend.
Comment 16 Mark Lam 2015-06-01 07:52:12 PDT
This issue turns out to be only 1 symptom of deeper issue.  I’m working on a fix for the general issue.  Fix coming soon.
Comment 17 Mark Lam 2015-06-03 13:14:06 PDT
The exceptions re-work in https://bugs.webkit.org/show_bug.cgi?id=145525 includes the fix for this issue (among a few other things).  Hence, I'll close this one as a dup.

*** This bug has been marked as a duplicate of bug 145525 ***