summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFabrice Bellard <fabrice@bellard.org>2023-12-13 19:02:47 +0100
committerFabrice Bellard <fabrice@bellard.org>2023-12-13 19:02:47 +0100
commit4bb8c35da7c83b5eb67541772b857e822da855da (patch)
tree3e4d0e578217c85af579416d4d644fa132cc13da
parent57105c7f23d196b2a6d954d70c290d021f8bbefc (diff)
downloadquickjs-4bb8c35da7c83b5eb67541772b857e822da855da.tar.gz
quickjs-4bb8c35da7c83b5eb67541772b857e822da855da.zip
fixed 'return' handling with 'yield' in 'for of' or with finally blocks (gihub ticket #166)
-rw-r--r--quickjs-opcode.h2
-rw-r--r--quickjs.c192
-rw-r--r--tests/test_builtin.js18
3 files changed, 139 insertions, 73 deletions
diff --git a/quickjs-opcode.h b/quickjs-opcode.h
index 15a9fce..55e9809 100644
--- a/quickjs-opcode.h
+++ b/quickjs-opcode.h
@@ -182,6 +182,7 @@ DEF( goto, 5, 0, 0, label) /* must come after if_true */
DEF( catch, 5, 0, 1, label)
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( nip_catch, 1, 2, 1, none) /* catch ... a -> a */
DEF( to_object, 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_get_value_done, 1, 1, 2, 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_call, 2, 4, 5, u8)
DEF( initial_yield, 1, 0, 0, none)
diff --git a/quickjs.c b/quickjs.c
index 496930f..d68abf7 100644
--- a/quickjs.c
+++ b/quickjs.c
@@ -87,6 +87,7 @@
8: dump stdlib functions
16: dump bytecode in hex
32: dump line number table
+ 64: dump compute_stack_size
*/
//#define DUMP_BYTECODE (1)
/* dump the occurence of the automatic GC */
@@ -17091,26 +17092,21 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
}
sp--;
BREAK;
- CASE(OP_iterator_close_return):
+ CASE(OP_nip_catch):
{
JSValue ret_val;
- /* iter_obj next catch_offset ... ret_val ->
- ret_eval iter_obj next catch_offset */
+ /* catch_offset ... ret_val -> ret_eval */
ret_val = *--sp;
while (sp > stack_buf &&
JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_CATCH_OFFSET) {
JS_FreeValue(ctx, *--sp);
}
- if (unlikely(sp < stack_buf + 3)) {
- JS_ThrowInternalError(ctx, "iterator_close_return");
+ if (unlikely(sp == stack_buf)) {
+ JS_ThrowInternalError(ctx, "nip_catch");
JS_FreeValue(ctx, ret_val);
goto exception;
}
- sp[0] = sp[-1];
- sp[-1] = sp[-2];
- sp[-2] = sp[-3];
- sp[-3] = ret_val;
- sp++;
+ sp[-1] = ret_val;
}
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)
{
BlockEnv *top;
- int drop_count;
if (s->cur_func->func_kind != JS_FUNC_NORMAL) {
if (!hasval) {
@@ -25452,59 +25447,48 @@ static void emit_return(JSParseState *s, BOOL hasval)
}
}
- drop_count = 0;
top = s->cur_func->top_break;
while (top != NULL) {
- /* XXX: emit the appropriate OP_leave_scope opcodes? Probably not
- 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 (top->has_iterator || top->label_finally != -1) {
if (!hasval) {
emit_op(s, OP_undefined);
hasval = TRUE;
}
- emit_op(s, OP_iterator_close_return);
- if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) {
- int label_next, label_next2;
-
- emit_op(s, OP_drop); /* catch offset */
- emit_op(s, OP_drop); /* next */
- 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);
+ /* 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) {
+ 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);
+ }
} 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;
}
@@ -31901,6 +31885,7 @@ typedef struct StackSizeState {
int bc_len;
int stack_len_max;
uint16_t *stack_level_tab;
+ int32_t *catch_pos_tab;
int *pc_stack;
int pc_stack_len;
int pc_stack_size;
@@ -31908,7 +31893,7 @@ typedef struct StackSizeState {
/* 'op' is only used for error indication */
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) {
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)",
s->stack_level_tab[pos], stack_len, pos);
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 {
return 0;
}
@@ -31934,7 +31923,8 @@ static __exception int ss_check(JSContext *ctx, StackSizeState *s,
/* mark as explored and store the stack size */
s->stack_level_tab[pos] = stack_len;
-
+ s->catch_pos_tab[pos] = catch_pos;
+
/* queue the new PC to explore */
if (js_resize_array(ctx, (void **)&s->pc_stack, sizeof(s->pc_stack[0]),
&s->pc_stack_size, s->pc_stack_len + 1))
@@ -31948,7 +31938,7 @@ static __exception int compute_stack_size(JSContext *ctx,
int *pstack_size)
{
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 uint8_t *bc_buf;
@@ -31961,24 +31951,33 @@ static __exception int compute_stack_size(JSContext *ctx,
return -1;
for(i = 0; i < s->bc_len; i++)
s->stack_level_tab[i] = 0xffff;
- s->stack_len_max = 0;
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_size = 0;
/* 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;
while (s->pc_stack_len > 0) {
pos = s->pc_stack[--s->pc_stack_len];
stack_len = s->stack_level_tab[pos];
+ catch_pos = s->catch_pos_tab[pos];
op = bc_buf[pos];
if (op == 0 || op >= OP_COUNT) {
JS_ThrowInternalError(ctx, "invalid opcode (op=%d, pc=%d)", op, pos);
goto fail;
}
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;
if (pos_next > s->bc_len) {
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_false8:
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;
break;
#endif
case OP_if_true:
case OP_if_false:
- case OP_catch:
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;
break;
case OP_gosub:
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;
break;
case OP_with_get_var:
case OP_with_delete_var:
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;
break;
case OP_with_make_ref:
case OP_with_get_ref:
case OP_with_get_ref_undef:
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;
break;
case OP_with_put_var:
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;
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:
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;
done_insn: ;
}
- js_free(ctx, s->stack_level_tab);
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;
return 0;
fail:
- js_free(ctx, s->stack_level_tab);
js_free(ctx, s->pc_stack);
+ js_free(ctx, s->catch_pos_tab);
+ js_free(ctx, s->stack_level_tab);
*pstack_size = 0;
return -1;
}
diff --git a/tests/test_builtin.js b/tests/test_builtin.js
index c5c0f83..0b310c0 100644
--- a/tests/test_builtin.js
+++ b/tests/test_builtin.js
@@ -645,6 +645,18 @@ function test_generator()
assert(ret, "ret_val");
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;
g = f();
v = g.next();
@@ -665,6 +677,12 @@ function test_generator()
assert(v.value === 3 && v.done === true);
v = g.next();
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();