optional chaining fixes (github issue #103)

This commit is contained in:
Fabrice Bellard 2024-01-09 19:15:40 +01:00
parent e1e65aca91
commit f25e5d4094
4 changed files with 137 additions and 9 deletions

View file

@ -286,6 +286,8 @@ def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phas
def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in phase 1, removed in phase 2 */
def(scope_put_private_field, 7, 2, 0, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */
def(scope_in_private_field, 7, 1, 1, atom_u16) /* obj -> res emitted in phase 1, removed in phase 2 */
def(get_field_opt_chain, 5, 1, 1, atom) /* emitted in phase 1, removed in phase 2 */
def(get_array_el_opt_chain, 1, 2, 1, none) /* emitted in phase 1, removed in phase 2 */
def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */
def( line_num, 5, 0, 0, u32) /* emitted in phase 1, removed in phase 3 */

116
quickjs.c
View file

@ -21530,6 +21530,14 @@ static int new_label(JSParseState *s)
return new_label_fd(s->cur_func, -1);
}
/* don't update the last opcode and don't emit line number info */
static void emit_label_raw(JSParseState *s, int label)
{
emit_u8(s, OP_label);
emit_u32(s, label);
s->cur_func->label_slots[label].pos = s->cur_func->byte_code.size;
}
/* return the label ID offset */
static int emit_label(JSParseState *s, int label)
{
@ -24643,6 +24651,25 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags)
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2;
drop_count = 2;
break;
case OP_get_field_opt_chain:
{
int opt_chain_label, next_label;
opt_chain_label = get_u32(fd->byte_code.buf +
fd->last_opcode_pos + 1 + 4 + 1);
/* keep the object on the stack */
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2;
fd->byte_code.size = fd->last_opcode_pos + 1 + 4;
next_label = emit_goto(s, OP_goto, -1);
emit_label(s, opt_chain_label);
/* need an additional undefined value for the
case where the optional field does not
exists */
emit_op(s, OP_undefined);
emit_label(s, next_label);
drop_count = 2;
opcode = OP_get_field;
}
break;
case OP_scope_get_private_field:
/* keep the object on the stack */
fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_get_private_field2;
@ -24653,6 +24680,25 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags)
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2;
drop_count = 2;
break;
case OP_get_array_el_opt_chain:
{
int opt_chain_label, next_label;
opt_chain_label = get_u32(fd->byte_code.buf +
fd->last_opcode_pos + 1 + 1);
/* keep the object on the stack */
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2;
fd->byte_code.size = fd->last_opcode_pos + 1;
next_label = emit_goto(s, OP_goto, -1);
emit_label(s, opt_chain_label);
/* need an additional undefined value for the
case where the optional field does not
exists */
emit_op(s, OP_undefined);
emit_label(s, next_label);
drop_count = 2;
opcode = OP_get_array_el;
}
break;
case OP_scope_get_var:
{
JSAtom name;
@ -24935,8 +24981,23 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags)
break;
}
}
if (optional_chaining_label >= 0)
emit_label(s, optional_chaining_label);
if (optional_chaining_label >= 0) {
JSFunctionDef *fd = s->cur_func;
int opcode;
emit_label_raw(s, optional_chaining_label);
/* modify the last opcode so that it is an indicator of an
optional chain */
opcode = get_prev_opcode(fd);
if (opcode == OP_get_field || opcode == OP_get_array_el) {
if (opcode == OP_get_field)
opcode = OP_get_field_opt_chain;
else
opcode = OP_get_array_el_opt_chain;
fd->byte_code.buf[fd->last_opcode_pos] = opcode;
} else {
fd->last_opcode_pos = -1;
}
}
return 0;
}
@ -24952,27 +25013,57 @@ static __exception int js_parse_delete(JSParseState *s)
return -1;
switch(opcode = get_prev_opcode(fd)) {
case OP_get_field:
case OP_get_field_opt_chain:
{
JSValue val;
int ret;
int ret, opt_chain_label, next_label;
if (opcode == OP_get_field_opt_chain) {
opt_chain_label = get_u32(fd->byte_code.buf +
fd->last_opcode_pos + 1 + 4 + 1);
} else {
opt_chain_label = -1;
}
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
fd->byte_code.size = fd->last_opcode_pos;
fd->last_opcode_pos = -1;
val = JS_AtomToValue(s->ctx, name);
ret = emit_push_const(s, val, 1);
JS_FreeValue(s->ctx, val);
JS_FreeAtom(s->ctx, name);
if (ret)
return ret;
emit_op(s, OP_delete);
if (opt_chain_label >= 0) {
next_label = emit_goto(s, OP_goto, -1);
emit_label(s, opt_chain_label);
/* if the optional chain is not taken, return 'true' */
emit_op(s, OP_drop);
emit_op(s, OP_push_true);
emit_label(s, next_label);
}
fd->last_opcode_pos = -1;
}
goto do_delete;
break;
case OP_get_array_el:
fd->byte_code.size = fd->last_opcode_pos;
fd->last_opcode_pos = -1;
do_delete:
emit_op(s, OP_delete);
break;
case OP_get_array_el_opt_chain:
{
int opt_chain_label, next_label;
opt_chain_label = get_u32(fd->byte_code.buf +
fd->last_opcode_pos + 1 + 1);
fd->byte_code.size = fd->last_opcode_pos;
emit_op(s, OP_delete);
next_label = emit_goto(s, OP_goto, -1);
emit_label(s, opt_chain_label);
/* if the optional chain is not taken, return 'true' */
emit_op(s, OP_drop);
emit_op(s, OP_push_true);
emit_label(s, next_label);
fd->last_opcode_pos = -1;
}
break;
case OP_scope_get_var:
/* 'delete this': this is not a reference */
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
@ -31607,6 +31698,17 @@ static __exception int resolve_variables(JSContext *ctx, JSFunctionDef *s)
/* only used during parsing */
break;
case OP_get_field_opt_chain: /* equivalent to OP_get_field */
{
JSAtom name = get_u32(bc_buf + pos + 1);
dbuf_putc(&bc_out, OP_get_field);
dbuf_put_u32(&bc_out, name);
}
break;
case OP_get_array_el_opt_chain: /* equivalent to OP_get_array_el */
dbuf_putc(&bc_out, OP_get_array_el);
break;
default:
no_change:
dbuf_put(&bc_out, bc_buf + pos, len);

View file

@ -9,6 +9,4 @@ test262/test/language/expressions/dynamic-import/usage-from-eval.js:26: TypeErro
test262/test/language/expressions/dynamic-import/usage-from-eval.js:26: strict mode: TypeError: $DONE() not called
test262/test/language/expressions/in/private-field-invalid-assignment-target.js:23: unexpected error type: Test262: This statement should not be evaluated.
test262/test/language/expressions/in/private-field-invalid-assignment-target.js:23: strict mode: unexpected error type: Test262: This statement should not be evaluated.
test262/test/language/expressions/optional-chaining/optional-call-preserves-this.js:21: TypeError: cannot read property 'c' of undefined
test262/test/language/expressions/optional-chaining/optional-call-preserves-this.js:15: strict mode: TypeError: cannot read property '_b' of undefined
test262/test/language/global-code/script-decl-lex-var-declared-via-eval-sloppy.js:13: Test262Error: variable Expected a SyntaxError to be thrown but no exception was thrown at all

View file

@ -558,6 +558,31 @@ function test_parse_semicolon()
}
}
/* optional chaining tests not present in test262 */
function test_optional_chaining()
{
var a, z;
z = null;
a = { b: { c: 2 } };
assert(delete z?.b.c, true);
assert(delete a?.b.c, true);
assert(JSON.stringify(a), '{"b":{}}', "optional chaining delete");
a = { b: { c: 2 } };
assert(delete z?.b["c"], true);
assert(delete a?.b["c"], true);
assert(JSON.stringify(a), '{"b":{}}');
a = {
b() { return this._b; },
_b: { c: 42 }
};
assert((a?.b)().c, 42);
assert((a?.["b"])().c, 42);
}
test_op1();
test_cvt();
test_eq();
@ -578,3 +603,4 @@ test_function_length();
test_argument_scope();
test_function_expr_name();
test_parse_semicolon();
test_optional_chaining();