MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add proper brand based private class support

+722 -40
+1
include/common.h
··· 27 27 X(SLOT_PROXY_REF) \ 28 28 X(SLOT_BUILTIN) \ 29 29 X(SLOT_BRAND) \ 30 + X(SLOT_PRIVATE_ELEMENTS) \ 30 31 X(SLOT_DATA) \ 31 32 X(SLOT_EVENT_MAX_LISTENERS) \ 32 33 X(SLOT_CTOR) \
+19
include/silver/compiler.h
··· 68 68 int count; 69 69 } sv_line_table_t; 70 70 71 + typedef struct { 72 + const char *name; 73 + uint32_t len; 74 + uint8_t kind; 75 + bool is_static; 76 + bool has_getter; 77 + bool has_setter; 78 + struct sv_compiler *owner; 79 + int local; 80 + } sv_private_name_t; 81 + 82 + typedef struct sv_private_scope { 83 + struct sv_private_scope *parent; 84 + sv_private_name_t *names; 85 + int count; 86 + int cap; 87 + } sv_private_scope_t; 88 + 71 89 typedef struct sv_compiler { 72 90 ant_t *js; 73 91 const char *filename; ··· 151 169 152 170 const_dedup_entry_t *const_dedup; 153 171 sv_line_table_t *line_table; 172 + sv_private_scope_t *private_scope; 154 173 } sv_compiler_t; 155 174 156 175
+5 -4
include/silver/opcode.h
··· 91 91 OP_DEF( GET_FIELD_OPT, 5, 1, 1, atom) /* null-safe obj -> val */ 92 92 OP_DEF( GET_ELEM_OPT, 1, 2, 1, none) /* null-safe obj key -> val */ 93 93 94 - OP_DEF( GET_PRIVATE, 1, 2, 1, none) /* obj prop -> value */ 95 - OP_DEF( PUT_PRIVATE, 1, 3, 0, none) /* obj value prop -> */ 96 - OP_DEF( DEF_PRIVATE, 1, 3, 1, none) /* obj prop value -> obj */ 94 + OP_DEF( GET_PRIVATE, 1, 2, 1, none) /* obj private_name -> value */ 95 + OP_DEF( GET_PRIVATE_OPT, 1, 2, 1, none) /* obj private_name -> value|undefined */ 96 + OP_DEF( PUT_PRIVATE, 1, 3, 1, none) /* obj value private_name -> value */ 97 + OP_DEF( DEF_PRIVATE, 2, 3, 1, u8) /* obj private_name value -> obj */ 98 + OP_DEF( HAS_PRIVATE, 1, 2, 1, none) /* obj private_name -> bool */ 97 99 98 100 OP_DEF( GET_SUPER, 1, 1, 1, none) /* obj -> super */ 99 101 OP_DEF( GET_SUPER_VAL, 1, 3, 1, none) /* this obj prop -> value */ ··· 229 231 230 232 OP_DEF( DEFINE_CLASS, 14, 2, 2, atom_u8) /* parent ctor -> ctor proto */ 231 233 OP_DEF( DEFINE_CLASS_COMP, 14, 3, 3, atom_u8) /* computed name variant */ 232 - OP_DEF( ADD_BRAND, 1, 2, 0, none) /* this_obj home_obj -> (private brand) */ 233 234 234 235 OP_DEF( TO_OBJECT, 1, 1, 1, none) /* coerce to object wrapper */ 235 236 OP_DEF( TO_PROPKEY, 1, 1, 1, none) /* coerce to string/symbol */
+2
src/silver/compile_ctx.c
··· 44 44 ctx->super_local = -1; 45 45 ctx->using_stack_local = -1; 46 46 ctx->line_table = line_table; 47 + ctx->private_scope = NULL; 47 48 } 48 49 49 50 void sv_compile_ctx_init_child( ··· 70 71 ctx->super_local = -1; 71 72 ctx->using_stack_local = -1; 72 73 ctx->param_count = node ? node->args.count : 0; 74 + ctx->private_scope = enclosing->private_scope; 73 75 } 74 76 75 77 void sv_compile_ctx_cleanup(sv_compiler_t *ctx) {
+310 -13
src/silver/compiler.c
··· 270 270 compile_expr(c, key); 271 271 } 272 272 273 + enum { 274 + SV_COMP_PRIVATE_FIELD = 0, 275 + SV_COMP_PRIVATE_METHOD = 1, 276 + SV_COMP_PRIVATE_GETTER = 3, 277 + SV_COMP_PRIVATE_SETTER = 4 278 + }; 279 + 280 + static int local_to_frame_slot(sv_compiler_t *c, int local_idx); 281 + static int add_upvalue(sv_compiler_t *c, uint16_t index, bool is_local, bool is_const); 282 + static void emit_get_local(sv_compiler_t *c, int local_idx); 283 + 284 + static inline bool is_private_name_node(const sv_ast_t *node) { 285 + return node && node->type == N_IDENT && node->str && node->len > 0 && node->str[0] == '#'; 286 + } 287 + 288 + static sv_private_name_t *private_scope_find_current( 289 + sv_private_scope_t *scope, const char *name, uint32_t len 290 + ) { 291 + if (!scope || !name) return NULL; 292 + for (int i = 0; i < scope->count; i++) { 293 + sv_private_name_t *p = &scope->names[i]; 294 + if (p->len == len && memcmp(p->name, name, len) == 0) return p; 295 + } 296 + return NULL; 297 + } 298 + 299 + static sv_private_name_t *private_scope_resolve( 300 + sv_compiler_t *c, const char *name, uint32_t len 301 + ) { 302 + for (sv_private_scope_t *scope = c->private_scope; scope; scope = scope->parent) { 303 + sv_private_name_t *p = private_scope_find_current(scope, name, len); 304 + if (p) return p; 305 + } 306 + return NULL; 307 + } 308 + 309 + static bool private_scope_add( 310 + sv_compiler_t *c, sv_private_scope_t *scope, 311 + sv_ast_t *name, uint8_t kind, bool is_static 312 + ) { 313 + if (!is_private_name_node(name)) return true; 314 + 315 + if (name->len == 12 && memcmp(name->str, "#constructor", 12) == 0) { 316 + js_mkerr_typed(c->js, JS_ERR_SYNTAX, "Classes may not declare private constructor names"); 317 + return false; 318 + } 319 + 320 + sv_private_name_t *existing = private_scope_find_current(scope, name->str, name->len); 321 + bool is_accessor = kind == SV_COMP_PRIVATE_GETTER || kind == SV_COMP_PRIVATE_SETTER; 322 + if (existing) { 323 + bool existing_accessor = existing->kind == SV_COMP_PRIVATE_GETTER || 324 + existing->kind == SV_COMP_PRIVATE_SETTER; 325 + if (!is_accessor || !existing_accessor || existing->is_static != is_static) { 326 + js_mkerr_typed(c->js, JS_ERR_SYNTAX, "Duplicate private name '%.*s'", (int)name->len, name->str); 327 + return false; 328 + } 329 + if ((kind == SV_COMP_PRIVATE_GETTER && existing->has_getter) || 330 + (kind == SV_COMP_PRIVATE_SETTER && existing->has_setter)) { 331 + js_mkerr_typed(c->js, JS_ERR_SYNTAX, "Duplicate private accessor '%.*s'", (int)name->len, name->str); 332 + return false; 333 + } 334 + if (kind == SV_COMP_PRIVATE_GETTER) existing->has_getter = true; 335 + if (kind == SV_COMP_PRIVATE_SETTER) existing->has_setter = true; 336 + return true; 337 + } 338 + 339 + if (scope->count >= scope->cap) { 340 + int new_cap = scope->cap ? scope->cap * 2 : 8; 341 + sv_private_name_t *new_names = realloc(scope->names, (size_t)new_cap * sizeof(*scope->names)); 342 + if (!new_names) return false; 343 + scope->names = new_names; 344 + scope->cap = new_cap; 345 + } 346 + 347 + sv_private_name_t *p = &scope->names[scope->count++]; 348 + *p = (sv_private_name_t){ 349 + .name = name->str, 350 + .len = name->len, 351 + .kind = kind, 352 + .is_static = is_static, 353 + .has_getter = kind == SV_COMP_PRIVATE_GETTER, 354 + .has_setter = kind == SV_COMP_PRIVATE_SETTER, 355 + .owner = NULL, 356 + .local = -1 357 + }; 358 + return true; 359 + } 360 + 361 + static int resolve_private_upvalue(sv_compiler_t *c, sv_private_name_t *p) { 362 + if (!c->enclosing || !p || !p->owner || p->local < 0) return -1; 363 + 364 + if (c->enclosing == p->owner) { 365 + p->owner->locals[p->local].captured = true; 366 + uint16_t slot = (uint16_t)local_to_frame_slot(p->owner, p->local); 367 + return add_upvalue(c, slot, true, false); 368 + } 369 + 370 + int upvalue = resolve_private_upvalue(c->enclosing, p); 371 + if (upvalue == -1) return -1; 372 + return add_upvalue(c, (uint16_t)upvalue, false, false); 373 + } 374 + 375 + static bool emit_private_token(sv_compiler_t *c, sv_ast_t *name) { 376 + sv_private_name_t *p = private_scope_resolve(c, name->str, name->len); 377 + if (!p) { 378 + js_mkerr_typed(c->js, JS_ERR_SYNTAX, "Private name '%.*s' is not declared", (int)name->len, name->str); 379 + emit_op(c, OP_UNDEF); 380 + return false; 381 + } 382 + if (!p->owner || p->local < 0) { 383 + js_mkerr_typed(c->js, JS_ERR_SYNTAX, "Private name '%.*s' is not initialized", (int)name->len, name->str); 384 + emit_op(c, OP_UNDEF); 385 + return false; 386 + } 387 + if (p->owner == c) { 388 + emit_get_local(c, p->local); 389 + return true; 390 + } 391 + int upvalue = resolve_private_upvalue(c, p); 392 + if (upvalue == -1) { 393 + js_mkerr_typed(c->js, JS_ERR_SYNTAX, "Private name '%.*s' is not in scope", (int)name->len, name->str); 394 + emit_op(c, OP_UNDEF); 395 + return false; 396 + } 397 + emit_op(c, OP_GET_UPVAL); 398 + emit_u16(c, (uint16_t)upvalue); 399 + return true; 400 + } 401 + 273 402 static int add_atom(sv_compiler_t *c, const char *str, uint32_t len) { 274 403 const char *interned = intern_string(str, (size_t)len); 275 404 const char *stored = interned; ··· 1429 1558 } 1430 1559 1431 1560 case N_IDENT: 1561 + if (is_private_name_node(node)) { 1562 + js_mkerr_typed(c->js, JS_ERR_SYNTAX, "Private names may only be used as class member names"); 1563 + emit_op(c, OP_UNDEF); 1564 + break; 1565 + } 1432 1566 emit_get_var(c, node->str, node->len); 1433 1567 break; 1434 1568 ··· 1643 1777 1644 1778 if (op == TOK_IN && node->left->type == N_IDENT && 1645 1779 node->left->len > 0 && node->left->str[0] == '#') { 1646 - emit_constant(c, js_mkstr_permanent(c->js, node->left->str, node->left->len)); 1647 1780 compile_expr(c, node->right); 1648 - emit_op(c, OP_IN); 1781 + emit_private_token(c, node->left); 1782 + emit_op(c, OP_HAS_PRIVATE); 1649 1783 return; 1650 1784 } 1651 1785 ··· 1724 1858 emit_op(c, is_inc ? OP_POST_INC : OP_POST_DEC); 1725 1859 emit_set_var(c, target->str, target->len, false); 1726 1860 } 1861 + } else if (target->type == N_MEMBER && !(target->flags & 1) && is_private_name_node(target->right)) { 1862 + compile_expr(c, target->left); 1863 + emit_op(c, OP_DUP); 1864 + emit_private_token(c, target->right); 1865 + emit_op(c, OP_GET_PRIVATE); 1866 + if (prefix) { 1867 + emit_op(c, is_inc ? OP_INC : OP_DEC); 1868 + emit_private_token(c, target->right); 1869 + emit_op(c, OP_PUT_PRIVATE); 1870 + } else { 1871 + emit_op(c, is_inc ? OP_POST_INC : OP_POST_DEC); 1872 + emit_op(c, OP_SWAP_UNDER); 1873 + emit_private_token(c, target->right); 1874 + emit_op(c, OP_PUT_PRIVATE); 1875 + emit_op(c, OP_POP); 1876 + } 1727 1877 } else if (target->type == N_MEMBER && !(target->flags & 1)) { 1728 1878 compile_expr(c, target->left); 1729 1879 emit_op(c, OP_DUP); ··· 1770 1920 ); 1771 1921 1772 1922 if (op == TOK_ASSIGN) { 1923 + if (target->type == N_MEMBER && !(target->flags & 1) && is_private_name_node(target->right)) { 1924 + compile_expr(c, target->left); 1925 + compile_expr(c, node->right); 1926 + emit_private_token(c, target->right); 1927 + emit_op(c, OP_PUT_PRIVATE); 1928 + return; 1929 + } 1930 + 1773 1931 if (target->type == N_MEMBER && !(target->flags & 1)) { 1774 1932 int atom = add_atom(c, target->right->str, target->right->len); 1775 1933 compile_expr(c, target->left); ··· 1885 2043 default: break; 1886 2044 } 1887 2045 emit_set_var(c, target->str, target->len, true); 2046 + } else if (target->type == N_MEMBER && !(target->flags & 1) && is_private_name_node(target->right)) { 2047 + if (op == TOK_LOR_ASSIGN || op == TOK_LAND_ASSIGN || 2048 + op == TOK_NULLISH_ASSIGN) { 2049 + compile_expr(c, target->left); 2050 + emit_op(c, OP_DUP); 2051 + emit_private_token(c, target->right); 2052 + emit_op(c, OP_GET_PRIVATE); 2053 + int skip = emit_jump(c, 2054 + op == TOK_LOR_ASSIGN ? OP_JMP_TRUE_PEEK : 2055 + op == TOK_LAND_ASSIGN ? OP_JMP_FALSE_PEEK : OP_JMP_NOT_NULLISH); 2056 + emit_op(c, OP_POP); 2057 + compile_expr(c, node->right); 2058 + emit_private_token(c, target->right); 2059 + emit_op(c, OP_PUT_PRIVATE); 2060 + int end = emit_jump(c, OP_JMP); 2061 + patch_jump(c, skip); 2062 + emit_op(c, OP_NIP); 2063 + patch_jump(c, end); 2064 + return; 2065 + } 2066 + 2067 + compile_expr(c, target->left); 2068 + emit_op(c, OP_DUP); 2069 + emit_private_token(c, target->right); 2070 + emit_op(c, OP_GET_PRIVATE); 2071 + compile_expr(c, node->right); 2072 + switch (op) { 2073 + case TOK_PLUS_ASSIGN: emit_op(c, OP_ADD); break; 2074 + case TOK_MINUS_ASSIGN: emit_op(c, OP_SUB); break; 2075 + case TOK_MUL_ASSIGN: emit_op(c, OP_MUL); break; 2076 + case TOK_DIV_ASSIGN: emit_op(c, OP_DIV); break; 2077 + case TOK_REM_ASSIGN: emit_op(c, OP_MOD); break; 2078 + case TOK_SHL_ASSIGN: emit_op(c, OP_SHL); break; 2079 + case TOK_SHR_ASSIGN: emit_op(c, OP_SHR); break; 2080 + case TOK_ZSHR_ASSIGN: emit_op(c, OP_USHR); break; 2081 + case TOK_AND_ASSIGN: emit_op(c, OP_BAND); break; 2082 + case TOK_XOR_ASSIGN: emit_op(c, OP_BXOR); break; 2083 + case TOK_OR_ASSIGN: emit_op(c, OP_BOR); break; 2084 + case TOK_EXP_ASSIGN: emit_op(c, OP_EXP); break; 2085 + default: break; 2086 + } 2087 + emit_private_token(c, target->right); 2088 + emit_op(c, OP_PUT_PRIVATE); 1888 2089 } else if (target->type == N_MEMBER && !(target->flags & 1)) { 1889 2090 int atom = add_atom(c, target->right->str, target->right->len); 1890 2091 ··· 1980 2181 void compile_lhs_set(sv_compiler_t *c, sv_ast_t *target, bool keep) { 1981 2182 if (target->type == N_IDENT) { 1982 2183 emit_set_var(c, target->str, target->len, keep); 2184 + } else if (target->type == N_MEMBER && !(target->flags & 1) && is_private_name_node(target->right)) { 2185 + (void)keep; 2186 + compile_expr(c, target->left); 2187 + emit_op(c, OP_SWAP); 2188 + emit_private_token(c, target->right); 2189 + emit_op(c, OP_PUT_PRIVATE); 1983 2190 } else if (target->type == N_MEMBER && !(target->flags & 1)) { 1984 2191 if (keep) emit_op(c, OP_DUP); 1985 2192 compile_expr(c, target->left); ··· 2069 2276 2070 2277 void compile_delete(sv_compiler_t *c, sv_ast_t *node) { 2071 2278 sv_ast_t *arg = node->right; 2279 + if ((arg->type == N_MEMBER || arg->type == N_OPTIONAL) && 2280 + arg->right && is_private_name_node(arg->right)) { 2281 + js_mkerr_typed(c->js, JS_ERR_SYNTAX, "Cannot delete private fields"); 2282 + emit_op(c, OP_TRUE); 2283 + return; 2284 + } 2072 2285 if (arg->type == N_OPTIONAL) { 2073 2286 compile_delete_optional(c, arg); 2074 2287 } else if (arg->type == N_MEMBER && sv_node_has_optional_base(arg->left)) { ··· 2168 2381 if (node->flags & 1) { 2169 2382 compile_expr(c, node->right); 2170 2383 emit_op(c, OP_GET_ELEM); 2384 + } else if (is_private_name_node(node->right)) { 2385 + emit_private_token(c, node->right); 2386 + emit_op(c, OP_GET_PRIVATE); 2171 2387 } else { 2172 2388 emit_srcpos(c, node->right); 2173 2389 emit_atom_op(c, OP_GET_FIELD, node->right->str, node->right->len); ··· 2202 2418 } 2203 2419 2204 2420 if (callee->type == N_MEMBER && is_ident_name(callee->left, "super")) { 2421 + if (!(callee->flags & 1) && is_private_name_node(callee->right)) { 2422 + js_mkerr_typed(c->js, JS_ERR_SYNTAX, "Cannot access private member through super"); 2423 + emit_op(c, OP_UNDEF); 2424 + return SV_CALL_DIRECT; 2425 + } 2205 2426 emit_op(c, OP_THIS); 2206 2427 emit_op(c, OP_THIS); 2207 2428 emit_get_var(c, "super", 5); ··· 2439 2660 2440 2661 void compile_member(sv_compiler_t *c, sv_ast_t *node) { 2441 2662 if (is_ident_name(node->left, "super")) { 2663 + if (!(node->flags & 1) && is_private_name_node(node->right)) { 2664 + js_mkerr_typed(c->js, JS_ERR_SYNTAX, "Cannot access private member through super"); 2665 + emit_op(c, OP_UNDEF); 2666 + return; 2667 + } 2442 2668 emit_op(c, OP_THIS); 2443 2669 emit_get_var(c, "super", 5); 2444 2670 if (node->flags & 1) ··· 2461 2687 if (node->flags & 1) { 2462 2688 compile_expr(c, node->right); 2463 2689 emit_op(c, OP_GET_ELEM); 2690 + } else if (is_private_name_node(node->right)) { 2691 + emit_private_token(c, node->right); 2692 + emit_op(c, OP_GET_PRIVATE); 2464 2693 } else { 2465 2694 if (node->right->len == 6 && memcmp(node->right->str, "length", 6) == 0) 2466 2695 emit_op(c, OP_GET_LENGTH); ··· 2477 2706 if (node->flags & 1) { 2478 2707 compile_expr(c, node->right); 2479 2708 emit_op(c, OP_GET_ELEM_OPT); 2709 + } else if (is_private_name_node(node->right)) { 2710 + emit_private_token(c, node->right); 2711 + emit_op(c, OP_GET_PRIVATE_OPT); 2480 2712 } else { 2481 2713 emit_srcpos(c, node->right); 2482 2714 emit_atom_op(c, OP_GET_FIELD_OPT, node->right->str, node->right->len); ··· 4203 4435 } 4204 4436 } 4205 4437 4206 - 4438 + static inline bool is_class_method_def(const sv_ast_t *m); 4207 4439 static void emit_field_inits(sv_compiler_t *c, sv_ast_t **fields, int count) { 4208 4440 sv_compiler_t *enc = c->enclosing; 4209 4441 for (int i = 0; i < count; i++) { 4210 4442 sv_ast_t *m = fields[i]; 4443 + bool is_fn = is_class_method_def(m); 4444 + if (is_private_name_node(m->left)) { 4445 + emit_op(c, OP_THIS); 4446 + emit_private_token(c, m->left); 4447 + if (is_fn) compile_func_expr(c, m->right); 4448 + else if (m->right) compile_expr(c, m->right); 4449 + else emit_op(c, OP_UNDEF); 4450 + emit_op(c, OP_DEF_PRIVATE); 4451 + if (m->flags & FN_GETTER) emit(c, SV_COMP_PRIVATE_GETTER); 4452 + else if (m->flags & FN_SETTER) emit(c, SV_COMP_PRIVATE_SETTER); 4453 + else emit(c, is_fn ? SV_COMP_PRIVATE_METHOD : SV_COMP_PRIVATE_FIELD); 4454 + emit_op(c, OP_POP); 4455 + continue; 4456 + } 4457 + 4211 4458 emit_op(c, OP_THIS); 4212 4459 if (m->right) compile_expr(c, m->right); 4213 4460 else emit_op(c, OP_UNDEF); ··· 4282 4529 emit_op(c, OP_POP); 4283 4530 } 4284 4531 4532 + static void compile_private_static_element(sv_compiler_t *c, sv_ast_t *m, int ctor_local) { 4533 + bool is_fn = is_class_method_def(m); 4534 + emit_get_local(c, ctor_local); 4535 + emit_private_token(c, m->left); 4536 + if (is_fn) { 4537 + if (m->flags & FN_STATIC) m->right->flags |= FN_STATIC; 4538 + compile_func_expr(c, m->right); 4539 + } else if (m->right) compile_expr(c, m->right); 4540 + else emit_op(c, OP_UNDEF); 4541 + 4542 + emit_op(c, OP_DEF_PRIVATE); 4543 + if (m->flags & FN_GETTER) emit(c, SV_COMP_PRIVATE_GETTER); 4544 + else if (m->flags & FN_SETTER) emit(c, SV_COMP_PRIVATE_SETTER); 4545 + else emit(c, is_fn ? SV_COMP_PRIVATE_METHOD : SV_COMP_PRIVATE_FIELD); 4546 + emit_op(c, OP_POP); 4547 + } 4548 + 4285 4549 static inline int compile_class_precompute_key(sv_compiler_t *c, sv_ast_t *key_expr) { 4286 4550 compile_expr(c, key_expr); 4287 4551 int loc = add_local(c, "", 0, false, c->scope_depth); ··· 4303 4567 if (node->left) compile_expr(c, node->left); 4304 4568 else emit_op(c, OP_UNDEF); 4305 4569 4570 + sv_private_scope_t private_scope = { .parent = c->private_scope }; 4571 + sv_private_scope_t *saved_private_scope = c->private_scope; 4572 + c->private_scope = &private_scope; 4573 + 4306 4574 for (int i = 0; i < node->args.count; i++) { 4307 4575 sv_ast_t *m = node->args.items[i]; 4308 4576 if (m->type != N_METHOD) continue; 4577 + bool is_fn = is_class_method_def(m); 4578 + bool is_private = is_private_name_node(m->left); 4579 + 4580 + if (is_private) { 4581 + uint8_t private_kind = (m->flags & FN_GETTER) ? SV_COMP_PRIVATE_GETTER : 4582 + (m->flags & FN_SETTER) ? SV_COMP_PRIVATE_SETTER : 4583 + is_fn ? SV_COMP_PRIVATE_METHOD : SV_COMP_PRIVATE_FIELD; 4584 + if (!private_scope_add(c, &private_scope, m->left, private_kind, !!(m->flags & FN_STATIC))) { 4585 + c->private_scope = saved_private_scope; 4586 + free(private_scope.names); 4587 + emit_op(c, OP_UNDEF); 4588 + return; 4589 + } 4590 + } 4309 4591 4310 4592 if ( 4311 4593 !(m->flags & FN_STATIC) && ··· 4323 4605 memcmp(m->left->str, "name", 4) == 0 4324 4606 ) has_static_name = true; 4325 4607 4326 - bool is_fn = is_class_method_def(m); 4327 - if (!(m->flags & FN_STATIC) && !is_fn) field_count++; 4328 - if (node->str && (m->flags & FN_COMPUTED) && (is_fn || (m->flags & FN_STATIC))) computed_method_count++; 4608 + if (!(m->flags & FN_STATIC) && (is_private || !is_fn)) field_count++; 4609 + if (!is_private && node->str && (m->flags & FN_COMPUTED) && (is_fn || (m->flags & FN_STATIC))) computed_method_count++; 4329 4610 } 4330 4611 4331 4612 sv_ast_t **field_inits = NULL; ··· 4347 4628 if (m->type != N_METHOD || m == ctor_method) continue; 4348 4629 4349 4630 bool is_fn = is_class_method_def(m); 4350 - bool is_instance_field = !(m->flags & FN_STATIC) && !is_fn; 4631 + bool is_private = is_private_name_node(m->left); 4632 + bool needs_instance_init = !(m->flags & FN_STATIC) && (is_private || !is_fn); 4351 4633 4352 - if (is_instance_field) { 4634 + if (needs_instance_init) { 4353 4635 if (field_inits) field_inits[fi] = m; 4354 - if (computed_key_locals) computed_key_locals[fi] = (m->flags & FN_COMPUTED) 4636 + if (computed_key_locals) computed_key_locals[fi] = (!is_private && (m->flags & FN_COMPUTED)) 4355 4637 ? compile_class_precompute_key(c, m->left) : -1; 4356 4638 fi++; 4357 4639 continue; 4358 4640 } 4359 4641 4360 - if (!method_comp_keys || !(m->flags & FN_COMPUTED)) continue; 4642 + if (is_private || !method_comp_keys || !(m->flags & FN_COMPUTED)) continue; 4361 4643 method_comp_keys[i] = compile_class_precompute_key(c, m->left); 4362 4644 }} 4363 4645 4364 4646 int inner_name_local = -1; 4365 - if (node->str) { 4647 + bool has_class_scope = node->str || private_scope.count > 0; 4648 + if (has_class_scope) { 4366 4649 begin_scope(c); 4367 - inner_name_local = add_local(c, node->str, node->len, true, c->scope_depth); 4650 + if (node->str) 4651 + inner_name_local = add_local(c, node->str, node->len, true, c->scope_depth); 4652 + for (int i = 0; i < private_scope.count; i++) { 4653 + sv_private_name_t *p = &private_scope.names[i]; 4654 + p->owner = c; 4655 + p->local = add_local(c, "", 0, true, c->scope_depth); 4656 + emit_op(c, OP_OBJECT); 4657 + emit_put_local(c, p->local); 4658 + } 4368 4659 } 4369 4660 4370 4661 if (ctor_method && ctor_method->right) { ··· 4493 4784 4494 4785 if (m->type != N_METHOD) continue; 4495 4786 if (m == ctor_method) continue; 4787 + if (is_private_name_node(m->left)) { 4788 + if (m->flags & FN_STATIC) compile_private_static_element(c, m, ctor_local); 4789 + continue; 4790 + } 4496 4791 4497 4792 bool is_fn = is_class_method_def(m); 4498 4793 if (!is_fn && !(m->flags & FN_STATIC)) continue; ··· 4515 4810 c->locals[outer_name_local].is_tdz = false; 4516 4811 } 4517 4812 4518 - if (node->str) end_scope(c); 4813 + if (has_class_scope) end_scope(c); 4814 + c->private_scope = saved_private_scope; 4815 + free(private_scope.names); 4519 4816 } 4520 4817 4521 4818 static bool ast_contains_await_expr(const sv_ast_t *node) {
+5 -4
src/silver/engine.c
··· 1019 1019 L_GET_FIELD_OPT: { VM_CHECK(sv_op_get_field_opt(vm, js, func, ip)); NEXT(5); } 1020 1020 L_GET_ELEM_OPT: { VM_CHECK(sv_op_get_elem_opt(vm, js, func, ip)); NEXT(1); } 1021 1021 1022 - L_GET_PRIVATE: { sv_op_get_private(vm, js); NEXT(1); } 1023 - L_PUT_PRIVATE: { sv_op_put_private(vm, js); NEXT(1); } 1024 - L_DEF_PRIVATE: { sv_op_def_private(vm, js); NEXT(1); } 1022 + L_GET_PRIVATE: { VM_CHECK(sv_op_get_private(vm, js)); NEXT(1); } 1023 + L_GET_PRIVATE_OPT: { VM_CHECK(sv_op_get_private_opt(vm, js)); NEXT(1); } 1024 + L_PUT_PRIVATE: { VM_CHECK(sv_op_put_private(vm, js)); NEXT(1); } 1025 + L_DEF_PRIVATE: { VM_CHECK(sv_op_def_private(vm, js, ip)); NEXT(2); } 1026 + L_HAS_PRIVATE: { VM_CHECK(sv_op_has_private(vm, js)); NEXT(1); } 1025 1027 1026 1028 L_GET_SUPER: { sv_op_get_super(vm, js); NEXT(1); } 1027 1029 L_GET_SUPER_VAL: { sv_op_get_super_val(vm, js, frame); NEXT(1); } ··· 1857 1859 1858 1860 L_DEFINE_CLASS: { sv_op_define_class(vm, js, func, ip); NEXT(14); } 1859 1861 L_DEFINE_CLASS_COMP: { sv_op_define_class_comp(vm, js, func, ip); NEXT(14); } 1860 - L_ADD_BRAND: { sv_op_add_brand(vm); NEXT(1); } 1861 1862 1862 1863 L_TO_OBJECT: { VM_CHECK(sv_op_to_object(vm, js)); NEXT(1); } 1863 1864 L_TO_PROPKEY: { sv_op_to_propkey(vm, js); NEXT(1); }
-4
src/silver/ops/objects.h
··· 302 302 vm->stack[vm->sp++] = name; 303 303 } 304 304 305 - static inline void sv_op_add_brand(sv_vm_t *vm) { 306 - vm->sp -= 2; 307 - } 308 - 309 305 #endif
+211 -15
src/silver/ops/private.h
··· 2 2 #define SV_PRIVATE_H 3 3 4 4 #include "silver/engine.h" 5 + #include <stdio.h> 6 + 7 + enum { 8 + SV_PRIVATE_FIELD = 0, 9 + SV_PRIVATE_METHOD = 1, 10 + SV_PRIVATE_ACCESSOR = 2, 11 + SV_PRIVATE_GETTER = 3, 12 + SV_PRIVATE_SETTER = 4 13 + }; 14 + 15 + static inline ant_value_t sv_private_entry_get(ant_t *js, ant_value_t entry, ant_offset_t idx) { 16 + return vtype(entry) == T_ARR 17 + ? js_arr_get(js, entry, idx) 18 + : js_mkundef(); 19 + } 20 + 21 + static inline ant_value_t sv_private_entry_set(ant_t *js, ant_value_t entry, ant_offset_t idx, ant_value_t value) { 22 + char key_buf[8]; 23 + int key_len = snprintf(key_buf, sizeof(key_buf), "%u", (unsigned)idx); 24 + return js_setprop(js, entry, js_mkstr(js, key_buf, (size_t)key_len), value); 25 + } 26 + 27 + static inline ant_value_t sv_private_table(ant_t *js, ant_value_t obj, bool create) { 28 + if (!is_object_type(obj)) return js_mkundef(); 29 + ant_value_t table = js_get_slot(obj, SLOT_PRIVATE_ELEMENTS); 30 + 31 + if (vtype(table) == T_ARR) return table; 32 + if (!create) return js_mkundef(); 33 + table = js_mkarr(js); 34 + 35 + if (is_err(table)) return table; 36 + js_set_slot_wb(js, obj, SLOT_PRIVATE_ELEMENTS, table); 37 + 38 + return table; 39 + } 40 + 41 + static inline ant_value_t sv_private_cached_entry( 42 + ant_t *js, ant_value_t table, ant_value_t token, ant_offset_t len 43 + ) { 44 + if (!is_object_type(token)) return js_mkundef(); 45 + ant_value_t cached = js_get_slot(token, SLOT_DATA); 46 + if (vtype(cached) != T_NUM) return js_mkundef(); 47 + 48 + double idx_num = js_getnum(cached); 49 + if (idx_num < 0 || idx_num >= (double)len) return js_mkundef(); 50 + 51 + ant_offset_t idx = (ant_offset_t)idx_num; 52 + ant_value_t entry = js_arr_get(js, table, idx); 53 + if (vtype(entry) == T_ARR && sv_private_entry_get(js, entry, 0) == token) 54 + return entry; 55 + return js_mkundef(); 56 + } 57 + 58 + static inline void sv_private_cache_entry(ant_t *js, ant_value_t token, ant_offset_t idx) { 59 + if (is_object_type(token)) 60 + js_set_slot(token, SLOT_DATA, js_mknum((double)idx)); 61 + } 5 62 6 - static inline void sv_op_get_private(sv_vm_t *vm, ant_t *js) { 7 - ant_value_t prop = vm->stack[--vm->sp]; 63 + static inline ant_value_t sv_private_find_entry(ant_t *js, ant_value_t obj, ant_value_t token) { 64 + ant_value_t table = sv_private_table(js, obj, false); 65 + if (vtype(table) != T_ARR) return js_mkundef(); 66 + ant_offset_t len = js_arr_len(js, table); 67 + 68 + ant_value_t cached = sv_private_cached_entry(js, table, token, len); 69 + if (vtype(cached) != T_UNDEF) return cached; 70 + 71 + for (ant_offset_t i = 0; i < len; i++) { 72 + ant_value_t entry = js_arr_get(js, table, i); 73 + if (vtype(entry) == T_ARR && sv_private_entry_get(js, entry, 0) == token) { 74 + sv_private_cache_entry(js, token, i); 75 + return entry; 76 + }} 77 + 78 + return js_mkundef(); 79 + } 80 + 81 + static inline ant_value_t sv_private_make_entry( 82 + ant_t *js, ant_value_t obj, ant_value_t token, 83 + int kind, ant_value_t value, ant_value_t getter, ant_value_t setter 84 + ) { 85 + ant_value_t table = sv_private_table(js, obj, true); 86 + if (is_err(table)) return table; 87 + 88 + ant_value_t entry = js_mkarr(js); 89 + if (is_err(entry)) return entry; 90 + js_arr_push(js, entry, token); 91 + js_arr_push(js, entry, js_mknum((double)kind)); 92 + js_arr_push(js, entry, value); 93 + js_arr_push(js, entry, getter); 94 + js_arr_push(js, entry, setter); 95 + 96 + ant_offset_t idx = js_arr_len(js, table); 97 + js_arr_push(js, table, entry); 98 + sv_private_cache_entry(js, token, idx); 99 + 100 + return entry; 101 + } 102 + 103 + static inline ant_value_t sv_private_missing(ant_t *js) { 104 + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot access private member on object whose class did not declare it"); 105 + } 106 + 107 + static inline ant_value_t sv_op_get_private_impl(sv_vm_t *vm, ant_t *js, bool optional) { 108 + ant_value_t token = vm->stack[--vm->sp]; 8 109 ant_value_t obj = vm->stack[--vm->sp]; 9 - ant_value_t key_str = coerce_to_str(js, prop); 10 - ant_offset_t klen; 11 - ant_offset_t koff = vstr(js, key_str, &klen); 12 - const char *kptr = (const char *)(uintptr_t)(koff); 13 - vm->stack[vm->sp++] = js_getprop_fallback(js, obj, kptr); 110 + 111 + if (!is_object_type(obj)) { 112 + if (optional && (vtype(obj) == T_UNDEF || vtype(obj) == T_NULL)) { 113 + vm->stack[vm->sp++] = js_mkundef(); 114 + return js_mkundef(); 115 + } 116 + return sv_private_missing(js); 117 + } 118 + 119 + ant_value_t entry = sv_private_find_entry(js, obj, token); 120 + if (vtype(entry) == T_UNDEF) return sv_private_missing(js); 121 + 122 + ant_value_t kind_val = sv_private_entry_get(js, entry, 1); 123 + int kind = vtype(kind_val) == T_NUM ? (int)js_getnum(kind_val) : SV_PRIVATE_FIELD; 124 + if (kind == SV_PRIVATE_ACCESSOR) { 125 + ant_value_t getter = sv_private_entry_get(js, entry, 3); 126 + if (vtype(getter) == T_UNDEF) 127 + return js_mkerr_typed(js, JS_ERR_TYPE, "Private accessor has no getter"); 128 + ant_value_t result = sv_vm_call_explicit_this(vm, js, getter, obj, NULL, 0); 129 + if (is_err(result)) return result; 130 + vm->stack[vm->sp++] = result; 131 + return js_mkundef(); 132 + } 133 + 134 + vm->stack[vm->sp++] = sv_private_entry_get(js, entry, 2); 135 + return js_mkundef(); 14 136 } 15 137 16 - static inline void sv_op_put_private(sv_vm_t *vm, ant_t *js) { 17 - ant_value_t prop = vm->stack[--vm->sp]; 138 + static inline ant_value_t sv_op_get_private(sv_vm_t *vm, ant_t *js) { 139 + return sv_op_get_private_impl(vm, js, false); 140 + } 141 + 142 + static inline ant_value_t sv_op_get_private_opt(sv_vm_t *vm, ant_t *js) { 143 + return sv_op_get_private_impl(vm, js, true); 144 + } 145 + 146 + static inline ant_value_t sv_op_put_private(sv_vm_t *vm, ant_t *js) { 147 + ant_value_t token = vm->stack[--vm->sp]; 18 148 ant_value_t val = vm->stack[--vm->sp]; 19 149 ant_value_t obj = vm->stack[--vm->sp]; 20 - ant_value_t key_str = coerce_to_str(js, prop); 21 - js_setprop(js, obj, key_str, val); 150 + 151 + if (!is_object_type(obj)) return sv_private_missing(js); 152 + ant_value_t entry = sv_private_find_entry(js, obj, token); 153 + if (vtype(entry) == T_UNDEF) return sv_private_missing(js); 154 + 155 + ant_value_t kind_val = sv_private_entry_get(js, entry, 1); 156 + int kind = vtype(kind_val) == T_NUM ? (int)js_getnum(kind_val) : SV_PRIVATE_FIELD; 157 + if (kind == SV_PRIVATE_FIELD) { 158 + ant_value_t set = sv_private_entry_set(js, entry, 2, val); 159 + if (is_err(set)) return set; 160 + vm->stack[vm->sp++] = val; 161 + return js_mkundef(); 162 + } 163 + 164 + if (kind == SV_PRIVATE_ACCESSOR) { 165 + ant_value_t setter = sv_private_entry_get(js, entry, 4); 166 + if (vtype(setter) == T_UNDEF) 167 + return js_mkerr_typed(js, JS_ERR_TYPE, "Private accessor has no setter"); 168 + ant_value_t args[1] = { val }; 169 + ant_value_t result = sv_vm_call_explicit_this(vm, js, setter, obj, args, 1); 170 + if (is_err(result)) return result; 171 + vm->stack[vm->sp++] = val; 172 + return js_mkundef(); 173 + } 174 + 175 + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot write to private method"); 22 176 } 23 177 24 - static inline void sv_op_def_private(sv_vm_t *vm, ant_t *js) { 178 + static inline ant_value_t sv_op_def_private(sv_vm_t *vm, ant_t *js, uint8_t *ip) { 179 + uint8_t def_kind = sv_get_u8(ip + 1); 25 180 ant_value_t val = vm->stack[--vm->sp]; 26 - ant_value_t prop = vm->stack[--vm->sp]; 181 + ant_value_t token = vm->stack[--vm->sp]; 27 182 ant_value_t obj = vm->stack[vm->sp - 1]; 28 - ant_value_t key_str = coerce_to_str(js, prop); 29 - js_setprop(js, obj, key_str, val); 183 + 184 + if (!is_object_type(obj)) return sv_private_missing(js); 185 + ant_value_t existing = sv_private_find_entry(js, obj, token); 186 + 187 + if (def_kind == SV_PRIVATE_GETTER || def_kind == SV_PRIVATE_SETTER) { 188 + ant_value_t entry = existing; 189 + if (vtype(entry) == T_UNDEF) { 190 + entry = sv_private_make_entry( 191 + js, obj, token, SV_PRIVATE_ACCESSOR, 192 + js_mkundef(), 193 + def_kind == SV_PRIVATE_GETTER ? val : js_mkundef(), 194 + def_kind == SV_PRIVATE_SETTER ? val : js_mkundef()); 195 + return is_err(entry) ? entry : js_mkundef(); 196 + } 197 + 198 + ant_value_t kind_val = sv_private_entry_get(js, entry, 1); 199 + int kind = vtype(kind_val) == T_NUM ? (int)js_getnum(kind_val) : SV_PRIVATE_FIELD; 200 + if (kind != SV_PRIVATE_ACCESSOR) 201 + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot redefine private member"); 202 + 203 + ant_offset_t slot = def_kind == SV_PRIVATE_GETTER ? 3 : 4; 204 + if (vtype(sv_private_entry_get(js, entry, slot)) != T_UNDEF) 205 + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot redefine private accessor"); 206 + return sv_private_entry_set(js, entry, slot, val); 207 + } 208 + 209 + if (vtype(existing) != T_UNDEF) 210 + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot initialize private member twice"); 211 + 212 + ant_value_t entry = sv_private_make_entry( 213 + js, obj, token, 214 + def_kind == SV_PRIVATE_METHOD ? SV_PRIVATE_METHOD : SV_PRIVATE_FIELD, 215 + val, js_mkundef(), js_mkundef()); 216 + return is_err(entry) ? entry : js_mkundef(); 217 + } 218 + 219 + static inline ant_value_t sv_op_has_private(sv_vm_t *vm, ant_t *js) { 220 + ant_value_t token = vm->stack[--vm->sp]; 221 + ant_value_t obj = vm->stack[--vm->sp]; 222 + if (!is_object_type(obj)) 223 + return js_mkerr_typed(js, JS_ERR_TYPE, "Right operand of private brand check must be an object"); 224 + vm->stack[vm->sp++] = js_bool(vtype(sv_private_find_entry(js, obj, token)) != T_UNDEF); 225 + return js_mkundef(); 30 226 } 31 227 32 228 #endif
+162
tests/test_private_brands.cjs
··· 1 + const assert = (cond, msg) => { 2 + if (!cond) throw new Error(msg); 3 + }; 4 + 5 + const assertThrows = (fn, name, msg) => { 6 + let threw = false; 7 + try { 8 + fn(); 9 + } catch (err) { 10 + threw = name === undefined || (err && err.name === name); 11 + } 12 + if (!threw) throw new Error(msg); 13 + }; 14 + 15 + class A { 16 + #x = 1; 17 + 18 + #m() { 19 + return this.#x + 1; 20 + } 21 + 22 + get #g() { 23 + return this.#x + 2; 24 + } 25 + 26 + set #g(value) { 27 + this.#x = value; 28 + } 29 + 30 + has(value) { 31 + return #x in value; 32 + } 33 + 34 + read(value) { 35 + return value.#x; 36 + } 37 + 38 + write(value) { 39 + this.#g = value; 40 + } 41 + 42 + call() { 43 + return this.#m(); 44 + } 45 + 46 + get value() { 47 + return this.#g; 48 + } 49 + } 50 + 51 + class B { 52 + #x = 99; 53 + 54 + has(value) { 55 + return #x in value; 56 + } 57 + } 58 + 59 + const a = new A(); 60 + const b = new B(); 61 + 62 + assert(a.has(a) === true, "own private brand should be present"); 63 + assert(a.has({}) === false, "plain object should not have private brand"); 64 + assert(a.has(b) === false, "same spelling in another class should be distinct"); 65 + assert(b.has(b) === true, "other class should keep its own private brand"); 66 + assertThrows(() => a.has(1), "TypeError", "private brand check right operand should be object"); 67 + assertThrows(() => a.read({}), "TypeError", "wrong receiver private get should throw"); 68 + 69 + a.write(10); 70 + assert(a.value === 12, "private accessors should read and write hidden state"); 71 + assert(a.call() === 11, "private methods should be callable"); 72 + assert(a["#x"] === undefined, "private field should not be a public string property"); 73 + assert(Object.keys(a).indexOf("#x") === -1, "private field should not be enumerable"); 74 + assert(Reflect.ownKeys(a).indexOf("#x") === -1, "private field should not be reflected"); 75 + 76 + class S { 77 + static #v = 3; 78 + 79 + static #inc() { 80 + return ++this.#v; 81 + } 82 + 83 + static has(value) { 84 + return #v in value; 85 + } 86 + 87 + static run() { 88 + return this.#inc(); 89 + } 90 + } 91 + 92 + assert(S.has(S) === true, "static private brand should be on the constructor"); 93 + assert(S.has(new S()) === false, "static private brand should not be on instances"); 94 + assert(S.run() === 4, "static private methods should access static private fields"); 95 + 96 + let baseSeen = false; 97 + 98 + class Base { 99 + #base = 1; 100 + 101 + constructor() { 102 + baseSeen = #base in this; 103 + } 104 + 105 + hasBase(value) { 106 + return #base in value; 107 + } 108 + } 109 + 110 + class Derived extends Base { 111 + #derived = 2; 112 + 113 + hasDerived(value) { 114 + return #derived in value; 115 + } 116 + } 117 + 118 + const d = new Derived(); 119 + assert(baseSeen === true, "base private fields should be initialized during construction"); 120 + assert(d.hasBase(d) === true, "derived instances should keep base private brands"); 121 + assert(d.hasDerived(d) === true, "derived instances should keep derived private brands"); 122 + 123 + assertThrows(() => Function("class Bad { #x; #x; }"), "SyntaxError", "duplicate private field should be syntax error"); 124 + assertThrows(() => Function("class Bad { #constructor; }"), "SyntaxError", "#constructor should be rejected"); 125 + assertThrows(() => Function("class Bad { m() { return this.#missing; } }"), "SyntaxError", "undeclared private name should be syntax error"); 126 + 127 + const makeBox = Function("return class { #x = 1; has(value) { return #x in value; } read(value) { return value.#x; } };"); 128 + const Box1 = makeBox(); 129 + const Box2 = makeBox(); 130 + const box1 = new Box1(); 131 + const box2 = new Box2(); 132 + assert(box1.has(box2) === false, "private brands from separately compiled classes should not collide"); 133 + assertThrows(() => box1.read(box2), "TypeError", "same source offsets from separate compiles should stay distinct"); 134 + 135 + class ReturnObjectBase { 136 + constructor(value) { 137 + return value; 138 + } 139 + } 140 + 141 + class OtherStamp extends ReturnObjectBase { 142 + #other = 1; 143 + } 144 + 145 + class OffsetStamp extends ReturnObjectBase { 146 + #x = 2; 147 + 148 + getX() { 149 + return this.#x; 150 + } 151 + } 152 + 153 + const offsetZero = new OffsetStamp({}); 154 + const offsetOne = {}; 155 + new OtherStamp(offsetOne); 156 + new OffsetStamp(offsetOne); 157 + 158 + assert(OffsetStamp.prototype.getX.call(offsetZero) === 2, "cached private lookup should work at offset 0"); 159 + assert(OffsetStamp.prototype.getX.call(offsetOne) === 2, "cached private lookup should validate and recover at offset 1"); 160 + assert(OffsetStamp.prototype.getX.call(offsetZero) === 2, "cached private lookup should validate when returning to offset 0"); 161 + 162 + console.log("private brand tests ok");
+7
vendor/wirecall.wrap
··· 1 + [wrap-git] 2 + url = https://github.com/theMackabu/wirecall.git 3 + revision = head 4 + depth = 1 5 + 6 + [provide] 7 + wirecall = wirecall_dep