Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 30 additions & 15 deletions quickjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -31244,6 +31244,7 @@ static int exec_module_list_cmp(const void *p1, const void *p2, void *opaque)
static int js_execute_async_module(JSContext *ctx, JSModuleDef *m);
static int js_execute_sync_module(JSContext *ctx, JSModuleDef *m,
JSValue *pvalue);
static void js_promise_set_handled(JSContext *ctx, JSValueConst promise);
#ifdef DUMP_MODULE_EXEC
static void js_dump_module(JSContext *ctx, const char *str, JSModuleDef *m)
{
Expand Down Expand Up @@ -31404,6 +31405,9 @@ static int js_execute_sync_module(JSContext *ctx, JSModuleDef *m,
JS_FreeValue(ctx, promise);
} else if (state == JS_PROMISE_REJECTED) {
*pvalue = JS_PromiseResult(ctx, promise);
// rejection is propagated to module's evaluation promise; mark this internal
// promise as handled so it's not also surfaced as an unhandled rejection
js_promise_set_handled(ctx, promise);
JS_FreeValue(ctx, promise);
return -1;
} else {
Expand Down Expand Up @@ -53335,7 +53339,7 @@ typedef struct JSPromiseData {
JSPromiseStateEnum promise_state;
/* 0=fulfill, 1=reject, list of JSPromiseReactionData.link */
struct list_head promise_reactions[2];
BOOL is_handled; /* Note: only useful to debug */
BOOL is_handled;
JSValue promise_result;
} JSPromiseData;

Expand Down Expand Up @@ -53433,6 +53437,29 @@ void JS_SetHostPromiseRejectionTracker(JSRuntime *rt,
rt->host_promise_rejection_tracker_opaque = opaque;
}

// Notify the host tracker of an unreported rejection's state
static void call_promise_rejection_tracker(JSContext *ctx, JSValueConst promise,
BOOL is_handled)
{
JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE);
JSRuntime *rt = ctx->rt;
if (s && s->promise_state == JS_PROMISE_REJECTED && !s->is_handled &&
rt->host_promise_rejection_tracker) {
rt->host_promise_rejection_tracker(ctx, promise, s->promise_result,
is_handled, rt->host_promise_rejection_tracker_opaque);
}
}

// Mark rejected promise as handled by the engine itself and let the host know that the promise is handled
static void js_promise_set_handled(JSContext *ctx, JSValueConst promise)
{
JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE);
if (!s)
return;
call_promise_rejection_tracker(ctx, promise, TRUE);
s->is_handled = TRUE;
}

static void fulfill_or_reject_promise(JSContext *ctx, JSValueConst promise,
JSValueConst value, BOOL is_reject)
{
Expand All @@ -53448,13 +53475,7 @@ static void fulfill_or_reject_promise(JSContext *ctx, JSValueConst promise,
#ifdef DUMP_PROMISE
printf("fulfill_or_reject_promise: is_reject=%d\n", is_reject);
#endif
if (s->promise_state == JS_PROMISE_REJECTED && !s->is_handled) {
JSRuntime *rt = ctx->rt;
if (rt->host_promise_rejection_tracker) {
rt->host_promise_rejection_tracker(ctx, promise, value, FALSE,
rt->host_promise_rejection_tracker_opaque);
}
}
call_promise_rejection_tracker(ctx, promise, FALSE);

list_for_each_safe(el, el1, &s->promise_reactions[is_reject]) {
rd = list_entry(el, JSPromiseReactionData, link);
Expand Down Expand Up @@ -54221,13 +54242,7 @@ static __exception int perform_promise_then(JSContext *ctx,
list_add_tail(&rd_array[i]->link, &s->promise_reactions[i]);
} else {
JSValueConst args[5];
if (s->promise_state == JS_PROMISE_REJECTED && !s->is_handled) {
JSRuntime *rt = ctx->rt;
if (rt->host_promise_rejection_tracker) {
rt->host_promise_rejection_tracker(ctx, promise, s->promise_result,
TRUE, rt->host_promise_rejection_tracker_opaque);
}
}
call_promise_rejection_tracker(ctx, promise, TRUE);
i = s->promise_state - JS_PROMISE_FULFILLED;
rd = rd_array[i];
args[0] = rd->resolving_funcs[0];
Expand Down
1 change: 1 addition & 0 deletions tests/fixture_throwing_module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
throw new Error("Nuke the entire site from orbit. It's the only way to be sure.")
12 changes: 12 additions & 0 deletions tests/test_std.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,17 @@ function test_async_promise_rejection()
os.setTimeout(() => { assert(counter, 3) }, 10);
}

/* a dynamically imported module that throws at top level must not leak
an unhandled rejection when the import() rejection is handled.
https://github.com/quickjs-ng/quickjs/pull/1554 */
function test_module_rejection_handled()
{
var handled = false;
import("./fixture_throwing_module.js").then(() => assert(false),
() => { handled = true; });
os.setTimeout(() => { assert(handled, true) }, 10);
}

test_printf();
test_file1();
test_file2();
Expand All @@ -333,4 +344,5 @@ test_timer();
test_ext_json();
test_async_gc();
test_async_promise_rejection();
test_module_rejection_handled();