added 'in' operator for private fields

This commit is contained in:
Fabrice Bellard 2024-01-02 16:11:20 +01:00
parent 16057644f3
commit 3ab1c2b314
5 changed files with 166 additions and 77 deletions

2
TODO
View file

@ -63,5 +63,5 @@ Optimization ideas:
Test262o: 0/11262 errors, 463 excluded Test262o: 0/11262 errors, 463 excluded
Test262o commit: 7da91bceb9ce7613f87db47ddd1292a2dda58b42 (es5-tests branch) Test262o commit: 7da91bceb9ce7613f87db47ddd1292a2dda58b42 (es5-tests branch)
Result: 16/76909 errors, 1497 excluded, 8136 skipped Result: 18/76947 errors, 1497 excluded, 8117 skipped
Test262 commit: 6cbb6da9473c56d95358d8e679c5a6d2b4574efb Test262 commit: 6cbb6da9473c56d95358d8e679c5a6d2b4574efb

View file

@ -256,6 +256,7 @@ DEF( and, 1, 2, 1, none)
DEF( xor, 1, 2, 1, none) DEF( xor, 1, 2, 1, none)
DEF( or, 1, 2, 1, none) DEF( or, 1, 2, 1, none)
DEF(is_undefined_or_null, 1, 1, 1, none) DEF(is_undefined_or_null, 1, 1, 1, none)
DEF( private_in, 1, 2, 1, none)
#ifdef CONFIG_BIGNUM #ifdef CONFIG_BIGNUM
DEF( mul_pow10, 1, 2, 1, none) DEF( mul_pow10, 1, 2, 1, none)
DEF( math_mod, 1, 2, 1, none) DEF( math_mod, 1, 2, 1, none)
@ -280,7 +281,7 @@ def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in pha
def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phase 1, removed in phase 2 */ def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phase 1, removed in phase 2 */
def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in phase 1, removed in phase 2 */ 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_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( set_class_name, 5, 1, 1, u32) /* 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 */ def( line_num, 5, 0, 0, u32) /* emitted in phase 1, removed in phase 3 */

234
quickjs.c
View file

@ -7351,6 +7351,8 @@ static int JS_SetPrivateField(JSContext *ctx, JSValueConst obj,
return 0; return 0;
} }
/* add a private brand field to 'home_obj' if not already present and
if obj is != null add a private brand to it */
static int JS_AddBrand(JSContext *ctx, JSValueConst obj, JSValueConst home_obj) static int JS_AddBrand(JSContext *ctx, JSValueConst obj, JSValueConst home_obj)
{ {
JSObject *p, *p1; JSObject *p, *p1;
@ -7366,10 +7368,10 @@ static int JS_AddBrand(JSContext *ctx, JSValueConst obj, JSValueConst home_obj)
p = JS_VALUE_GET_OBJ(home_obj); p = JS_VALUE_GET_OBJ(home_obj);
prs = find_own_property(&pr, p, JS_ATOM_Private_brand); prs = find_own_property(&pr, p, JS_ATOM_Private_brand);
if (!prs) { if (!prs) {
/* if the brand is not present, add it */
brand = JS_NewSymbolFromAtom(ctx, JS_ATOM_brand, JS_ATOM_TYPE_PRIVATE); brand = JS_NewSymbolFromAtom(ctx, JS_ATOM_brand, JS_ATOM_TYPE_PRIVATE);
if (JS_IsException(brand)) if (JS_IsException(brand))
return -1; return -1;
/* if the brand is not present, add it */
pr = add_property(ctx, p, JS_ATOM_Private_brand, JS_PROP_C_W_E); pr = add_property(ctx, p, JS_ATOM_Private_brand, JS_PROP_C_W_E);
if (!pr) { if (!pr) {
JS_FreeValue(ctx, brand); JS_FreeValue(ctx, brand);
@ -7381,26 +7383,27 @@ static int JS_AddBrand(JSContext *ctx, JSValueConst obj, JSValueConst home_obj)
} }
brand_atom = js_symbol_to_atom(ctx, brand); brand_atom = js_symbol_to_atom(ctx, brand);
if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { if (JS_IsObject(obj)) {
JS_ThrowTypeErrorNotAnObject(ctx); p1 = JS_VALUE_GET_OBJ(obj);
prs = find_own_property(&pr, p1, brand_atom);
if (unlikely(prs)) {
JS_FreeAtom(ctx, brand_atom);
JS_ThrowTypeError(ctx, "private method is already present");
return -1;
}
pr = add_property(ctx, p1, brand_atom, JS_PROP_C_W_E);
JS_FreeAtom(ctx, brand_atom); JS_FreeAtom(ctx, brand_atom);
return -1; if (!pr)
} return -1;
p1 = JS_VALUE_GET_OBJ(obj); pr->u.value = JS_UNDEFINED;
prs = find_own_property(&pr, p1, brand_atom); } else {
if (unlikely(prs)) {
JS_FreeAtom(ctx, brand_atom); JS_FreeAtom(ctx, brand_atom);
JS_ThrowTypeError(ctx, "private method is already present");
return -1;
} }
pr = add_property(ctx, p1, brand_atom, JS_PROP_C_W_E);
JS_FreeAtom(ctx, brand_atom);
if (!pr)
return -1;
pr->u.value = JS_UNDEFINED;
return 0; return 0;
} }
/* return a boolean telling if the brand of the home object of 'func'
is present on 'obj' or -1 in case of exception */
static int JS_CheckBrand(JSContext *ctx, JSValueConst obj, JSValueConst func) static int JS_CheckBrand(JSContext *ctx, JSValueConst obj, JSValueConst func)
{ {
JSObject *p, *p1, *home_obj; JSObject *p, *p1, *home_obj;
@ -7409,11 +7412,8 @@ static int JS_CheckBrand(JSContext *ctx, JSValueConst obj, JSValueConst func)
JSValueConst brand; JSValueConst brand;
/* get the home object of 'func' */ /* get the home object of 'func' */
if (unlikely(JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)) { if (unlikely(JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT))
not_obj: goto not_obj;
JS_ThrowTypeErrorNotAnObject(ctx);
return -1;
}
p1 = JS_VALUE_GET_OBJ(func); p1 = JS_VALUE_GET_OBJ(func);
if (!js_class_has_bytecode(p1->class_id)) if (!js_class_has_bytecode(p1->class_id))
goto not_obj; goto not_obj;
@ -7431,15 +7431,14 @@ static int JS_CheckBrand(JSContext *ctx, JSValueConst obj, JSValueConst func)
goto not_obj; goto not_obj;
/* get the brand array of 'obj' */ /* get the brand array of 'obj' */
if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) {
goto not_obj; not_obj:
p = JS_VALUE_GET_OBJ(obj); JS_ThrowTypeErrorNotAnObject(ctx);
prs = find_own_property(&pr, p, js_symbol_to_atom(ctx, (JSValue)brand));
if (!prs) {
JS_ThrowTypeError(ctx, "invalid brand on object");
return -1; return -1;
} }
return 0; p = JS_VALUE_GET_OBJ(obj);
prs = find_own_property(&pr, p, js_symbol_to_atom(ctx, (JSValue)brand));
return (prs != NULL);
} }
static uint32_t js_string_obj_get_length(JSContext *ctx, static uint32_t js_string_obj_get_length(JSContext *ctx,
@ -14498,6 +14497,43 @@ static __exception int js_operator_in(JSContext *ctx, JSValue *sp)
return 0; return 0;
} }
static __exception int js_operator_private_in(JSContext *ctx, JSValue *sp)
{
JSValue op1, op2;
int ret;
op1 = sp[-2]; /* object */
op2 = sp[-1]; /* field name or method function */
if (JS_VALUE_GET_TAG(op1) != JS_TAG_OBJECT) {
JS_ThrowTypeError(ctx, "invalid 'in' operand");
return -1;
}
if (JS_IsObject(op2)) {
/* method: use the brand */
ret = JS_CheckBrand(ctx, op1, op2);
if (ret < 0)
return -1;
} else {
JSAtom atom;
JSObject *p;
JSShapeProperty *prs;
JSProperty *pr;
/* field */
atom = JS_ValueToAtom(ctx, op2);
if (unlikely(atom == JS_ATOM_NULL))
return -1;
p = JS_VALUE_GET_OBJ(op1);
prs = find_own_property(&pr, p, atom);
JS_FreeAtom(ctx, atom);
ret = (prs != NULL);
}
JS_FreeValue(ctx, op1);
JS_FreeValue(ctx, op2);
sp[-2] = JS_NewBool(ctx, ret);
return 0;
}
static __exception int js_has_unscopable(JSContext *ctx, JSValueConst obj, static __exception int js_has_unscopable(JSContext *ctx, JSValueConst obj,
JSAtom atom) JSAtom atom)
{ {
@ -16500,8 +16536,15 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
} }
BREAK; BREAK;
CASE(OP_check_brand): CASE(OP_check_brand):
if (JS_CheckBrand(ctx, sp[-2], sp[-1]) < 0) {
goto exception; int ret = JS_CheckBrand(ctx, sp[-2], sp[-1]);
if (ret < 0)
goto exception;
if (!ret) {
JS_ThrowTypeError(ctx, "invalid brand on object");
goto exception;
}
}
BREAK; BREAK;
CASE(OP_add_brand): CASE(OP_add_brand):
if (JS_AddBrand(ctx, sp[-2], sp[-1]) < 0) if (JS_AddBrand(ctx, sp[-2], sp[-1]) < 0)
@ -18156,6 +18199,11 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
goto exception; goto exception;
sp--; sp--;
BREAK; BREAK;
CASE(OP_private_in):
if (js_operator_private_in(ctx, sp))
goto exception;
sp--;
BREAK;
CASE(OP_instanceof): CASE(OP_instanceof):
if (js_operator_instanceof(ctx, sp)) if (js_operator_instanceof(ctx, sp))
goto exception; goto exception;
@ -22642,8 +22690,9 @@ static JSAtom get_private_setter_name(JSContext *ctx, JSAtom name)
typedef struct { typedef struct {
JSFunctionDef *fields_init_fd; JSFunctionDef *fields_init_fd;
int computed_fields_count; int computed_fields_count;
BOOL has_brand; BOOL need_brand;
int brand_push_pos; int brand_push_pos;
BOOL is_static;
} ClassFieldsDef; } ClassFieldsDef;
static __exception int emit_class_init_start(JSParseState *s, static __exception int emit_class_init_start(JSParseState *s,
@ -22657,48 +22706,34 @@ static __exception int emit_class_init_start(JSParseState *s,
s->cur_func = cf->fields_init_fd; s->cur_func = cf->fields_init_fd;
/* XXX: would be better to add the code only if needed, maybe in a if (!cf->is_static) {
later pass */ /* add the brand to the newly created instance */
emit_op(s, OP_push_false); /* will be patched later */ /* XXX: would be better to add the code only if needed, maybe in a
cf->brand_push_pos = cf->fields_init_fd->last_opcode_pos; later pass */
label_add_brand = emit_goto(s, OP_if_false, -1); emit_op(s, OP_push_false); /* will be patched later */
cf->brand_push_pos = cf->fields_init_fd->last_opcode_pos;
emit_op(s, OP_scope_get_var); label_add_brand = emit_goto(s, OP_if_false, -1);
emit_atom(s, JS_ATOM_this);
emit_u16(s, 0);
emit_op(s, OP_scope_get_var);
emit_atom(s, JS_ATOM_home_object);
emit_u16(s, 0);
emit_op(s, OP_add_brand);
emit_label(s, label_add_brand);
s->cur_func = s->cur_func->parent;
return 0;
}
static __exception int add_brand(JSParseState *s, ClassFieldsDef *cf)
{
if (!cf->has_brand) {
/* define the brand field in 'this' of the initializer */
if (!cf->fields_init_fd) {
if (emit_class_init_start(s, cf))
return -1;
}
/* patch the start of the function to enable the OP_add_brand code */
cf->fields_init_fd->byte_code.buf[cf->brand_push_pos] = OP_push_true;
cf->has_brand = TRUE; emit_op(s, OP_scope_get_var);
emit_atom(s, JS_ATOM_this);
emit_u16(s, 0);
emit_op(s, OP_scope_get_var);
emit_atom(s, JS_ATOM_home_object);
emit_u16(s, 0);
emit_op(s, OP_add_brand);
emit_label(s, label_add_brand);
} }
s->cur_func = s->cur_func->parent;
return 0; return 0;
} }
static void emit_class_init_end(JSParseState *s, ClassFieldsDef *cf) static void emit_class_init_end(JSParseState *s, ClassFieldsDef *cf)
{ {
int cpool_idx; int cpool_idx;
s->cur_func = cf->fields_init_fd; s->cur_func = cf->fields_init_fd;
emit_op(s, OP_return_undef); emit_op(s, OP_return_undef);
s->cur_func = s->cur_func->parent; s->cur_func = s->cur_func->parent;
@ -22725,7 +22760,7 @@ static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr,
const uint8_t *class_start_ptr = s->token.ptr; const uint8_t *class_start_ptr = s->token.ptr;
const uint8_t *start_ptr; const uint8_t *start_ptr;
ClassFieldsDef class_fields[2]; ClassFieldsDef class_fields[2];
/* classes are parsed and executed in strict mode */ /* classes are parsed and executed in strict mode */
saved_js_mode = fd->js_mode; saved_js_mode = fd->js_mode;
fd->js_mode |= JS_MODE_STRICT; fd->js_mode |= JS_MODE_STRICT;
@ -22798,7 +22833,8 @@ static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr,
ClassFieldsDef *cf = &class_fields[i]; ClassFieldsDef *cf = &class_fields[i];
cf->fields_init_fd = NULL; cf->fields_init_fd = NULL;
cf->computed_fields_count = 0; cf->computed_fields_count = 0;
cf->has_brand = FALSE; cf->need_brand = FALSE;
cf->is_static = i;
} }
ctor_fd = NULL; ctor_fd = NULL;
@ -22905,8 +22941,7 @@ static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr,
JS_VAR_PRIVATE_GETTER + is_set, is_static) < 0) JS_VAR_PRIVATE_GETTER + is_set, is_static) < 0)
goto fail; goto fail;
} }
if (add_brand(s, &class_fields[is_static]) < 0) class_fields[is_static].need_brand = TRUE;
goto fail;
} }
if (js_parse_function_decl2(s, JS_PARSE_FUNC_GETTER + is_set, if (js_parse_function_decl2(s, JS_PARSE_FUNC_GETTER + is_set,
@ -23053,8 +23088,7 @@ static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr,
func_type = JS_PARSE_FUNC_CLASS_CONSTRUCTOR; func_type = JS_PARSE_FUNC_CLASS_CONSTRUCTOR;
} }
if (is_private) { if (is_private) {
if (add_brand(s, &class_fields[is_static]) < 0) class_fields[is_static].need_brand = TRUE;
goto fail;
} }
if (js_parse_function_decl2(s, func_type, func_kind, JS_ATOM_NULL, start_ptr, s->token.line_num, JS_PARSE_EXPORT_NONE, &method_fd)) if (js_parse_function_decl2(s, func_type, func_kind, JS_ATOM_NULL, start_ptr, s->token.line_num, JS_PARSE_EXPORT_NONE, &method_fd))
goto fail; goto fail;
@ -23120,12 +23154,29 @@ static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr,
if (next_token(s)) if (next_token(s))
goto fail; goto fail;
/* store the function to initialize the fields to that it can be
referenced by the constructor */
{ {
ClassFieldsDef *cf = &class_fields[0]; ClassFieldsDef *cf = &class_fields[0];
int var_idx; int var_idx;
if (cf->need_brand) {
/* add a private brand to the prototype */
emit_op(s, OP_dup);
emit_op(s, OP_null);
emit_op(s, OP_swap);
emit_op(s, OP_add_brand);
/* define the brand field in 'this' of the initializer */
if (!cf->fields_init_fd) {
if (emit_class_init_start(s, cf))
goto fail;
}
/* patch the start of the function to enable the
OP_add_brand_instance code */
cf->fields_init_fd->byte_code.buf[cf->brand_push_pos] = OP_push_true;
}
/* store the function to initialize the fields to that it can be
referenced by the constructor */
var_idx = define_var(s, fd, JS_ATOM_class_fields_init, var_idx = define_var(s, fd, JS_ATOM_class_fields_init,
JS_VAR_DEF_CONST); JS_VAR_DEF_CONST);
if (var_idx < 0) if (var_idx < 0)
@ -23143,6 +23194,13 @@ static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr,
/* drop the prototype */ /* drop the prototype */
emit_op(s, OP_drop); emit_op(s, OP_drop);
if (class_fields[1].need_brand) {
/* add a private brand to the class */
emit_op(s, OP_dup);
emit_op(s, OP_dup);
emit_op(s, OP_add_brand);
}
/* initialize the static fields */ /* initialize the static fields */
if (class_fields[1].fields_init_fd != NULL) { if (class_fields[1].fields_init_fd != NULL) {
ClassFieldsDef *cf = &class_fields[1]; ClassFieldsDef *cf = &class_fields[1];
@ -25013,9 +25071,32 @@ static __exception int js_parse_expr_binary(JSParseState *s, int level,
if (level == 0) { if (level == 0) {
return js_parse_unary(s, (parse_flags & PF_ARROW_FUNC) | return js_parse_unary(s, (parse_flags & PF_ARROW_FUNC) |
PF_POW_ALLOWED); PF_POW_ALLOWED);
} else if (s->token.val == TOK_PRIVATE_NAME &&
(parse_flags & PF_IN_ACCEPTED) && level == 4 &&
peek_token(s, FALSE) == TOK_IN) {
JSAtom atom;
atom = JS_DupAtom(s->ctx, s->token.u.ident.atom);
if (next_token(s))
goto fail_private_in;
if (s->token.val != TOK_IN)
goto fail_private_in;
if (next_token(s))
goto fail_private_in;
if (js_parse_expr_binary(s, level - 1, parse_flags & ~PF_ARROW_FUNC)) {
fail_private_in:
JS_FreeAtom(s->ctx, atom);
return -1;
}
emit_op(s, OP_scope_in_private_field);
emit_atom(s, atom);
emit_u16(s, s->cur_func->scope_level);
JS_FreeAtom(s->ctx, atom);
return 0;
} else {
if (js_parse_expr_binary(s, level - 1, parse_flags))
return -1;
} }
if (js_parse_expr_binary(s, level - 1, parse_flags))
return -1;
for(;;) { for(;;) {
op = s->token.val; op = s->token.val;
switch(level) { switch(level) {
@ -30507,6 +30588,10 @@ static int resolve_scope_private_field(JSContext *ctx, JSFunctionDef *s,
abort(); abort();
} }
break; break;
case OP_scope_in_private_field:
get_loc_or_ref(bc, is_ref, idx);
dbuf_putc(bc, OP_private_in);
break;
default: default:
abort(); abort();
} }
@ -31213,6 +31298,7 @@ static __exception int resolve_variables(JSContext *ctx, JSFunctionDef *s)
case OP_scope_get_private_field: case OP_scope_get_private_field:
case OP_scope_get_private_field2: case OP_scope_get_private_field2:
case OP_scope_put_private_field: case OP_scope_put_private_field:
case OP_scope_in_private_field:
{ {
int ret; int ret;
var_name = get_u32(bc_buf + pos + 1); var_name = get_u32(bc_buf + pos + 1);

View file

@ -76,7 +76,7 @@ caller
change-array-by-copy change-array-by-copy
class class
class-fields-private class-fields-private
class-fields-private-in=skip class-fields-private-in
class-fields-public class-fields-public
class-methods-private class-methods-private
class-static-block class-static-block

View file

@ -11,6 +11,8 @@ test262/test/language/expressions/assignment/target-member-computed-reference-un
test262/test/language/expressions/assignment/target-member-computed-reference-undefined.js:32: strict mode: Test262Error: Expected a DummyError but got a TypeError test262/test/language/expressions/assignment/target-member-computed-reference-undefined.js:32: strict mode: Test262Error: Expected a DummyError but got a TypeError
test262/test/language/expressions/dynamic-import/usage-from-eval.js:26: TypeError: $DONE() not called test262/test/language/expressions/dynamic-import/usage-from-eval.js:26: TypeError: $DONE() not called
test262/test/language/expressions/dynamic-import/usage-from-eval.js:26: strict mode: TypeError: $DONE() not called 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: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/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 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