From 56c47f7d2a2a10575be758db70f28b2ad9567978 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Fri, 28 Mar 2025 10:11:15 +0100 Subject: [PATCH] fixed exception handling in AsyncFromSyncIterator and async for of --- quickjs-opcode.h | 3 +- quickjs.c | 140 ++++++++++++++++++++++++++++++--------------- test262_errors.txt | 24 -------- 3 files changed, 97 insertions(+), 70 deletions(-) diff --git a/quickjs-opcode.h b/quickjs-opcode.h index 02ef4a7..17448d7 100644 --- a/quickjs-opcode.h +++ b/quickjs-opcode.h @@ -207,8 +207,9 @@ DEF( for_of_start, 1, 1, 3, none) DEF(for_await_of_start, 1, 1, 3, none) DEF( for_in_next, 1, 1, 3, none) DEF( for_of_next, 2, 3, 5, u8) +DEF(for_await_of_next, 1, 3, 4, none) /* iter next catch_offset -> iter next catch_offset obj */ DEF(iterator_check_object, 1, 1, 1, none) -DEF(iterator_get_value_done, 1, 1, 2, none) +DEF(iterator_get_value_done, 1, 2, 3, none) /* catch_offset obj -> catch_offset value done */ DEF( iterator_close, 1, 3, 0, none) DEF( iterator_next, 1, 4, 4, none) DEF( iterator_call, 2, 4, 5, u8) diff --git a/quickjs.c b/quickjs.c index b656e3b..a07c084 100644 --- a/quickjs.c +++ b/quickjs.c @@ -15227,6 +15227,21 @@ static __exception int js_for_of_next(JSContext *ctx, JSValue *sp, int offset) return 0; } +static __exception int js_for_await_of_next(JSContext *ctx, JSValue *sp) +{ + JSValue obj, iter, next; + + sp[-1] = JS_UNDEFINED; /* disable the catch offset so that + exceptions do not close the iterator */ + iter = sp[-3]; + next = sp[-2]; + obj = JS_Call(ctx, next, iter, 0, NULL); + if (JS_IsException(obj)) + return -1; + sp[0] = obj; + return 0; +} + static JSValue JS_IteratorGetCompleteValue(JSContext *ctx, JSValueConst obj, BOOL *pdone) { @@ -15259,6 +15274,9 @@ static __exception int js_iterator_get_value_done(JSContext *ctx, JSValue *sp) if (JS_IsException(value)) return -1; JS_FreeValue(ctx, obj); + /* put again the catch offset so that exceptions close the + iterator */ + sp[-2] = JS_NewCatchOffset(ctx, 0); sp[-1] = value; sp[0] = JS_NewBool(ctx, done); return 0; @@ -17214,6 +17232,11 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, sp += 2; } BREAK; + CASE(OP_for_await_of_next): + if (js_for_await_of_next(ctx, sp)) + goto exception; + sp++; + BREAK; CASE(OP_for_await_of_start): if (js_for_of_start(ctx, sp, TRUE)) goto exception; @@ -26138,12 +26161,9 @@ static __exception int js_parse_for_in_of(JSParseState *s, int label_name, emit_label(s, label_cont); if (is_for_of) { if (is_async) { - /* call the next method */ /* stack: iter_obj next catch_offset */ - emit_op(s, OP_dup3); - emit_op(s, OP_drop); - emit_op(s, OP_call_method); - emit_u16(s, 0); + /* call the next method */ + emit_op(s, OP_for_await_of_next); /* get the result of the promise */ emit_op(s, OP_await); /* unwrap the value and done values */ @@ -48426,25 +48446,6 @@ static const JSCFunctionListEntry js_async_function_proto_funcs[] = { JS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncFunction", JS_PROP_CONFIGURABLE ), }; -static JSValue js_async_from_sync_iterator_unwrap(JSContext *ctx, - JSValueConst this_val, - int argc, JSValueConst *argv, - int magic, JSValue *func_data) -{ - return js_create_iterator_result(ctx, JS_DupValue(ctx, argv[0]), - JS_ToBool(ctx, func_data[0])); -} - -static JSValue js_async_from_sync_iterator_unwrap_func_create(JSContext *ctx, - BOOL done) -{ - JSValueConst func_data[1]; - - func_data[0] = (JSValueConst)JS_NewBool(ctx, done); - return JS_NewCFunctionData(ctx, js_async_from_sync_iterator_unwrap, - 1, 0, 1, func_data); -} - /* AsyncIteratorPrototype */ static const JSCFunctionListEntry js_async_iterator_proto_funcs[] = { @@ -48506,6 +48507,41 @@ static JSValue JS_CreateAsyncFromSyncIterator(JSContext *ctx, return async_iter; } +static JSValue js_async_from_sync_iterator_unwrap(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv, + int magic, JSValue *func_data) +{ + return js_create_iterator_result(ctx, JS_DupValue(ctx, argv[0]), + JS_ToBool(ctx, func_data[0])); +} + +static JSValue js_async_from_sync_iterator_unwrap_func_create(JSContext *ctx, + BOOL done) +{ + JSValueConst func_data[1]; + + func_data[0] = (JSValueConst)JS_NewBool(ctx, done); + return JS_NewCFunctionData(ctx, js_async_from_sync_iterator_unwrap, + 1, 0, 1, func_data); +} + +static JSValue js_async_from_sync_iterator_close_wrap(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv, + int magic, JSValue *func_data) +{ + JS_Throw(ctx, JS_DupValue(ctx, argv[0])); + JS_IteratorClose(ctx, func_data[0], TRUE); + return JS_EXCEPTION; +} + +static JSValue js_async_from_sync_iterator_close_wrap_func_create(JSContext *ctx, JSValueConst sync_iter) +{ + return JS_NewCFunctionData(ctx, js_async_from_sync_iterator_close_wrap, + 1, 0, 1, &sync_iter); +} + static JSValue js_async_from_sync_iterator_next(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) @@ -48536,11 +48572,13 @@ static JSValue js_async_from_sync_iterator_next(JSContext *ctx, JSValueConst thi if (magic == GEN_MAGIC_RETURN) { err = js_create_iterator_result(ctx, JS_DupValue(ctx, argv[0]), TRUE); is_reject = 0; + goto done_resolve; } else { - err = JS_DupValue(ctx, argv[0]); - is_reject = 1; + if (JS_IteratorClose(ctx, s->sync_iter, FALSE)) + goto reject; + JS_ThrowTypeError(ctx, "throw is not a method"); + goto reject; } - goto done_resolve; } } value = JS_IteratorNext2(ctx, s->sync_iter, method, @@ -48555,21 +48593,9 @@ static JSValue js_async_from_sync_iterator_next(JSContext *ctx, JSValueConst thi if (JS_IsException(value)) goto reject; } - - if (JS_IsException(value)) { - JSValue res2; - reject: - err = JS_GetException(ctx); - is_reject = 1; - done_resolve: - res2 = JS_Call(ctx, resolving_funcs[is_reject], JS_UNDEFINED, - 1, (JSValueConst *)&err); - JS_FreeValue(ctx, err); - JS_FreeValue(ctx, res2); - JS_FreeValue(ctx, resolving_funcs[0]); - JS_FreeValue(ctx, resolving_funcs[1]); - return promise; - } + + if (JS_IsException(value)) + goto reject; { JSValue value_wrapper_promise, resolve_reject[2]; int res; @@ -48577,8 +48603,22 @@ static JSValue js_async_from_sync_iterator_next(JSContext *ctx, JSValueConst thi value_wrapper_promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, (JSValueConst *)&value, 0); if (JS_IsException(value_wrapper_promise)) { + JSValue res2; JS_FreeValue(ctx, value); - goto reject; + if (magic != GEN_MAGIC_RETURN && !done) { + JS_IteratorClose(ctx, s->sync_iter, TRUE); + } + reject: + err = JS_GetException(ctx); + is_reject = 1; + done_resolve: + res2 = JS_Call(ctx, resolving_funcs[is_reject], JS_UNDEFINED, + 1, (JSValueConst *)&err); + JS_FreeValue(ctx, err); + JS_FreeValue(ctx, res2); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + return promise; } resolve_reject[0] = @@ -48587,13 +48627,23 @@ static JSValue js_async_from_sync_iterator_next(JSContext *ctx, JSValueConst thi JS_FreeValue(ctx, value_wrapper_promise); goto fail; } + if (done || magic == GEN_MAGIC_RETURN) { + resolve_reject[1] = JS_UNDEFINED; + } else { + resolve_reject[1] = + js_async_from_sync_iterator_close_wrap_func_create(ctx, s->sync_iter); + if (JS_IsException(resolve_reject[1])) { + JS_FreeValue(ctx, value_wrapper_promise); + JS_FreeValue(ctx, resolve_reject[0]); + goto fail; + } + } JS_FreeValue(ctx, value); - resolve_reject[1] = JS_UNDEFINED; - res = perform_promise_then(ctx, value_wrapper_promise, (JSValueConst *)resolve_reject, (JSValueConst *)resolving_funcs); JS_FreeValue(ctx, resolve_reject[0]); + JS_FreeValue(ctx, resolve_reject[1]); JS_FreeValue(ctx, value_wrapper_promise); JS_FreeValue(ctx, resolving_funcs[0]); JS_FreeValue(ctx, resolving_funcs[1]); diff --git a/test262_errors.txt b/test262_errors.txt index 58dfe5a..cba927d 100644 --- a/test262_errors.txt +++ b/test262_errors.txt @@ -1,30 +1,6 @@ test262/test/annexB/language/comments/single-line-html-close-first-line-1.js:1: unexpected error type: SyntaxError: unexpected token in expression: '>' test262/test/annexB/language/comments/single-line-html-close-first-line-2.js:1: unexpected error type: SyntaxError: unexpected token in expression: '>' test262/test/annexB/language/comments/single-line-html-close-first-line-3.js:1: unexpected error type: SyntaxError: unexpected token in expression: '>' -test262/test/built-ins/AsyncFromSyncIteratorPrototype/next/iterator-result-poisoned-wrapper.js:64: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/next/iterator-result-poisoned-wrapper.js:64: strict mode: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/next/next-result-poisoned-wrapper.js:69: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/next/next-result-poisoned-wrapper.js:69: strict mode: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/next/yield-iterator-next-rejected-promise-close.js:59: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/next/yield-iterator-next-rejected-promise-close.js:59: strict mode: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/next/yield-next-rejected-promise-close.js:64: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/next/yield-next-rejected-promise-close.js:64: strict mode: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/iterator-result-rejected-promise-close.js:74: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/iterator-result-rejected-promise-close.js:74: strict mode: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/throw-null.js:52: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/throw-null.js:52: strict mode: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/throw-result-poisoned-wrapper.js:81: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/throw-result-poisoned-wrapper.js:81: strict mode: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/throw-undefined-get-return-undefined.js:64: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/throw-undefined-get-return-undefined.js:64: strict mode: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/throw-undefined-poisoned-return.js:68: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/throw-undefined-poisoned-return.js:68: strict mode: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/throw-undefined-return-not-object.js:72: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/throw-undefined-return-not-object.js:72: strict mode: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/throw-undefined-return-object.js:66: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/throw-undefined-return-object.js:66: strict mode: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/throw-undefined.js:41: TypeError: $DONE() not called -test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/throw-undefined.js:41: strict mode: TypeError: $DONE() not called test262/test/built-ins/Function/prototype/arguments/prop-desc.js:31: Test262Error: Function.prototype.arguments property getter/setter are the same function Expected SameValue(«function () { [native code] }», «function () {