Skip to content

Commit 21a775c

Browse files
committed
Fix 'return' handling with 'yield' in 'for of' or with finally blocks
Ref: bellard/quickjs@4bb8c35
1 parent 31fbc9f commit 21a775c

File tree

3 files changed

+141
-76
lines changed

3 files changed

+141
-76
lines changed

quickjs-opcode.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ DEF( goto, 5, 0, 0, label) /* must come after if_true */
183183
DEF( catch, 5, 0, 1, label)
184184
DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */
185185
DEF( ret, 1, 1, 0, none) /* used to return from the finally block */
186+
DEF( nip_catch, 1, 2, 1, none) /* catch ... a -> a */
186187

187188
DEF( to_object, 1, 1, 1, none)
188189
//DEF( to_string, 1, 1, 1, none)
@@ -209,7 +210,6 @@ DEF( for_of_next, 2, 3, 5, u8)
209210
DEF(iterator_check_object, 1, 1, 1, none)
210211
DEF(iterator_get_value_done, 1, 1, 2, none)
211212
DEF( iterator_close, 1, 3, 0, none)
212-
DEF(iterator_close_return, 1, 4, 4, none)
213213
DEF( iterator_next, 1, 4, 4, none)
214214
DEF( iterator_call, 2, 4, 5, u8)
215215
DEF( initial_yield, 1, 0, 0, none)

quickjs.c

+122-75
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@
7070
8: dump stdlib functions
7171
16: dump bytecode in hex
7272
32: dump line number table
73-
64: dump executed bytecode
73+
64: dump compute_stack_size
74+
128: dump executed bytecode
7475
*/
7576
//#define DUMP_BYTECODE (1)
7677
/* dump the occurence of the automatic GC */
@@ -14330,7 +14331,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj,
1433014331
size_t alloca_size;
1433114332
JSInlineCache *ic;
1433214333

14333-
#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 64)
14334+
#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 128)
1433414335
#define DUMP_BYTECODE_OR_DONT(pc) dump_single_byte_code(ctx, pc, b);
1433514336
#else
1433614337
#define DUMP_BYTECODE_OR_DONT(pc)
@@ -14439,7 +14440,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj,
1443914440
ctx = b->realm; /* set the current realm */
1444014441
ic = b->ic;
1444114442

14442-
#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 64)
14443+
#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 128)
1444314444
print_func_name(b);
1444414445
#endif
1444514446

@@ -15575,26 +15576,21 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj,
1557515576
}
1557615577
sp--;
1557715578
BREAK;
15578-
CASE(OP_iterator_close_return):
15579+
CASE(OP_nip_catch):
1557915580
{
1558015581
JSValue ret_val;
15581-
/* iter_obj next catch_offset ... ret_val ->
15582-
ret_eval iter_obj next catch_offset */
15582+
/* catch_offset ... ret_val -> ret_eval */
1558315583
ret_val = *--sp;
1558415584
while (sp > stack_buf &&
1558515585
JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_CATCH_OFFSET) {
1558615586
JS_FreeValue(ctx, *--sp);
1558715587
}
15588-
if (unlikely(sp < stack_buf + 3)) {
15589-
JS_ThrowInternalError(ctx, "iterator_close_return");
15588+
if (unlikely(sp == stack_buf)) {
15589+
JS_ThrowInternalError(ctx, "nip_catch");
1559015590
JS_FreeValue(ctx, ret_val);
1559115591
goto exception;
1559215592
}
15593-
sp[0] = sp[-1];
15594-
sp[-1] = sp[-2];
15595-
sp[-2] = sp[-3];
15596-
sp[-3] = ret_val;
15597-
sp++;
15593+
sp[-1] = ret_val;
1559815594
}
1559915595
BREAK;
1560015596

@@ -23852,7 +23848,6 @@ static __exception int emit_break(JSParseState *s, JSAtom name, int is_cont)
2385223848
static void emit_return(JSParseState *s, BOOL hasval)
2385323849
{
2385423850
BlockEnv *top;
23855-
int drop_count;
2385623851

2385723852
if (s->cur_func->func_kind != JS_FUNC_NORMAL) {
2385823853
if (!hasval) {
@@ -23866,60 +23861,49 @@ static void emit_return(JSParseState *s, BOOL hasval)
2386623861
}
2386723862
}
2386823863

23869-
drop_count = 0;
2387023864
top = s->cur_func->top_break;
2387123865
while (top != NULL) {
23872-
/* XXX: emit the appropriate OP_leave_scope opcodes? Probably not
23873-
required as all local variables will be closed upon returning
23874-
from JS_CallInternal, but not in the same order. */
23875-
if (top->has_iterator) {
23876-
/* with 'yield', the exact number of OP_drop to emit is
23877-
unknown, so we use a specific operation to look for
23878-
the catch offset */
23866+
if (top->has_iterator || top->label_finally != -1) {
2387923867
if (!hasval) {
2388023868
emit_op(s, OP_undefined);
2388123869
hasval = TRUE;
2388223870
}
23883-
emit_op(s, OP_iterator_close_return);
23884-
if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) {
23885-
int label_next, label_next2;
23886-
23887-
emit_op(s, OP_drop); /* catch offset */
23888-
emit_op(s, OP_drop); /* next */
23889-
emit_op(s, OP_get_field2);
23890-
emit_atom(s, JS_ATOM_return);
23891-
emit_ic(s, JS_ATOM_return);
23892-
/* stack: iter_obj return_func */
23893-
emit_op(s, OP_dup);
23894-
emit_op(s, OP_is_undefined_or_null);
23895-
label_next = emit_goto(s, OP_if_true, -1);
23896-
emit_op(s, OP_call_method);
23897-
emit_u16(s, 0);
23898-
emit_op(s, OP_iterator_check_object);
23899-
emit_op(s, OP_await);
23900-
label_next2 = emit_goto(s, OP_goto, -1);
23901-
emit_label(s, label_next);
23902-
emit_op(s, OP_drop);
23903-
emit_label(s, label_next2);
23904-
emit_op(s, OP_drop);
23871+
/* Remove the stack elements up to and including the catch
23872+
offset. When 'yield' is used in an expression we have
23873+
no easy way to count them, so we use this specific
23874+
instruction instead. */
23875+
emit_op(s, OP_nip_catch);
23876+
/* stack: iter_obj next ret_val */
23877+
if (top->has_iterator) {
23878+
if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) {
23879+
int label_next, label_next2;
23880+
emit_op(s, OP_nip); /* next */
23881+
emit_op(s, OP_swap);
23882+
emit_op(s, OP_get_field2);
23883+
emit_atom(s, JS_ATOM_return);
23884+
emit_ic(s, JS_ATOM_return);
23885+
/* stack: iter_obj return_func */
23886+
emit_op(s, OP_dup);
23887+
emit_op(s, OP_is_undefined_or_null);
23888+
label_next = emit_goto(s, OP_if_true, -1);
23889+
emit_op(s, OP_call_method);
23890+
emit_u16(s, 0);
23891+
emit_op(s, OP_iterator_check_object);
23892+
emit_op(s, OP_await);
23893+
label_next2 = emit_goto(s, OP_goto, -1);
23894+
emit_label(s, label_next);
23895+
emit_op(s, OP_drop);
23896+
emit_label(s, label_next2);
23897+
emit_op(s, OP_drop);
23898+
} else {
23899+
emit_op(s, OP_rot3r);
23900+
emit_op(s, OP_undefined); /* dummy catch offset */
23901+
emit_op(s, OP_iterator_close);
23902+
}
2390523903
} else {
23906-
emit_op(s, OP_iterator_close);
23907-
}
23908-
drop_count = -3;
23909-
}
23910-
drop_count += top->drop_count;
23911-
if (top->label_finally != -1) {
23912-
while(drop_count) {
23913-
/* must keep the stack top if hasval */
23914-
emit_op(s, hasval ? OP_nip : OP_drop);
23915-
drop_count--;
23904+
/* execute the "finally" block */
23905+
emit_goto(s, OP_gosub, top->label_finally);
2391623906
}
23917-
if (!hasval) {
23918-
/* must push return value to keep same stack size */
23919-
emit_op(s, OP_undefined);
23920-
hasval = TRUE;
23921-
}
23922-
emit_goto(s, OP_gosub, top->label_finally);
2392323907
}
2392423908
top = top->prev;
2392523909
}
@@ -30431,14 +30415,15 @@ typedef struct StackSizeState {
3043130415
int bc_len;
3043230416
int stack_len_max;
3043330417
uint16_t *stack_level_tab;
30418+
int32_t *catch_pos_tab;
3043430419
int *pc_stack;
3043530420
int pc_stack_len;
3043630421
int pc_stack_size;
3043730422
} StackSizeState;
3043830423

3043930424
/* 'op' is only used for error indication */
3044030425
static __exception int ss_check(JSContext *ctx, StackSizeState *s,
30441-
int pos, int op, int stack_len)
30426+
int pos, int op, int stack_len, int catch_pos)
3044230427
{
3044330428
if ((unsigned)pos >= s->bc_len) {
3044430429
JS_ThrowInternalError(ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos);
@@ -30457,13 +30442,18 @@ static __exception int ss_check(JSContext *ctx, StackSizeState *s,
3045730442
JS_ThrowInternalError(ctx, "inconsistent stack size: %d %d (pc=%d)",
3045830443
s->stack_level_tab[pos], stack_len, pos);
3045930444
return -1;
30445+
} else if (s->catch_pos_tab[pos] != catch_pos) {
30446+
JS_ThrowInternalError(ctx, "inconsistent catch position: %d %d (pc=%d)",
30447+
s->catch_pos_tab[pos], catch_pos, pos);
30448+
return -1;
3046030449
} else {
3046130450
return 0;
3046230451
}
3046330452
}
3046430453

3046530454
/* mark as explored and store the stack size */
3046630455
s->stack_level_tab[pos] = stack_len;
30456+
s->catch_pos_tab[pos] = catch_pos;
3046730457

3046830458
/* queue the new PC to explore */
3046930459
if (js_resize_array(ctx, (void **)&s->pc_stack, sizeof(s->pc_stack[0]),
@@ -30478,7 +30468,7 @@ static __exception int compute_stack_size(JSContext *ctx,
3047830468
int *pstack_size)
3047930469
{
3048030470
StackSizeState s_s, *s = &s_s;
30481-
int i, diff, n_pop, pos_next, stack_len, pos, op;
30471+
int i, diff, n_pop, pos_next, stack_len, pos, op, catch_pos, catch_level;
3048230472
const JSOpCode *oi;
3048330473
const uint8_t *bc_buf;
3048430474

@@ -30491,24 +30481,32 @@ static __exception int compute_stack_size(JSContext *ctx,
3049130481
return -1;
3049230482
for(i = 0; i < s->bc_len; i++)
3049330483
s->stack_level_tab[i] = 0xffff;
30494-
s->stack_len_max = 0;
3049530484
s->pc_stack = NULL;
30485+
s->catch_pos_tab = js_malloc(ctx, sizeof(s->catch_pos_tab[0]) * s->bc_len);
30486+
if (!s->catch_pos_tab)
30487+
goto fail;
30488+
30489+
s->stack_len_max = 0;
3049630490
s->pc_stack_len = 0;
3049730491
s->pc_stack_size = 0;
3049830492

3049930493
/* breadth-first graph exploration */
30500-
if (ss_check(ctx, s, 0, OP_invalid, 0))
30494+
if (ss_check(ctx, s, 0, OP_invalid, 0, -1))
3050130495
goto fail;
3050230496

3050330497
while (s->pc_stack_len > 0) {
3050430498
pos = s->pc_stack[--s->pc_stack_len];
3050530499
stack_len = s->stack_level_tab[pos];
30500+
catch_pos = s->catch_pos_tab[pos];
3050630501
op = bc_buf[pos];
3050730502
if (op == 0 || op >= OP_COUNT) {
3050830503
JS_ThrowInternalError(ctx, "invalid opcode (op=%d, pc=%d)", op, pos);
3050930504
goto fail;
3051030505
}
3051130506
oi = &short_opcode_info(op);
30507+
#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 64)
30508+
printf("%5d: %10s %5d %5d\n", pos, oi->name, stack_len, catch_pos);
30509+
#endif
3051230510
pos_next = pos + oi->size;
3051330511
if (pos_next > s->bc_len) {
3051430512
JS_ThrowInternalError(ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos);
@@ -30559,54 +30557,103 @@ static __exception int compute_stack_size(JSContext *ctx,
3055930557
case OP_if_true8:
3056030558
case OP_if_false8:
3056130559
diff = (int8_t)bc_buf[pos + 1];
30562-
if (ss_check(ctx, s, pos + 1 + diff, op, stack_len))
30560+
if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos))
3056330561
goto fail;
3056430562
break;
3056530563
case OP_if_true:
3056630564
case OP_if_false:
30567-
case OP_catch:
3056830565
diff = get_u32(bc_buf + pos + 1);
30569-
if (ss_check(ctx, s, pos + 1 + diff, op, stack_len))
30566+
if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos))
3057030567
goto fail;
3057130568
break;
3057230569
case OP_gosub:
3057330570
diff = get_u32(bc_buf + pos + 1);
30574-
if (ss_check(ctx, s, pos + 1 + diff, op, stack_len + 1))
30571+
if (ss_check(ctx, s, pos + 1 + diff, op, stack_len + 1, catch_pos))
3057530572
goto fail;
3057630573
break;
3057730574
case OP_with_get_var:
3057830575
case OP_with_delete_var:
3057930576
diff = get_u32(bc_buf + pos + 5);
30580-
if (ss_check(ctx, s, pos + 5 + diff, op, stack_len + 1))
30577+
if (ss_check(ctx, s, pos + 5 + diff, op, stack_len + 1, catch_pos))
3058130578
goto fail;
3058230579
break;
3058330580
case OP_with_make_ref:
3058430581
case OP_with_get_ref:
3058530582
case OP_with_get_ref_undef:
3058630583
diff = get_u32(bc_buf + pos + 5);
30587-
if (ss_check(ctx, s, pos + 5 + diff, op, stack_len + 2))
30584+
if (ss_check(ctx, s, pos + 5 + diff, op, stack_len + 2, catch_pos))
3058830585
goto fail;
3058930586
break;
3059030587
case OP_with_put_var:
3059130588
diff = get_u32(bc_buf + pos + 5);
30592-
if (ss_check(ctx, s, pos + 5 + diff, op, stack_len - 1))
30589+
if (ss_check(ctx, s, pos + 5 + diff, op, stack_len - 1, catch_pos))
3059330590
goto fail;
3059430591
break;
30595-
30592+
case OP_catch:
30593+
diff = get_u32(bc_buf + pos + 1);
30594+
if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos))
30595+
goto fail;
30596+
catch_pos = pos;
30597+
break;
30598+
case OP_for_of_start:
30599+
case OP_for_await_of_start:
30600+
catch_pos = pos;
30601+
break;
30602+
/* we assume the catch offset entry is only removed with
30603+
some op codes */
30604+
case OP_drop:
30605+
catch_level = stack_len;
30606+
goto check_catch;
30607+
case OP_nip:
30608+
catch_level = stack_len - 1;
30609+
goto check_catch;
30610+
case OP_nip1:
30611+
catch_level = stack_len - 1;
30612+
goto check_catch;
30613+
case OP_iterator_close:
30614+
catch_level = stack_len + 2;
30615+
check_catch:
30616+
/* Note: for for_of_start/for_await_of_start we consider
30617+
the catch offset is on the first stack entry instead of
30618+
the thirst */
30619+
if (catch_pos >= 0) {
30620+
int level;
30621+
level = s->stack_level_tab[catch_pos];
30622+
if (bc_buf[catch_pos] != OP_catch)
30623+
level++; /* for_of_start, for_wait_of_start */
30624+
/* catch_level = stack_level before op_catch is executed ? */
30625+
if (catch_level == level) {
30626+
catch_pos = s->catch_pos_tab[catch_pos];
30627+
}
30628+
}
30629+
break;
30630+
case OP_nip_catch:
30631+
if (catch_pos < 0) {
30632+
JS_ThrowInternalError(ctx, "nip_catch: no catch op (pc=%d)", pos);
30633+
goto fail;
30634+
}
30635+
stack_len = s->stack_level_tab[catch_pos];
30636+
if (bc_buf[catch_pos] != OP_catch)
30637+
stack_len++; /* for_of_start, for_wait_of_start */
30638+
stack_len++; /* no stack overflow is possible by construction */
30639+
catch_pos = s->catch_pos_tab[catch_pos];
30640+
break;
3059630641
default:
3059730642
break;
3059830643
}
30599-
if (ss_check(ctx, s, pos_next, op, stack_len))
30644+
if (ss_check(ctx, s, pos_next, op, stack_len, catch_pos))
3060030645
goto fail;
3060130646
done_insn: ;
3060230647
}
30603-
js_free(ctx, s->stack_level_tab);
3060430648
js_free(ctx, s->pc_stack);
30649+
js_free(ctx, s->catch_pos_tab);
30650+
js_free(ctx, s->stack_level_tab);
3060530651
*pstack_size = s->stack_len_max;
3060630652
return 0;
3060730653
fail:
30608-
js_free(ctx, s->stack_level_tab);
3060930654
js_free(ctx, s->pc_stack);
30655+
js_free(ctx, s->catch_pos_tab);
30656+
js_free(ctx, s->stack_level_tab);
3061030657
*pstack_size = 0;
3061130658
return -1;
3061230659
}

0 commit comments

Comments
 (0)