From: Fabrice Bellard Date: Mon, 26 Oct 2026 15:00:14 +0000 (+0100) Subject: Await the async iterator return() when a for-await loop breaks (Divy Srivastava) X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=1ec58aaa769a992213ec2b871b0228074169904b;p=quickjs.git Await the async iterator return() when a for-await loop breaks (Divy Srivastava) --- diff --git a/quickjs.c b/quickjs.c index c564fdb..98bb3ed 100644 --- a/quickjs.c +++ b/quickjs.c @@ -17086,6 +17086,16 @@ static __exception int js_iterator_get_value_done(JSContext *ctx, JSValue *sp) sp[-2] = JS_NewCatchOffset(ctx, 0); sp[-1] = value; sp[0] = JS_NewBool(ctx, done); + if (done) { + /* Iterator exhausted via {done:true}: drop the iterator object (stack + layout at the for-await `next` call is iter_obj,next,catch_offset,result + so iter_obj is sp[-4]) so the trailing OP_iterator_close skips calling + return(). Mirrors js_for_of_next, which nulls the iterator on done. + Per spec AsyncIteratorClose must NOT run on normal completion. This op + is emitted only by the for-await-of loop, so the layout is fixed. */ + JS_FreeValue(ctx, sp[-4]); + sp[-4] = JS_UNDEFINED; + } return 0; } @@ -22350,6 +22360,7 @@ typedef struct BlockEnv { int using_label_catch; int using_label_end; uint8_t has_iterator : 1; + uint8_t is_async_iterator : 1; uint8_t is_regular_stmt : 1; /* i.e. not a loop statement */ uint8_t has_using : 1; /* the block contains a using declaration */ uint8_t is_await_using : 1; /* the block has await using declarations */ @@ -28836,6 +28847,7 @@ static void push_break_entry(JSFunctionDef *fd, BlockEnv *be, be->label_finally = -1; be->scope_level = fd->scope_level; be->has_iterator = FALSE; + be->is_async_iterator = FALSE; be->is_regular_stmt = FALSE; be->has_using = FALSE; be->is_await_using = FALSE; @@ -28906,6 +28918,51 @@ static void pop_break_entry(JSParseState *s) fd->top_break = be->prev; } +static void emit_iterator_close(JSParseState *s, BOOL is_async, + BOOL preserve_completion) +{ + int label_no_return, label_done; + + if (!is_async) { + if (preserve_completion) { + emit_op(s, OP_rot3r); + emit_op(s, OP_undefined); /* dummy catch offset */ + } + emit_op(s, OP_iterator_close); + return; + } + + if (preserve_completion) { + /* iter_obj next completion -> completion iter_obj */ + emit_op(s, OP_nip); + emit_op(s, OP_swap); + } else { + /* iter_obj next catch_offset -> iter_obj */ + emit_op(s, OP_drop); + emit_op(s, OP_drop); + } + + emit_op(s, OP_dup); + emit_op(s, OP_is_undefined); + label_done = emit_goto(s, OP_if_true, -1); + + emit_op(s, OP_get_field2); + emit_atom(s, JS_ATOM_return); + /* completion? iter_obj return_func */ + emit_op(s, OP_dup); + emit_op(s, OP_is_undefined_or_null); + label_no_return = emit_goto(s, OP_if_true, -1); + emit_op(s, OP_call_method); + emit_u16(s, 0); + emit_op(s, OP_await); + emit_goto(s, OP_goto, label_done); + emit_label(s, label_no_return); + emit_op(s, OP_drop); + + emit_label(s, label_done); + emit_op(s, OP_drop); +} + static __exception int emit_break(JSParseState *s, JSAtom name, int is_cont) { BlockEnv *top; @@ -28932,7 +28989,7 @@ static __exception int emit_break(JSParseState *s, JSAtom name, int is_cont) } i = 0; if (top->has_iterator) { - emit_op(s, OP_iterator_close); + emit_iterator_close(s, top->is_async_iterator, FALSE); i += 3; } for(; i < top->drop_count; i++) @@ -28992,30 +29049,7 @@ static void emit_return(JSParseState *s, BOOL hasval) emit_op(s, OP_nip_catch); /* stack: iter_obj next ret_val */ if (top->has_iterator) { - if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) { - int label_next, label_next2; - emit_op(s, OP_nip); /* next */ - emit_op(s, OP_swap); - emit_op(s, OP_get_field2); - emit_atom(s, JS_ATOM_return); - /* stack: iter_obj return_func */ - emit_op(s, OP_dup); - emit_op(s, OP_is_undefined_or_null); - label_next = emit_goto(s, OP_if_true, -1); - emit_op(s, OP_call_method); - emit_u16(s, 0); - emit_op(s, OP_iterator_check_object); - emit_op(s, OP_await); - label_next2 = emit_goto(s, OP_goto, -1); - emit_label(s, label_next); - emit_op(s, OP_drop); - emit_label(s, label_next2); - emit_op(s, OP_drop); - } else { - emit_op(s, OP_rot3r); - emit_op(s, OP_undefined); /* dummy catch offset */ - emit_op(s, OP_iterator_close); - } + emit_iterator_close(s, top->is_async_iterator, TRUE); } else if (top->has_using) { /* Dispose using variables. The return value is on TOS. stack: ret_val */ @@ -29546,6 +29580,7 @@ static __exception int js_parse_for_in_of(JSParseState *s, int label_name, that a yield in the expression does not try to close a not-yet-created iterator */ break_entry.has_iterator = TRUE; + break_entry.is_async_iterator = is_async; break_entry.drop_count += 2; if (is_async) emit_op(s, OP_for_await_of_start); @@ -29626,7 +29661,7 @@ static __exception int js_parse_for_in_of(JSParseState *s, int label_name, emit_label(s, label_break); if (is_for_of) { /* close and drop enum_rec */ - emit_op(s, OP_iterator_close); + emit_iterator_close(s, is_async, FALSE); } else { emit_op(s, OP_drop); }