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 commit: 7da91bceb9ce7613f87db47ddd1292a2dda58b42 (es5-tests branch)
Result: 16/76909 errors, 1497 excluded, 8136 skipped
Result: 18/76947 errors, 1497 excluded, 8117 skipped
Test262 commit: 6cbb6da9473c56d95358d8e679c5a6d2b4574efb

View File

@ -256,6 +256,7 @@ DEF( and, 1, 2, 1, none)
DEF( xor, 1, 2, 1, none)
DEF( or, 1, 2, 1, none)
DEF(is_undefined_or_null, 1, 1, 1, none)
DEF( private_in, 1, 2, 1, none)
#ifdef CONFIG_BIGNUM
DEF( mul_pow10, 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_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( 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 */

234
quickjs.c
View File

@ -7351,6 +7351,8 @@ static int JS_SetPrivateField(JSContext *ctx, JSValueConst obj,
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)
{
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);
prs = find_own_property(&pr, p, JS_ATOM_Private_brand);
if (!prs) {
/* if the brand is not present, add it */
brand = JS_NewSymbolFromAtom(ctx, JS_ATOM_brand, JS_ATOM_TYPE_PRIVATE);
if (JS_IsException(brand))
return -1;
/* if the brand is not present, add it */
pr = add_property(ctx, p, JS_ATOM_Private_brand, JS_PROP_C_W_E);
if (!pr) {
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);
if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) {
JS_ThrowTypeErrorNotAnObject(ctx);
if (JS_IsObject(obj)) {
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);
return -1;
}
p1 = JS_VALUE_GET_OBJ(obj);
prs = find_own_property(&pr, p1, brand_atom);
if (unlikely(prs)) {
if (!pr)
return -1;
pr->u.value = JS_UNDEFINED;
} else {
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 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)
{
JSObject *p, *p1, *home_obj;
@ -7409,11 +7412,8 @@ static int JS_CheckBrand(JSContext *ctx, JSValueConst obj, JSValueConst func)
JSValueConst brand;
/* get the home object of 'func' */
if (unlikely(JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)) {
not_obj:
JS_ThrowTypeErrorNotAnObject(ctx);
return -1;
}
if (unlikely(JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT))
goto not_obj;
p1 = JS_VALUE_GET_OBJ(func);
if (!js_class_has_bytecode(p1->class_id))
goto not_obj;
@ -7431,15 +7431,14 @@ static int JS_CheckBrand(JSContext *ctx, JSValueConst obj, JSValueConst func)
goto not_obj;
/* get the brand array of 'obj' */
if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT))
goto not_obj;
p = JS_VALUE_GET_OBJ(obj);
prs = find_own_property(&pr, p, js_symbol_to_atom(ctx, (JSValue)brand));
if (!prs) {
JS_ThrowTypeError(ctx, "invalid brand on object");
if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) {
not_obj:
JS_ThrowTypeErrorNotAnObject(ctx);
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,
@ -14498,6 +14497,43 @@ static __exception int js_operator_in(JSContext *ctx, JSValue *sp)
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,
JSAtom atom)
{
@ -16500,8 +16536,15 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
}
BREAK;
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;
CASE(OP_add_brand):
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;
sp--;
BREAK;
CASE(OP_private_in):
if (js_operator_private_in(ctx, sp))
goto exception;
sp--;
BREAK;
CASE(OP_instanceof):
if (js_operator_instanceof(ctx, sp))
goto exception;
@ -22642,8 +22690,9 @@ static JSAtom get_private_setter_name(JSContext *ctx, JSAtom name)
typedef struct {
JSFunctionDef *fields_init_fd;
int computed_fields_count;
BOOL has_brand;
BOOL need_brand;
int brand_push_pos;
BOOL is_static;
} ClassFieldsDef;
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;
/* XXX: would be better to add the code only if needed, maybe in a
later pass */
emit_op(s, OP_push_false); /* will be patched later */
cf->brand_push_pos = cf->fields_init_fd->last_opcode_pos;
label_add_brand = emit_goto(s, OP_if_false, -1);
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;
}
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;
if (!cf->is_static) {
/* add the brand to the newly created instance */
/* XXX: would be better to add the code only if needed, maybe in a
later pass */
emit_op(s, OP_push_false); /* will be patched later */
cf->brand_push_pos = cf->fields_init_fd->last_opcode_pos;
label_add_brand = emit_goto(s, OP_if_false, -1);
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;
}
static void emit_class_init_end(JSParseState *s, ClassFieldsDef *cf)
{
int cpool_idx;
s->cur_func = cf->fields_init_fd;
emit_op(s, OP_return_undef);
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 *start_ptr;
ClassFieldsDef class_fields[2];
/* classes are parsed and executed in strict mode */
saved_js_mode = fd->js_mode;
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];
cf->fields_init_fd = NULL;
cf->computed_fields_count = 0;
cf->has_brand = FALSE;
cf->need_brand = FALSE;
cf->is_static = i;
}
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)
goto fail;
}
if (add_brand(s, &class_fields[is_static]) < 0)
goto fail;
class_fields[is_static].need_brand = TRUE;
}
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;
}
if (is_private) {
if (add_brand(s, &class_fields[is_static]) < 0)
goto fail;
class_fields[is_static].need_brand = TRUE;
}
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;
@ -23120,12 +23154,29 @@ static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr,
if (next_token(s))
goto fail;
/* store the function to initialize the fields to that it can be
referenced by the constructor */
{
ClassFieldsDef *cf = &class_fields[0];
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,
JS_VAR_DEF_CONST);
if (var_idx < 0)
@ -23143,6 +23194,13 @@ static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr,
/* drop the prototype */
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 */
if (class_fields[1].fields_init_fd != NULL) {
ClassFieldsDef *cf = &class_fields[1];
@ -25013,9 +25071,32 @@ static __exception int js_parse_expr_binary(JSParseState *s, int level,
if (level == 0) {
return js_parse_unary(s, (parse_flags & PF_ARROW_FUNC) |
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(;;) {
op = s->token.val;
switch(level) {
@ -30507,6 +30588,10 @@ static int resolve_scope_private_field(JSContext *ctx, JSFunctionDef *s,
abort();
}
break;
case OP_scope_in_private_field:
get_loc_or_ref(bc, is_ref, idx);
dbuf_putc(bc, OP_private_in);
break;
default:
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_field2:
case OP_scope_put_private_field:
case OP_scope_in_private_field:
{
int ret;
var_name = get_u32(bc_buf + pos + 1);

View File

@ -76,7 +76,7 @@ caller
change-array-by-copy
class
class-fields-private
class-fields-private-in=skip
class-fields-private-in
class-fields-public
class-methods-private
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/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/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