fixed 'return' handling with 'yield' in 'for of' or with finally blocks (gihub ticket #166)

This commit is contained in:
Fabrice Bellard 2023-12-13 19:02:47 +01:00
parent 57105c7f23
commit 4bb8c35da7
3 changed files with 139 additions and 73 deletions

View file

@ -182,6 +182,7 @@ DEF( goto, 5, 0, 0, label) /* must come after if_true */
DEF( catch, 5, 0, 1, label) DEF( catch, 5, 0, 1, label)
DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */ DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */
DEF( ret, 1, 1, 0, none) /* used to return from the finally block */ DEF( ret, 1, 1, 0, none) /* used to return from the finally block */
DEF( nip_catch, 1, 2, 1, none) /* catch ... a -> a */
DEF( to_object, 1, 1, 1, none) DEF( to_object, 1, 1, 1, none)
//DEF( to_string, 1, 1, 1, none) //DEF( to_string, 1, 1, 1, none)
@ -208,7 +209,6 @@ DEF( for_of_next, 2, 3, 5, u8)
DEF(iterator_check_object, 1, 1, 1, none) DEF(iterator_check_object, 1, 1, 1, none)
DEF(iterator_get_value_done, 1, 1, 2, none) DEF(iterator_get_value_done, 1, 1, 2, none)
DEF( iterator_close, 1, 3, 0, none) DEF( iterator_close, 1, 3, 0, none)
DEF(iterator_close_return, 1, 4, 4, none)
DEF( iterator_next, 1, 4, 4, none) DEF( iterator_next, 1, 4, 4, none)
DEF( iterator_call, 2, 4, 5, u8) DEF( iterator_call, 2, 4, 5, u8)
DEF( initial_yield, 1, 0, 0, none) DEF( initial_yield, 1, 0, 0, none)

152
quickjs.c
View file

@ -87,6 +87,7 @@
8: dump stdlib functions 8: dump stdlib functions
16: dump bytecode in hex 16: dump bytecode in hex
32: dump line number table 32: dump line number table
64: dump compute_stack_size
*/ */
//#define DUMP_BYTECODE (1) //#define DUMP_BYTECODE (1)
/* dump the occurence of the automatic GC */ /* dump the occurence of the automatic GC */
@ -17091,26 +17092,21 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
} }
sp--; sp--;
BREAK; BREAK;
CASE(OP_iterator_close_return): CASE(OP_nip_catch):
{ {
JSValue ret_val; JSValue ret_val;
/* iter_obj next catch_offset ... ret_val -> /* catch_offset ... ret_val -> ret_eval */
ret_eval iter_obj next catch_offset */
ret_val = *--sp; ret_val = *--sp;
while (sp > stack_buf && while (sp > stack_buf &&
JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_CATCH_OFFSET) { JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_CATCH_OFFSET) {
JS_FreeValue(ctx, *--sp); JS_FreeValue(ctx, *--sp);
} }
if (unlikely(sp < stack_buf + 3)) { if (unlikely(sp == stack_buf)) {
JS_ThrowInternalError(ctx, "iterator_close_return"); JS_ThrowInternalError(ctx, "nip_catch");
JS_FreeValue(ctx, ret_val); JS_FreeValue(ctx, ret_val);
goto exception; goto exception;
} }
sp[0] = sp[-1]; sp[-1] = ret_val;
sp[-1] = sp[-2];
sp[-2] = sp[-3];
sp[-3] = ret_val;
sp++;
} }
BREAK; BREAK;
@ -25438,7 +25434,6 @@ static __exception int emit_break(JSParseState *s, JSAtom name, int is_cont)
static void emit_return(JSParseState *s, BOOL hasval) static void emit_return(JSParseState *s, BOOL hasval)
{ {
BlockEnv *top; BlockEnv *top;
int drop_count;
if (s->cur_func->func_kind != JS_FUNC_NORMAL) { if (s->cur_func->func_kind != JS_FUNC_NORMAL) {
if (!hasval) { if (!hasval) {
@ -25452,26 +25447,24 @@ static void emit_return(JSParseState *s, BOOL hasval)
} }
} }
drop_count = 0;
top = s->cur_func->top_break; top = s->cur_func->top_break;
while (top != NULL) { while (top != NULL) {
/* XXX: emit the appropriate OP_leave_scope opcodes? Probably not if (top->has_iterator || top->label_finally != -1) {
required as all local variables will be closed upon returning
from JS_CallInternal, but not in the same order. */
if (top->has_iterator) {
/* with 'yield', the exact number of OP_drop to emit is
unknown, so we use a specific operation to look for
the catch offset */
if (!hasval) { if (!hasval) {
emit_op(s, OP_undefined); emit_op(s, OP_undefined);
hasval = TRUE; hasval = TRUE;
} }
emit_op(s, OP_iterator_close_return); /* Remove the stack elements up to and including the catch
offset. When 'yield' is used in an expression we have
no easy way to count them, so we use this specific
instruction instead. */
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) { if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) {
int label_next, label_next2; int label_next, label_next2;
emit_op(s, OP_nip); /* next */
emit_op(s, OP_drop); /* catch offset */ emit_op(s, OP_swap);
emit_op(s, OP_drop); /* next */
emit_op(s, OP_get_field2); emit_op(s, OP_get_field2);
emit_atom(s, JS_ATOM_return); emit_atom(s, JS_ATOM_return);
/* stack: iter_obj return_func */ /* stack: iter_obj return_func */
@ -25488,24 +25481,15 @@ static void emit_return(JSParseState *s, BOOL hasval)
emit_label(s, label_next2); emit_label(s, label_next2);
emit_op(s, OP_drop); emit_op(s, OP_drop);
} else { } else {
emit_op(s, OP_rot3r);
emit_op(s, OP_undefined); /* dummy catch offset */
emit_op(s, OP_iterator_close); emit_op(s, OP_iterator_close);
} }
drop_count = -3; } else {
} /* execute the "finally" block */
drop_count += top->drop_count;
if (top->label_finally != -1) {
while(drop_count) {
/* must keep the stack top if hasval */
emit_op(s, hasval ? OP_nip : OP_drop);
drop_count--;
}
if (!hasval) {
/* must push return value to keep same stack size */
emit_op(s, OP_undefined);
hasval = TRUE;
}
emit_goto(s, OP_gosub, top->label_finally); emit_goto(s, OP_gosub, top->label_finally);
} }
}
top = top->prev; top = top->prev;
} }
if (s->cur_func->is_derived_class_constructor) { if (s->cur_func->is_derived_class_constructor) {
@ -31901,6 +31885,7 @@ typedef struct StackSizeState {
int bc_len; int bc_len;
int stack_len_max; int stack_len_max;
uint16_t *stack_level_tab; uint16_t *stack_level_tab;
int32_t *catch_pos_tab;
int *pc_stack; int *pc_stack;
int pc_stack_len; int pc_stack_len;
int pc_stack_size; int pc_stack_size;
@ -31908,7 +31893,7 @@ typedef struct StackSizeState {
/* 'op' is only used for error indication */ /* 'op' is only used for error indication */
static __exception int ss_check(JSContext *ctx, StackSizeState *s, static __exception int ss_check(JSContext *ctx, StackSizeState *s,
int pos, int op, int stack_len) int pos, int op, int stack_len, int catch_pos)
{ {
if ((unsigned)pos >= s->bc_len) { if ((unsigned)pos >= s->bc_len) {
JS_ThrowInternalError(ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos); JS_ThrowInternalError(ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos);
@ -31927,6 +31912,10 @@ static __exception int ss_check(JSContext *ctx, StackSizeState *s,
JS_ThrowInternalError(ctx, "inconsistent stack size: %d %d (pc=%d)", JS_ThrowInternalError(ctx, "inconsistent stack size: %d %d (pc=%d)",
s->stack_level_tab[pos], stack_len, pos); s->stack_level_tab[pos], stack_len, pos);
return -1; return -1;
} else if (s->catch_pos_tab[pos] != catch_pos) {
JS_ThrowInternalError(ctx, "inconsistent catch position: %d %d (pc=%d)",
s->catch_pos_tab[pos], catch_pos, pos);
return -1;
} else { } else {
return 0; return 0;
} }
@ -31934,6 +31923,7 @@ static __exception int ss_check(JSContext *ctx, StackSizeState *s,
/* mark as explored and store the stack size */ /* mark as explored and store the stack size */
s->stack_level_tab[pos] = stack_len; s->stack_level_tab[pos] = stack_len;
s->catch_pos_tab[pos] = catch_pos;
/* queue the new PC to explore */ /* queue the new PC to explore */
if (js_resize_array(ctx, (void **)&s->pc_stack, sizeof(s->pc_stack[0]), if (js_resize_array(ctx, (void **)&s->pc_stack, sizeof(s->pc_stack[0]),
@ -31948,7 +31938,7 @@ static __exception int compute_stack_size(JSContext *ctx,
int *pstack_size) int *pstack_size)
{ {
StackSizeState s_s, *s = &s_s; StackSizeState s_s, *s = &s_s;
int i, diff, n_pop, pos_next, stack_len, pos, op; int i, diff, n_pop, pos_next, stack_len, pos, op, catch_pos, catch_level;
const JSOpCode *oi; const JSOpCode *oi;
const uint8_t *bc_buf; const uint8_t *bc_buf;
@ -31961,24 +31951,33 @@ static __exception int compute_stack_size(JSContext *ctx,
return -1; return -1;
for(i = 0; i < s->bc_len; i++) for(i = 0; i < s->bc_len; i++)
s->stack_level_tab[i] = 0xffff; s->stack_level_tab[i] = 0xffff;
s->stack_len_max = 0;
s->pc_stack = NULL; s->pc_stack = NULL;
s->catch_pos_tab = js_malloc(ctx, sizeof(s->catch_pos_tab[0]) *
s->bc_len);
if (!s->catch_pos_tab)
goto fail;
s->stack_len_max = 0;
s->pc_stack_len = 0; s->pc_stack_len = 0;
s->pc_stack_size = 0; s->pc_stack_size = 0;
/* breadth-first graph exploration */ /* breadth-first graph exploration */
if (ss_check(ctx, s, 0, OP_invalid, 0)) if (ss_check(ctx, s, 0, OP_invalid, 0, -1))
goto fail; goto fail;
while (s->pc_stack_len > 0) { while (s->pc_stack_len > 0) {
pos = s->pc_stack[--s->pc_stack_len]; pos = s->pc_stack[--s->pc_stack_len];
stack_len = s->stack_level_tab[pos]; stack_len = s->stack_level_tab[pos];
catch_pos = s->catch_pos_tab[pos];
op = bc_buf[pos]; op = bc_buf[pos];
if (op == 0 || op >= OP_COUNT) { if (op == 0 || op >= OP_COUNT) {
JS_ThrowInternalError(ctx, "invalid opcode (op=%d, pc=%d)", op, pos); JS_ThrowInternalError(ctx, "invalid opcode (op=%d, pc=%d)", op, pos);
goto fail; goto fail;
} }
oi = &short_opcode_info(op); oi = &short_opcode_info(op);
#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 64)
printf("%5d: %10s %5d %5d\n", pos, oi->name, stack_len, catch_pos);
#endif
pos_next = pos + oi->size; pos_next = pos + oi->size;
if (pos_next > s->bc_len) { if (pos_next > s->bc_len) {
JS_ThrowInternalError(ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos); JS_ThrowInternalError(ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos);
@ -32034,55 +32033,104 @@ static __exception int compute_stack_size(JSContext *ctx,
case OP_if_true8: case OP_if_true8:
case OP_if_false8: case OP_if_false8:
diff = (int8_t)bc_buf[pos + 1]; diff = (int8_t)bc_buf[pos + 1];
if (ss_check(ctx, s, pos + 1 + diff, op, stack_len)) if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos))
goto fail; goto fail;
break; break;
#endif #endif
case OP_if_true: case OP_if_true:
case OP_if_false: case OP_if_false:
case OP_catch:
diff = get_u32(bc_buf + pos + 1); diff = get_u32(bc_buf + pos + 1);
if (ss_check(ctx, s, pos + 1 + diff, op, stack_len)) if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos))
goto fail; goto fail;
break; break;
case OP_gosub: case OP_gosub:
diff = get_u32(bc_buf + pos + 1); diff = get_u32(bc_buf + pos + 1);
if (ss_check(ctx, s, pos + 1 + diff, op, stack_len + 1)) if (ss_check(ctx, s, pos + 1 + diff, op, stack_len + 1, catch_pos))
goto fail; goto fail;
break; break;
case OP_with_get_var: case OP_with_get_var:
case OP_with_delete_var: case OP_with_delete_var:
diff = get_u32(bc_buf + pos + 5); diff = get_u32(bc_buf + pos + 5);
if (ss_check(ctx, s, pos + 5 + diff, op, stack_len + 1)) if (ss_check(ctx, s, pos + 5 + diff, op, stack_len + 1, catch_pos))
goto fail; goto fail;
break; break;
case OP_with_make_ref: case OP_with_make_ref:
case OP_with_get_ref: case OP_with_get_ref:
case OP_with_get_ref_undef: case OP_with_get_ref_undef:
diff = get_u32(bc_buf + pos + 5); diff = get_u32(bc_buf + pos + 5);
if (ss_check(ctx, s, pos + 5 + diff, op, stack_len + 2)) if (ss_check(ctx, s, pos + 5 + diff, op, stack_len + 2, catch_pos))
goto fail; goto fail;
break; break;
case OP_with_put_var: case OP_with_put_var:
diff = get_u32(bc_buf + pos + 5); diff = get_u32(bc_buf + pos + 5);
if (ss_check(ctx, s, pos + 5 + diff, op, stack_len - 1)) if (ss_check(ctx, s, pos + 5 + diff, op, stack_len - 1, catch_pos))
goto fail; goto fail;
break; break;
case OP_catch:
diff = get_u32(bc_buf + pos + 1);
if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos))
goto fail;
catch_pos = pos;
break;
case OP_for_of_start:
case OP_for_await_of_start:
catch_pos = pos;
break;
/* we assume the catch offset entry is only removed with
some op codes */
case OP_drop:
catch_level = stack_len;
goto check_catch;
case OP_nip:
catch_level = stack_len - 1;
goto check_catch;
case OP_nip1:
catch_level = stack_len - 1;
goto check_catch;
case OP_iterator_close:
catch_level = stack_len + 2;
check_catch:
/* Note: for for_of_start/for_await_of_start we consider
the catch offset is on the first stack entry instead of
the thirst */
if (catch_pos >= 0) {
int level;
level = s->stack_level_tab[catch_pos];
if (bc_buf[catch_pos] != OP_catch)
level++; /* for_of_start, for_wait_of_start */
/* catch_level = stack_level before op_catch is executed ? */
if (catch_level == level) {
catch_pos = s->catch_pos_tab[catch_pos];
}
}
break;
case OP_nip_catch:
if (catch_pos < 0) {
JS_ThrowInternalError(ctx, "nip_catch: no catch op (pc=%d)", pos);
goto fail;
}
stack_len = s->stack_level_tab[catch_pos];
if (bc_buf[catch_pos] != OP_catch)
stack_len++; /* for_of_start, for_wait_of_start */
stack_len++; /* no stack overflow is possible by construction */
catch_pos = s->catch_pos_tab[catch_pos];
break;
default: default:
break; break;
} }
if (ss_check(ctx, s, pos_next, op, stack_len)) if (ss_check(ctx, s, pos_next, op, stack_len, catch_pos))
goto fail; goto fail;
done_insn: ; done_insn: ;
} }
js_free(ctx, s->stack_level_tab);
js_free(ctx, s->pc_stack); js_free(ctx, s->pc_stack);
js_free(ctx, s->catch_pos_tab);
js_free(ctx, s->stack_level_tab);
*pstack_size = s->stack_len_max; *pstack_size = s->stack_len_max;
return 0; return 0;
fail: fail:
js_free(ctx, s->stack_level_tab);
js_free(ctx, s->pc_stack); js_free(ctx, s->pc_stack);
js_free(ctx, s->catch_pos_tab);
js_free(ctx, s->stack_level_tab);
*pstack_size = 0; *pstack_size = 0;
return -1; return -1;
} }

View file

@ -645,6 +645,18 @@ function test_generator()
assert(ret, "ret_val"); assert(ret, "ret_val");
return 3; return 3;
} }
function *f3() {
var ret;
/* test stack consistency with nip_n to handle yield return +
* finally clause */
try {
ret = 2 + (yield 1);
} catch(e) {
} finally {
ret++;
}
return ret;
}
var g, v; var g, v;
g = f(); g = f();
v = g.next(); v = g.next();
@ -665,6 +677,12 @@ function test_generator()
assert(v.value === 3 && v.done === true); assert(v.value === 3 && v.done === true);
v = g.next(); v = g.next();
assert(v.value === undefined && v.done === true); assert(v.value === undefined && v.done === true);
g = f3();
v = g.next();
assert(v.value === 1 && v.done === false);
v = g.next(3);
assert(v.value === 6 && v.done === true);
} }
test(); test();