Skip to content

Commit d2fa383

Browse files
committed
Fix uncatchable error inside a promise (#810)
1 parent c8153fe commit d2fa383

File tree

3 files changed

+120
-11
lines changed

3 files changed

+120
-11
lines changed

CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,15 @@ if(NOT EMSCRIPTEN)
298298
target_link_libraries(run-test262 qjs)
299299
endif()
300300

301+
# Interrupt test
302+
#
303+
304+
add_executable(interrupt-test
305+
interrupt-test.c
306+
)
307+
target_compile_definitions(interrupt-test PRIVATE ${qjs_defines})
308+
target_link_libraries(interrupt-test qjs)
309+
301310
# Unicode generator
302311
#
303312

interrupt-test.c

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#include <assert.h>
2+
#include "quickjs.h"
3+
4+
#define MAX_VAL 10
5+
6+
static int timeout_interrupt_handler(JSRuntime *rt, void *opaque) {
7+
int *time = (int *)opaque;
8+
if (*time <= MAX_VAL) {
9+
*time += 1;
10+
}
11+
return *time > MAX_VAL;
12+
}
13+
14+
static void sync_call()
15+
{
16+
const char *code =
17+
"(function() { \
18+
try { \
19+
while (true) {} \
20+
} catch (e) {} \
21+
})();";
22+
23+
JSRuntime *rt = JS_NewRuntime();
24+
JSContext *ctx = JS_NewContext(rt);
25+
int time = 0;
26+
JS_SetInterruptHandler(rt, timeout_interrupt_handler, &time);
27+
JSValue ret = JS_Eval(ctx, code, strlen(code), "<input>", JS_EVAL_TYPE_GLOBAL);
28+
assert(time > MAX_VAL);
29+
assert(JS_IsException(ret));
30+
JS_FreeValue(ctx, ret);
31+
assert(JS_HasException(ctx));
32+
JSValue e = JS_GetException(ctx);
33+
assert(JS_IsUncatchableError(ctx, e));
34+
JS_FreeValue(ctx, e);
35+
JS_FreeContext(ctx);
36+
JS_FreeRuntime(rt);
37+
}
38+
39+
static void async_call()
40+
{
41+
const char *code =
42+
"(async function() { \
43+
const loop = async () => { \
44+
await Promise.resolve(); \
45+
while (true) {} \
46+
}; \
47+
while (true) { \
48+
await loop().catch(() => {}); \
49+
} \
50+
})();";
51+
52+
JSRuntime *rt = JS_NewRuntime();
53+
JSContext *ctx = JS_NewContext(rt);
54+
int time = 0;
55+
JS_SetInterruptHandler(rt, timeout_interrupt_handler, &time);
56+
JSValue ret = JS_Eval(ctx, code, strlen(code), "<input>", JS_EVAL_TYPE_GLOBAL);
57+
assert(!JS_IsException(ret));
58+
JS_FreeValue(ctx, ret);
59+
assert(JS_IsJobPending(rt));
60+
int r = 0;
61+
while (JS_IsJobPending(rt)) {
62+
r = JS_ExecutePendingJob(rt, &ctx);
63+
}
64+
assert(time > MAX_VAL);
65+
assert(r == -1);
66+
assert(JS_HasException(ctx));
67+
JSValue e = JS_GetException(ctx);
68+
assert(JS_IsUncatchableError(ctx, e));
69+
JS_FreeValue(ctx, e);
70+
JS_FreeContext(ctx);
71+
JS_FreeRuntime(rt);
72+
}
73+
74+
int main()
75+
{
76+
sync_call();
77+
async_call();
78+
printf("interrupt-test pass\n");
79+
return 0;
80+
}

quickjs.c

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18002,20 +18002,32 @@ static int js_async_function_resolve_create(JSContext *ctx,
1800218002
return 0;
1800318003
}
1800418004

18005-
static void js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s)
18005+
static BOOL js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s)
1800618006
{
18007+
BOOL is_success = TRUE;
1800718008
JSValue func_ret, ret2;
1800818009

1800918010
func_ret = async_func_resume(ctx, &s->func_state);
1801018011
if (JS_IsException(func_ret)) {
18011-
JSValue error;
1801218012
fail:
18013-
error = JS_GetException(ctx);
18014-
ret2 = JS_Call(ctx, s->resolving_funcs[1], JS_UNDEFINED,
18013+
if (unlikely(JS_IsUncatchableError(ctx, ctx->rt->current_exception))) {
18014+
is_success = FALSE;
18015+
} else {
18016+
JSValue error = JS_GetException(ctx);
18017+
ret2 = JS_Call(ctx, s->resolving_funcs[1], JS_UNDEFINED,
1801518018
1, &error);
18016-
JS_FreeValue(ctx, error);
18019+
JS_FreeValue(ctx, error);
18020+
resolved:
18021+
if (unlikely(JS_IsException(ret2))) {
18022+
if (JS_IsUncatchableError(ctx, ctx->rt->current_exception)) {
18023+
is_success = FALSE;
18024+
} else {
18025+
abort(); /* BUG */
18026+
}
18027+
}
18028+
JS_FreeValue(ctx, ret2);
18029+
}
1801718030
js_async_function_terminate(ctx->rt, s);
18018-
JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */
1801918031
} else {
1802018032
JSValue value;
1802118033
value = s->func_state.frame.cur_sp[-1];
@@ -18024,9 +18036,8 @@ static void js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s)
1802418036
/* function returned */
1802518037
ret2 = JS_Call(ctx, s->resolving_funcs[0], JS_UNDEFINED,
1802618038
1, &value);
18027-
JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */
1802818039
JS_FreeValue(ctx, value);
18029-
js_async_function_terminate(ctx->rt, s);
18040+
goto resolved;
1803018041
} else {
1803118042
JSValue promise, resolving_funcs[2], resolving_funcs1[2];
1803218043
int i, res;
@@ -18057,6 +18068,7 @@ static void js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s)
1805718068
goto fail;
1805818069
}
1805918070
}
18071+
return is_success;
1806018072
}
1806118073

1806218074
static JSValue js_async_function_resolve_call(JSContext *ctx,
@@ -18081,7 +18093,9 @@ static JSValue js_async_function_resolve_call(JSContext *ctx,
1808118093
/* return value of await */
1808218094
s->func_state.frame.cur_sp[-1] = js_dup(arg);
1808318095
}
18084-
js_async_function_resume(ctx, s);
18096+
if (!js_async_function_resume(ctx, s)) {
18097+
return JS_EXCEPTION;
18098+
}
1808518099
return JS_UNDEFINED;
1808618100
}
1808718101

@@ -18113,7 +18127,9 @@ static JSValue js_async_function_call(JSContext *ctx, JSValue func_obj,
1811318127
}
1811418128
s->is_active = TRUE;
1811518129

18116-
js_async_function_resume(ctx, s);
18130+
if (!js_async_function_resume(ctx, s)) {
18131+
goto fail;
18132+
}
1811718133

1811818134
js_async_function_free(ctx->rt, s);
1811918135

@@ -48609,8 +48625,12 @@ static JSValue promise_reaction_job(JSContext *ctx, int argc,
4860948625
res = JS_Call(ctx, handler, JS_UNDEFINED, 1, &arg);
4861048626
}
4861148627
is_reject = JS_IsException(res);
48612-
if (is_reject)
48628+
if (is_reject) {
48629+
if (unlikely(JS_IsUncatchableError(ctx, ctx->rt->current_exception))) {
48630+
return JS_EXCEPTION;
48631+
}
4861348632
res = JS_GetException(ctx);
48633+
}
4861448634
func = argv[is_reject];
4861548635
/* as an extension, we support undefined as value to avoid
4861648636
creating a dummy promise in the 'await' implementation of async

0 commit comments

Comments
 (0)