diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2022-07-23 00:40:42 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-23 00:40:42 +0200 |
commit | 504d2936ecf1a5520ca20f83792a94b219e84f53 (patch) | |
tree | f4d23c520355f340374529d67b31657821e73163 | |
parent | 72199303d899b8ddf2ff46ed11bd513ed9cc47e2 (diff) |
fix: unhandledrejection handling for sync throw in top level (#15279)
Fixes an edge in "unhandledrejection" event that prevent synchronous
errors being surfaced when throw from a top-level scope.
-rw-r--r-- | cli/tests/integration/run_tests.rs | 5 | ||||
-rw-r--r-- | cli/tests/testdata/unhandled_rejection_sync_error.ts | 6 | ||||
-rw-r--r-- | cli/tests/testdata/unhandled_rejection_sync_error.ts.out | 6 | ||||
-rw-r--r-- | core/bindings.rs | 2 | ||||
-rw-r--r-- | core/runtime.rs | 10 | ||||
-rw-r--r-- | runtime/js/99_main.js | 16 |
6 files changed, 42 insertions, 3 deletions
diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs index 25d84ae46..29e424aae 100644 --- a/cli/tests/integration/run_tests.rs +++ b/cli/tests/integration/run_tests.rs @@ -2787,3 +2787,8 @@ itest!(unhandled_rejection { args: "run --check unhandled_rejection.ts", output: "unhandled_rejection.ts.out", }); + +itest!(unhandled_rejection_sync_error { + args: "run --check unhandled_rejection_sync_error.ts", + output: "unhandled_rejection_sync_error.ts.out", +}); diff --git a/cli/tests/testdata/unhandled_rejection_sync_error.ts b/cli/tests/testdata/unhandled_rejection_sync_error.ts new file mode 100644 index 000000000..0dabb1cb7 --- /dev/null +++ b/cli/tests/testdata/unhandled_rejection_sync_error.ts @@ -0,0 +1,6 @@ +globalThis.addEventListener("unhandledrejection", (e) => { + console.log("unhandled rejection at:", e.promise, "reason:", e.reason); + e.preventDefault(); +}); + +throw new Error("boom!"); diff --git a/cli/tests/testdata/unhandled_rejection_sync_error.ts.out b/cli/tests/testdata/unhandled_rejection_sync_error.ts.out new file mode 100644 index 000000000..270319824 --- /dev/null +++ b/cli/tests/testdata/unhandled_rejection_sync_error.ts.out @@ -0,0 +1,6 @@ +[WILDCARD] +unhandled rejection at: Promise { + <rejected> Error: boom! + at file:///[WILDCARD]testdata/unhandled_rejection_sync_error.ts:6:7 +} reason: Error: boom! + at file:///[WILDCARD]testdata/unhandled_rejection_sync_error.ts:6:7 diff --git a/core/bindings.rs b/core/bindings.rs index 6fa9f745b..a93a3e746 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -363,6 +363,8 @@ pub extern "C" fn promise_reject_callback(message: v8::PromiseRejectMessage) { } } + // TODO(bartlomieju): remove this whole block, `js_uncaught_exception_cb` is + // not needed anymore if let Some(exception) = tc_scope.exception() { if let Some(js_uncaught_exception_cb) = js_uncaught_exception_cb { tc_scope.reset(); // Cancel pending exception. diff --git a/core/runtime.rs b/core/runtime.rs index 64e7f635c..4568f2018 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -1335,7 +1335,15 @@ impl JsRuntime { .expect("Expected to get promise as module evaluation result"); let promise_global = v8::Global::new(tc_scope, promise); let mut state = state_rc.borrow_mut(); - state.pending_promise_exceptions.remove(&promise_global); + { + let pending_mod_evaluate = state.pending_mod_evaluate.as_ref().unwrap(); + let pending_rejection_was_already_handled = pending_mod_evaluate + .handled_promise_rejections + .contains(&promise_global); + if !pending_rejection_was_already_handled { + state.pending_promise_exceptions.remove(&promise_global); + } + } let promise_global = v8::Global::new(tc_scope, promise); state.pending_mod_evaluate.as_mut().unwrap().promise = Some(promise_global); diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index 3421528d2..115de4d4d 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -602,7 +602,7 @@ delete Intl.v8BreakIterator; WeakMapPrototypeDelete(pendingRejectionsReasons, promise); if (!hasPendingException) { - return; + continue; } const event = new PromiseRejectionEvent("unhandledrejection", { @@ -610,9 +610,21 @@ delete Intl.v8BreakIterator; promise, reason, }); + + const errorEventCb = (event) => { + if (event.error === reason) { + core.opSync("op_remove_pending_promise_exception", promise); + } + }; + // Add a callback for "error" event - it will be dispatched + // if error is thrown during dispatch of "unhandledrejection" + // event. + globalThis.addEventListener("error", errorEventCb); globalThis.dispatchEvent(event); + globalThis.removeEventListener("error", errorEventCb); - // If event was not prevented we will let Rust side handle it. + // If event was not prevented (or "unhandledrejection" listeners didn't + // throw) we will let Rust side handle it. if (event.defaultPrevented) { core.opSync("op_remove_pending_promise_exception", promise); } |