summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2022-07-23 00:40:42 +0200
committerGitHub <noreply@github.com>2022-07-23 00:40:42 +0200
commit504d2936ecf1a5520ca20f83792a94b219e84f53 (patch)
treef4d23c520355f340374529d67b31657821e73163
parent72199303d899b8ddf2ff46ed11bd513ed9cc47e2 (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.rs5
-rw-r--r--cli/tests/testdata/unhandled_rejection_sync_error.ts6
-rw-r--r--cli/tests/testdata/unhandled_rejection_sync_error.ts.out6
-rw-r--r--core/bindings.rs2
-rw-r--r--core/runtime.rs10
-rw-r--r--runtime/js/99_main.js16
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);
}