Skip to content

Commit

Permalink
Fix uncatchable error inside a promise (#810)
Browse files Browse the repository at this point in the history
  • Loading branch information
laishere committed Jan 10, 2025
1 parent c8153fe commit d2fa383
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 11 deletions.
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,15 @@ if(NOT EMSCRIPTEN)
target_link_libraries(run-test262 qjs)
endif()

# Interrupt test
#

add_executable(interrupt-test
interrupt-test.c
)
target_compile_definitions(interrupt-test PRIVATE ${qjs_defines})
target_link_libraries(interrupt-test qjs)

# Unicode generator
#

Expand Down
80 changes: 80 additions & 0 deletions interrupt-test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#include <assert.h>
#include "quickjs.h"

#define MAX_VAL 10

static int timeout_interrupt_handler(JSRuntime *rt, void *opaque) {
int *time = (int *)opaque;
if (*time <= MAX_VAL) {
*time += 1;
}
return *time > MAX_VAL;
}

static void sync_call()
{
const char *code =
"(function() { \
try { \
while (true) {} \
} catch (e) {} \
})();";

JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
int time = 0;
JS_SetInterruptHandler(rt, timeout_interrupt_handler, &time);
JSValue ret = JS_Eval(ctx, code, strlen(code), "<input>", JS_EVAL_TYPE_GLOBAL);
assert(time > MAX_VAL);
assert(JS_IsException(ret));
JS_FreeValue(ctx, ret);
assert(JS_HasException(ctx));
JSValue e = JS_GetException(ctx);
assert(JS_IsUncatchableError(ctx, e));
JS_FreeValue(ctx, e);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}

static void async_call()
{
const char *code =
"(async function() { \
const loop = async () => { \
await Promise.resolve(); \
while (true) {} \
}; \
while (true) { \
await loop().catch(() => {}); \
} \
})();";

JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
int time = 0;
JS_SetInterruptHandler(rt, timeout_interrupt_handler, &time);
JSValue ret = JS_Eval(ctx, code, strlen(code), "<input>", JS_EVAL_TYPE_GLOBAL);
assert(!JS_IsException(ret));
JS_FreeValue(ctx, ret);
assert(JS_IsJobPending(rt));
int r = 0;
while (JS_IsJobPending(rt)) {
r = JS_ExecutePendingJob(rt, &ctx);
}
assert(time > MAX_VAL);
assert(r == -1);
assert(JS_HasException(ctx));
JSValue e = JS_GetException(ctx);
assert(JS_IsUncatchableError(ctx, e));
JS_FreeValue(ctx, e);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}

int main()
{
sync_call();
async_call();
printf("interrupt-test pass\n");
return 0;
}
42 changes: 31 additions & 11 deletions quickjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -18002,20 +18002,32 @@ static int js_async_function_resolve_create(JSContext *ctx,
return 0;
}

static void js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s)
static BOOL js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s)
{
BOOL is_success = TRUE;
JSValue func_ret, ret2;

func_ret = async_func_resume(ctx, &s->func_state);
if (JS_IsException(func_ret)) {
JSValue error;
fail:
error = JS_GetException(ctx);
ret2 = JS_Call(ctx, s->resolving_funcs[1], JS_UNDEFINED,
if (unlikely(JS_IsUncatchableError(ctx, ctx->rt->current_exception))) {
is_success = FALSE;
} else {
JSValue error = JS_GetException(ctx);
ret2 = JS_Call(ctx, s->resolving_funcs[1], JS_UNDEFINED,
1, &error);
JS_FreeValue(ctx, error);
JS_FreeValue(ctx, error);
resolved:
if (unlikely(JS_IsException(ret2))) {
if (JS_IsUncatchableError(ctx, ctx->rt->current_exception)) {
is_success = FALSE;
} else {
abort(); /* BUG */
}
}
JS_FreeValue(ctx, ret2);
}
js_async_function_terminate(ctx->rt, s);
JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */
} else {
JSValue value;
value = s->func_state.frame.cur_sp[-1];
Expand All @@ -18024,9 +18036,8 @@ static void js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s)
/* function returned */
ret2 = JS_Call(ctx, s->resolving_funcs[0], JS_UNDEFINED,
1, &value);
JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */
JS_FreeValue(ctx, value);
js_async_function_terminate(ctx->rt, s);
goto resolved;
} else {
JSValue promise, resolving_funcs[2], resolving_funcs1[2];
int i, res;
Expand Down Expand Up @@ -18057,6 +18068,7 @@ static void js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s)
goto fail;
}
}
return is_success;
}

static JSValue js_async_function_resolve_call(JSContext *ctx,
Expand All @@ -18081,7 +18093,9 @@ static JSValue js_async_function_resolve_call(JSContext *ctx,
/* return value of await */
s->func_state.frame.cur_sp[-1] = js_dup(arg);
}
js_async_function_resume(ctx, s);
if (!js_async_function_resume(ctx, s)) {
return JS_EXCEPTION;
}
return JS_UNDEFINED;
}

Expand Down Expand Up @@ -18113,7 +18127,9 @@ static JSValue js_async_function_call(JSContext *ctx, JSValue func_obj,
}
s->is_active = TRUE;

js_async_function_resume(ctx, s);
if (!js_async_function_resume(ctx, s)) {
goto fail;
}

js_async_function_free(ctx->rt, s);

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

0 comments on commit d2fa383

Please sign in to comment.