Support private brand checks

Fixes: https://github.com/quickjs-ng/quickjs/issues/541
Ref: 3ab1c2b314
This commit is contained in:
Saúl Ibarra Corretgé 2024-09-24 13:04:45 +02:00
parent c963112349
commit 3a96b9cb6f
9 changed files with 159 additions and 71 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -262,6 +262,7 @@ DEF( neq, 1, 2, 1, none)
DEF( strict_eq, 1, 2, 1, none)
DEF( strict_neq, 1, 2, 1, none)
DEF(is_undefined_or_null, 1, 1, 1, none)
DEF( private_in, 1, 2, 1, none)
/* must be the last non short and non temporary opcode */
DEF( nop, 1, 0, 0, none)
@ -282,6 +283,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(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 */

174
quickjs.c
View file

@ -7496,6 +7496,8 @@ static int JS_SetPrivateField(JSContext *ctx, JSValue 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, JSValue obj, JSValue home_obj)
{
JSObject *p, *p1;
@ -7511,10 +7513,10 @@ static int JS_AddBrand(JSContext *ctx, JSValue obj, JSValue 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);
@ -7526,11 +7528,7 @@ static int JS_AddBrand(JSContext *ctx, JSValue obj, JSValue home_obj)
}
brand_atom = js_symbol_to_atom(ctx, brand);
if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) {
JS_ThrowTypeErrorNotAnObject(ctx);
JS_FreeAtom(ctx, brand_atom);
return -1;
}
if (JS_IsObject(obj)) {
p1 = JS_VALUE_GET_OBJ(obj);
prs = find_own_property(&pr, p1, brand_atom);
if (unlikely(prs)) {
@ -7543,9 +7541,15 @@ static int JS_AddBrand(JSContext *ctx, JSValue obj, JSValue home_obj)
if (!pr)
return -1;
pr->u.value = JS_UNDEFINED;
} else {
JS_FreeAtom(ctx, brand_atom);
}
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, JSValue obj, JSValue func)
{
JSObject *p, *p1, *home_obj;
@ -7554,11 +7558,8 @@ static int JS_CheckBrand(JSContext *ctx, JSValue obj, JSValue func)
JSValue 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;
@ -7576,15 +7577,14 @@ static int JS_CheckBrand(JSContext *ctx, JSValue obj, JSValue 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, 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, brand));
return (prs != NULL);
}
static uint32_t js_string_obj_get_length(JSContext *ctx,
@ -13217,6 +13217,41 @@ 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, JSValue obj,
JSAtom atom)
{
@ -15222,8 +15257,15 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj,
}
BREAK;
CASE(OP_check_brand):
if (JS_CheckBrand(ctx, sp[-2], sp[-1]) < 0)
{
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)
@ -16939,6 +16981,11 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj,
goto exception;
sp--;
BREAK;
CASE(OP_private_in):
if (js_operator_private_in(ctx, sp))
goto exception;
sp--;
BREAK;
CASE(OP_instanceof):
sf->cur_pc = pc;
if (js_operator_instanceof(ctx, sp))
@ -21513,8 +21560,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,
@ -21528,6 +21576,8 @@ static __exception int emit_class_init_start(JSParseState *s,
s->cur_func = cf->fields_init_fd;
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 */
@ -21545,27 +21595,11 @@ static __exception int emit_class_init_start(JSParseState *s,
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;
}
return 0;
}
static void emit_class_init_end(JSParseState *s, ClassFieldsDef *cf)
{
int cpool_idx;
@ -21764,7 +21798,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;
@ -21873,8 +21908,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,
@ -22031,8 +22065,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,
@ -22099,12 +22132,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)
@ -22122,6 +22172,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];
@ -23969,9 +24026,31 @@ static __exception int js_parse_expr_binary(JSParseState *s, int level,
if (level == 0) {
return js_parse_unary(s, 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)) {
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;
}
for(;;) {
op = s->token.val;
switch(level) {
@ -29577,6 +29656,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();
}
@ -30285,6 +30368,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);
@ -33331,7 +33415,7 @@ typedef enum BCTagEnum {
BC_TAG_SYMBOL,
} BCTagEnum;
#define BC_VERSION 15
#define BC_VERSION 16
typedef struct BCWriterState {
JSContext *ctx;

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

@ -88,6 +88,8 @@ test262/test/language/expressions/function/static-init-await-binding.js:16: Synt
test262/test/language/expressions/function/static-init-await-binding.js:16: strict mode: SyntaxError: 'await' is a reserved identifier
test262/test/language/expressions/generators/static-init-await-binding.js:16: SyntaxError: 'await' is a reserved identifier
test262/test/language/expressions/generators/static-init-await-binding.js:16: strict mode: SyntaxError: 'await' is a reserved identifier
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/member-expression/computed-reference-null-or-undefined.js:28: Test262Error: Expected a TypeError but got a Test262Error
test262/test/language/expressions/member-expression/computed-reference-null-or-undefined.js:28: strict mode: Test262Error: Expected a TypeError but got a Test262Error
test262/test/language/module-code/top-level-await/async-module-does-not-block-sibling-modules.js:13: SyntaxError: Could not find export 'check' in module 'test262/test/language/module-code/top-level-await/async-module-sync_FIXTURE.js'