Closed Bug 704259 Opened 13 years ago Closed 12 years ago

try/catch blocks seem to be ignored in window.onerror when the first error originated from addEventListener, but not from onclick

Categories

(Core :: JavaScript Engine, defect)

defect
Not set
normal

Tracking

()

RESOLVED FIXED
mozilla14
Tracking Status
firefox11 - ---
firefox12 - ---
firefox13 - ---

People

(Reporter: jason, Assigned: dmandelin)

References

Details

(Keywords: reproducible, testcase)

Attachments

(4 files, 4 obsolete files)

Attached file sample.html (obsolete) —
User Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.121 Safari/535.2

Steps to reproduce:

Raising an exception in the window.onerror event handler seems to ignore try/catch blocks and stops execution. I would expect a try/catch block in the window.onerror hander to be treated the same as any other location where it is used. 

Please see the following StackOverflow question related to this as well. http://bit.ly/uU5aoe


Actual results:

When clicking on the "Test" button in the attached sample file the following is output on the console window:

begin onerror


Expected results:

I would have expected the following output when clicking on the "Test" button in the attached sample:

begin onerror
catch block
end onerror

My expectation is based upon the results I see in IE, Chrome, and Safari.
This is related to jQuery somehow. I bet replacing the jQuery dependency with the minimal code necessary to trigger this behavior will either explain this or make this easier to debug and fix on the Firefox side.
Keywords: testcase-wanted
OS: Windows 7 → All
Product: Firefox → Core
QA Contact: general → general
Hardware: x86_64 → All
Summary: try/catch blocks seem to be ignored in window.onerror → try/catch blocks seem to be ignored in window.onerror with jQuery
Version: 8 Branch → Trunk
Seems to be related to addEventListener, doesn't happen with .onclick for example. Here is a test case: http://jsfiddle.net/dmethvin/2gpWB/

Related jQuery ticket: http://bugs.jquery.com/ticket/10904
Attached file testcase
Thanks! Attached a testcase not using jQuery at all, just to be sure.
Attachment #575953 - Attachment is obsolete: true
Attachment #577102 - Attachment is obsolete: true
Status: UNCONFIRMED → NEW
Component: General → DOM
Ever confirmed: true
QA Contact: general → general
Summary: try/catch blocks seem to be ignored in window.onerror with jQuery → try/catch blocks seem to be ignored in window.onerror when the first error originated from addEventListener, but not from onclick
I think this is fundamentally a JS engine bug...

The working case has code flow as follows:

1) Click handler called
2) js_ErrorToException is entered
3) js_ErrorToException is exited
4) error reporter is called via AutoLastFrameCheck::~AutoLastFrameCheck calling
   js_ReportUncaughtException

The non-working case has code flow as follows:

1) Click handler called
2) js_ErrorToException is entered
3) js_ErrorToException is exited
4) error reporter is called via nsXPCWrappedJSClass::CallMethod calling
   nsXPCWrappedJSClass::CheckForException which calls JS_ReportPendingException which
   calls js_ReportUncaughtException.

The difference is that JS_ReportPendingException sets cx->generatingError to true before calling js_ReportUncaughtException, while ~AutoLastFrameCheck does not do that.

So in the second case we call the error handler, it tries to throw, js_ErrorToException is entered and says "oh, we're already in the middle of generating an error; just do the uncatchable thing".

The nice comment in JS_ReportPendingException is:

6142         * Set cx->generatingError to suppress the standard error-to-exception
6143         * conversion done by all {js,JS}_Report* functions except for OOM.  The
6144         * cx->generatingError flag was added to suppress recursive divergence
6145         * under js_ErrorToException, but it serves for our purposes here too.

I don't think it in fact serves for our purposes....
Assignee: nobody → general
Component: DOM → JavaScript Engine
QA Contact: general → general
This is not a regression, so not tracking for 12. But I'm taking it.

Boris, thanks for the analysis. The code in JS_ReportPendingException is pretty old, so it's hard to tell what the purpose was. I think I'm going to try removing that and see what happens. If that doesn't work, then we can probably just add a new API (or new argument to JS_RPE) for use by CheckForException.
Assignee: general → dmandelin
This doesn't fix anything, it just gets rid of a bunch of gotos.
Attachment #603548 - Flags: review?(luke)
Comment on attachment 603548 [details] [diff] [review]
Part 1: refactor generatingError uses

s/JS_FALSE/false/
Attachment #603548 - Flags: review?(luke) → review+
The comment in the original code says that setting generatingError serves the programmer's intent, but doesn't deign to say what that intent is, and it's pre-hg code, so I consider it lost to the ages.

This patch fixes the problem and seems to be doing well on try, so I figure it's worth a shot.
Attachment #603552 - Flags: review?(luke)
Attachment #603552 - Attachment is patch: true
> so I consider it lost to the ages.

Check cvsblame?  ;)
Comment on attachment 603552 [details] [diff] [review]
Part 2: candidate fix

Yeah, setting generatingError seems strange and unnecessary.  If try likes it, so do I.
Attachment #603552 - Flags: review?(luke) → review+
The first patch in this bug causes a reliable infinite loop crash for me when I start Chatzilla (I bisected it).  I'll attach the first chunk of the stack trace.
Please back out the patches.
https://hg.mozilla.org/mozilla-central/rev/8219e6519190
Status: RESOLVED → REOPENED
Resolution: FIXED → ---
Comment on attachment 603548 [details] [diff] [review]
Part 1: refactor generatingError uses

>-    /*
>-     * Prevent runaway recursion, via cx->generatingError.  If an out-of-memory
>-     * error occurs, no exception object will be created, but we don't assume
>-     * that OOM is the only kind of error that subroutines of this function
>-     * called below might raise.
>-     */
>+    /* Prevent infinite recursion. */
>     if (cx->generatingError)
>         return JS_FALSE;
>-
>-    MUST_FLOW_THROUGH("out");
>-    cx->generatingError = JS_TRUE;
>+    AutoScopedAssign<bool> asa(&cx->generatingError, false);

Shouldn't this be setting GeneratingError to true like the old code did?  I see nothing in this code now that ever sets it to true.
Depends on: 734167
(In reply to Bill Gianopoulos [:WG9s] from comment #17)
> Comment on attachment 603548 [details] [diff] [review]
> Part 1: refactor generatingError uses
> 
> >-    /*
> >-     * Prevent runaway recursion, via cx->generatingError.  If an out-of-memory
> >-     * error occurs, no exception object will be created, but we don't assume
> >-     * that OOM is the only kind of error that subroutines of this function
> >-     * called below might raise.
> >-     */
> >+    /* Prevent infinite recursion. */
> >     if (cx->generatingError)
> >         return JS_FALSE;
> >-
> >-    MUST_FLOW_THROUGH("out");
> >-    cx->generatingError = JS_TRUE;
> >+    AutoScopedAssign<bool> asa(&cx->generatingError, false);
> 
> Shouldn't this be setting GeneratingError to true like the old code did?  I
> see nothing in this code now that ever sets it to true.

I think you spotted it. The last line should be 

    AutoScopedAssign<bool> asa(&cx->generatingError, true);
Attached patch Part 1 (refactoring), v2 (obsolete) — Splinter Review
Attachment #603548 - Attachment is obsolete: true
The new patch seems to work, and doesn't hit bug 734167.
Attachment #604587 - Flags: review?(luke)
Comment on attachment 604587 [details] [diff] [review]
Part 1 (refactoring), v2

>-    MUST_FLOW_THROUGH("out");
>-    cx->generatingError = JS_TRUE;
>+        return false;
>+    AutoScopedAssign<bool> asa(&cx->generatingError, true);

It seems that the desired effect is for generatingError to be true for the remainder of the body of the function, and reset to false on return.  In that case, I think you want:

cx->generatingError = true;
AutoScopedAssign<bool> asa(&cx->generatingError, false);

>     ok = js_GetClassPrototype(cx, NULL, GetExceptionProtoKey(exn), &errProto);
>     if (!ok)
>-        goto out;

Here and a few places below you can merge the two lines (if (!js_GetClassPrototype...)) and perhaps remove 'ok' altogether.
Attachment #604587 - Flags: review?(luke)
(In reply to Luke Wagner [:luke] from comment #21)
> Comment on attachment 604587 [details] [diff] [review]
> Part 1 (refactoring), v2
> 
> >-    MUST_FLOW_THROUGH("out");
> >-    cx->generatingError = JS_TRUE;
> >+        return false;
> >+    AutoScopedAssign<bool> asa(&cx->generatingError, true);
> 
> It seems that the desired effect is for generatingError to be true for the
> remainder of the body of the function, and reset to false on return.  In
> that case, I think you want:
> 
> cx->generatingError = true;
> AutoScopedAssign<bool> asa(&cx->generatingError, false);

I'm pretty sure it's right the way it is: AutoScopedAssign stores argument 2 in the ctor, and stores 'old' in the dtor.
> 
> >     ok = js_GetClassPrototype(cx, NULL, GetExceptionProtoKey(exn), &errProto);
> >     if (!ok)
> >-        goto out;
> 
> Here and a few places below you can merge the two lines (if
> (!js_GetClassPrototype...)) and perhaps remove 'ok' altogether.

I did that. Revised patch coming momentarily.
Attachment #604587 - Attachment is obsolete: true
Attachment #605257 - Flags: review?(luke)
Comment on attachment 605257 [details] [diff] [review]
Part 1 (refactoring), v3

Oops, you're right (about the patch and about what I mistakenly thought AutoScopedAssign did).  AutoScopedAssign is a confusing name (at least to me, clearly :).  Perhaps ScopedAssignAndRestore would be better (Auto seems redundant with Scoped).  There are currently 0 uses of AutoScopedAssign; so if you agree with that name or a better one, feel free to fix.
Attachment #605257 - Flags: review?(luke) → review+
(In reply to Luke Wagner [:luke] from comment #24)
> AutoScopedAssign is a confusing name (at least to
> me, clearly :).  Perhaps ScopedAssignAndRestore would be better (Auto seems
> redundant with Scoped).  There are currently 0 uses of AutoScopedAssign; so
> if you agree with that name or a better one, feel free to fix.

I'm not sure about the naming issue. The name is kind of opaque to me as well, and I agree that Auto and Scoped are redundant. I'll see if I can think of a name I like today. I think the class deserves a brief usage comment too.
(Missed the uplift, changing target milestone to 14).
Target Milestone: mozilla13 → mozilla14
We've got AutoRestore in xpcom/glue/AutoRestore.h, maybe move that to MFBT?
https://hg.mozilla.org/mozilla-central/rev/c5a948233453
https://hg.mozilla.org/mozilla-central/rev/d32b8027cb46
Status: REOPENED → RESOLVED
Closed: 12 years ago12 years ago
Resolution: --- → FIXED
Dave - you tracked this bug for FF13. Should we consider an uplift to Aurora?
(In reply to Alex Keybl [:akeybl] from comment #30)
> Dave - you tracked this bug for FF13. Should we consider an uplift to Aurora?

I think I recommend against:

 - It's not a regression, we've had it at least since 3.
 - We're not aware of web sites in the field being broken by it.
 - I don't actually understand the code that I patched for the fix, so the risks are hard to analyze.

I tracked it because it does seem like a significant behavioral bug and I wanted to fix it promptly.
(In reply to David Mandelin from comment #31)
> I tracked it because it does seem like a significant behavioral bug and I
> wanted to fix it promptly.

Thanks Dave. We'll untrack for FF13 in that case.
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: