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)

190
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,59 +25447,48 @@ 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
if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) { offset. When 'yield' is used in an expression we have
int label_next, label_next2; no easy way to count them, so we use this specific
instruction instead. */
emit_op(s, OP_drop); /* catch offset */ emit_op(s, OP_nip_catch);
emit_op(s, OP_drop); /* next */ /* stack: iter_obj next ret_val */
emit_op(s, OP_get_field2); if (top->has_iterator) {
emit_atom(s, JS_ATOM_return); if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) {
/* stack: iter_obj return_func */ int label_next, label_next2;
emit_op(s, OP_dup); emit_op(s, OP_nip); /* next */
emit_op(s, OP_is_undefined_or_null); emit_op(s, OP_swap);
label_next = emit_goto(s, OP_if_true, -1); emit_op(s, OP_get_field2);
emit_op(s, OP_call_method); emit_atom(s, JS_ATOM_return);
emit_u16(s, 0); /* stack: iter_obj return_func */
emit_op(s, OP_iterator_check_object); emit_op(s, OP_dup);
emit_op(s, OP_await); emit_op(s, OP_is_undefined_or_null);
label_next2 = emit_goto(s, OP_goto, -1); label_next = emit_goto(s, OP_if_true, -1);
emit_label(s, label_next); emit_op(s, OP_call_method);
emit_op(s, OP_drop); emit_u16(s, 0);
emit_label(s, label_next2); emit_op(s, OP_iterator_check_object);
emit_op(s, OP_drop); 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);
}
} else { } else {
emit_op(s, OP_iterator_close); /* execute the "finally" block */
emit_goto(s, OP_gosub, top->label_finally);
} }
drop_count = -3;
}
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);
} }
top = top->prev; top = top->prev;
} }
@ -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();