mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-31 07:23:06 +08:00
Store ordinary objects as a layout plus value slots (#2353)
This commit is contained in:
@@ -32,10 +32,9 @@ import { InternalCompilerError } from "../../errors.js";
|
||||
* are selected from the target C ABI.
|
||||
* v.num double | v.b i8 | v.str/v.bytes ptr at +16;
|
||||
* v.arr { len +16, cap +24, items +32 };
|
||||
* v.obj { len +16, cap +24, entries +32 };
|
||||
* v.obj { len +16 } (own properties; read them through
|
||||
* scr_dyn_member_*, which know both object modes);
|
||||
* v.fn { clo +16, thunk +24, sig +32, name +40, arity +48 }.
|
||||
* ScrDynEntry { key, key_len, value, getter, setter, accessor, writable,
|
||||
* enumerable, configurable } — 48 bytes on 64-bit, 24 on 32-bit.
|
||||
* ScrDynKind: NULL=0 BOOL=1 NUM=2 STR=3 ARR=4 OBJ=5 UNDEF=6 BYTES=7
|
||||
* FUNC=8 HANDLE=9.
|
||||
* ScrBytes { rc +0; len +8; elem +16; data +24 }.
|
||||
@@ -849,27 +848,23 @@ export class LlDyn {
|
||||
return e;
|
||||
}
|
||||
|
||||
/** Object entry field addresses: entries + i*sizeof(ScrDynEntry), with
|
||||
* key, key_len, and value at offsets 0, 8, and 16 on 64-bit targets. */
|
||||
/** Own property i of object `d` (borrowed key bytes, key length and
|
||||
* value), read through the runtime's accessors: the object may be in
|
||||
* layout or dictionary mode (scr_dyn.h). */
|
||||
private entryAt(
|
||||
B: BlockBuilder,
|
||||
entries: string,
|
||||
d: string,
|
||||
i: string,
|
||||
): { key: string; keyLen: string; value: string } {
|
||||
const off = B.tmp();
|
||||
const base = B.tmp();
|
||||
B.line(`${off} = mul ${this.S} ${i}, ${this.abiOffset(48, 24)} ; sizeof(ScrDynEntry)`);
|
||||
B.line(`${base} = getelementptr inbounds i8, ptr ${entries}, ${this.S} ${off}`);
|
||||
this.host.declare(`declare ptr @scr_dyn_member_key(ptr, ${this.S})`);
|
||||
this.host.declare(`declare ${this.S} @scr_dyn_member_key_len(ptr, ${this.S})`);
|
||||
this.host.declare(`declare ptr @scr_dyn_member_value(ptr, ${this.S})`);
|
||||
const key = B.tmp();
|
||||
B.line(`${key} = load ptr, ptr ${base}`);
|
||||
const klp = B.tmp();
|
||||
const keyLen = B.tmp();
|
||||
B.line(`${klp} = getelementptr inbounds i8, ptr ${base}, i64 ${this.abiOffset(8, 4)}`);
|
||||
B.line(`${keyLen} = load ${this.S}, ptr ${klp}`);
|
||||
const vp = B.tmp();
|
||||
const value = B.tmp();
|
||||
B.line(`${vp} = getelementptr inbounds i8, ptr ${base}, i64 ${this.abiOffset(16, 8)}`);
|
||||
B.line(`${value} = load ptr, ptr ${vp}`);
|
||||
B.line(`${key} = call ptr @scr_dyn_member_key(ptr ${d}, ${this.S} ${i})`);
|
||||
B.line(`${keyLen} = call ${this.S} @scr_dyn_member_key_len(ptr ${d}, ${this.S} ${i})`);
|
||||
B.line(`${value} = call ptr @scr_dyn_member_value(ptr ${d}, ${this.S} ${i})`);
|
||||
return { key, keyLen, value };
|
||||
}
|
||||
|
||||
@@ -1441,9 +1436,8 @@ export class LlDyn {
|
||||
if (shape.indexValue && shape.indexValue.kind !== "dyn") {
|
||||
this.host.declare(`declare i32 @memcmp(ptr, ptr, ${this.S})`);
|
||||
const n = this.lenOf(B, "%d");
|
||||
const entries = this.itemsOf(B, "%d");
|
||||
this.i64Loop(B, "dm.ov", n, (i, brNext) => {
|
||||
const ent = this.entryAt(B, entries, i);
|
||||
const ent = this.entryAt(B, "%d", i);
|
||||
// Skip declared keys.
|
||||
for (const f of shape.fields) {
|
||||
const klen = Buffer.byteLength(f.name, "utf8");
|
||||
@@ -2777,9 +2771,8 @@ export class LlDyn {
|
||||
B.line(`store i8 ${np}, ptr ${flagsp}`);
|
||||
}
|
||||
const n = this.lenOf(B, "%d");
|
||||
const entries = this.itemsOf(B, "%d");
|
||||
this.i64Loop(B, "dcv", n, (i, brNext) => {
|
||||
const ent = this.entryAt(B, entries, i);
|
||||
const ent = this.entryAt(B, "%d", i);
|
||||
for (const f of shape.fields) {
|
||||
const klen = Buffer.byteLength(f.name, "utf8");
|
||||
const lenEq = B.tmp();
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
* A property site in a dynamic-tier function owns one zeroed thread-local
|
||||
* ScrJsIc record (`@sc_js_ic_N`). The code at the site is fixed:
|
||||
*
|
||||
* load ordinary object whose shape is entry 0's own-property shape:
|
||||
* read entry `slot` inline unless it is an accessor; otherwise
|
||||
* scr_js_get_probe (polymorphic, prototype hits, absent keys,
|
||||
* misses)
|
||||
* store ordinary object whose shape is entry 0's replace shape, outside
|
||||
* an intact parse region, writable data entry: replace inline;
|
||||
* otherwise scr_js_store_probe (adds, polymorphic, misses)
|
||||
* load ordinary object whose layout is entry 0's own-property layout:
|
||||
* read slot `slot` inline; otherwise scr_js_get_probe
|
||||
* (polymorphic, prototype hits, absent keys, misses)
|
||||
* store ordinary object whose layout is entry 0's replace layout,
|
||||
* outside an intact parse region: replace inline; otherwise
|
||||
* scr_js_store_probe (adds, polymorphic, misses)
|
||||
* keyed dense array element at an integral number key: inline;
|
||||
* otherwise scr_js_keyed_probe (string keys of ordinary objects)
|
||||
* method dynInvoke's method read: scr_js_load_probe
|
||||
@@ -22,12 +21,13 @@
|
||||
* defines the operation's semantics; nothing is ever patched.
|
||||
*
|
||||
* Layout facts (64-bit only; 32-bit targets keep the helper calls):
|
||||
* ScrDyn kind +8; v.obj {len +16, entries +32, region +72,
|
||||
* shape +80}; v.arr {len +16, items +32, presence +56};
|
||||
* v.num +16
|
||||
* ScrDynEntry 48 bytes: value +16, accessor bit 0 of +40, writable +41
|
||||
* ScrJsIc entries of 40 bytes {shape, target, holder, holder_shape,
|
||||
* slot}; 168 bytes in all */
|
||||
* ScrDyn kind +8; v.obj {len +16, slots +32, region +72,
|
||||
* layout +80}; v.arr {len +16, items +32, presence +56};
|
||||
* v.num +16. Every property of an object with a layout is
|
||||
* a writable data property (scr_dyn.h's layout mode);
|
||||
* dictionary-mode objects have a null layout.
|
||||
* ScrJsIc entries of 40 bytes {layout, target, holder,
|
||||
* holder_layout, slot}; 168 bytes in all */
|
||||
import { BOOL, DYN, F64, STRING, type IrExpr, type IrFunction } from "../../ir/ir.js";
|
||||
import { jsIcMethodSites, type JsIcSite } from "../../ir/js-ic.js";
|
||||
import { mangleBorrowedFunction } from "../mangle.js";
|
||||
@@ -42,8 +42,6 @@ type Host = LlvmEmitterContext;
|
||||
/** Words of one ScrJsIc record. */
|
||||
export const JS_IC_WORDS = 21;
|
||||
|
||||
const ENTRY_STRIDE = 48;
|
||||
|
||||
/** The cache site a call is, when it is emitted in a dynamic-tier function
|
||||
* of a 64-bit target. */
|
||||
export function jsIcSiteOf(host: Host, e: ExprOf<"call">): JsIcSite | null {
|
||||
@@ -88,32 +86,17 @@ function kindIs(host: Host, d: string, kind: number, yes: string, no: string): v
|
||||
* the prototype hit made method-heavy code 3-6% faster for 15-90 bytes.
|
||||
* So a site tests entry 0, own or prototype, and its probe does the rest.
|
||||
* (0 makes every access probe: with a runtime built with SCR_JS_VERIFY,
|
||||
* that checks every cached shape against its object.) */
|
||||
* that probes every access.) */
|
||||
const INLINE_WAYS: number = 1;
|
||||
const INLINE_PROTO = true;
|
||||
|
||||
/** The address of entry `slot` of object `d`'s entries. */
|
||||
function entryAt(host: Host, d: string, slot: string): string {
|
||||
/** The address of slot `slot` of layout-mode object `d`. */
|
||||
function slotAt(host: Host, d: string, slot: string): string {
|
||||
const B = host.B;
|
||||
const entries = load(host, "ptr", d, 32);
|
||||
const offset = B.tmp();
|
||||
const entry = B.tmp();
|
||||
B.line(`${offset} = mul i64 ${slot}, ${ENTRY_STRIDE}`);
|
||||
B.line(`${entry} = getelementptr inbounds i8, ptr ${entries}, i64 ${offset}`);
|
||||
return entry;
|
||||
}
|
||||
|
||||
/** Branches to `miss` when the entry at `entry` is an accessor. */
|
||||
function dataEntry(host: Host, entry: string, miss: string): void {
|
||||
const B = host.B;
|
||||
const flags = load(host, "i8", entry, 40);
|
||||
const accessor = B.tmp();
|
||||
const isAccessor = B.tmp();
|
||||
B.line(`${accessor} = and i8 ${flags}, 1`);
|
||||
B.line(`${isAccessor} = icmp ne i8 ${accessor}, 0`);
|
||||
const data = B.newLabel("ic.data");
|
||||
B.condBr(isAccessor, miss, data);
|
||||
B.startBlock(data);
|
||||
const slots = load(host, "ptr", d, 32);
|
||||
const address = B.tmp();
|
||||
B.line(`${address} = getelementptr inbounds ptr, ptr ${slots}, i64 ${slot}`);
|
||||
return address;
|
||||
}
|
||||
|
||||
/** The inline part of a load site, entered for an ordinary object `d`:
|
||||
@@ -137,7 +120,7 @@ function inlineLoad(
|
||||
B.br(miss);
|
||||
return;
|
||||
}
|
||||
const shape = load(host, "ptr", d, 80);
|
||||
const shape = load(host, "ptr", d, 80); // v.obj.layout
|
||||
const present = B.tmp();
|
||||
B.line(`${present} = icmp ne ptr ${shape}, null`);
|
||||
let next = B.newLabel("ic.w");
|
||||
@@ -160,9 +143,7 @@ function inlineLoad(
|
||||
B.condBr(own, ownLabel, protoLabel);
|
||||
const hit = (holder: string): void => {
|
||||
const slot = load(host, "i64", ic, base + 32);
|
||||
const entry = entryAt(host, holder, slot);
|
||||
dataEntry(host, entry, miss);
|
||||
const value = load(host, "ptr", entry, 16);
|
||||
const value = load(host, "ptr", slotAt(host, holder, slot), 0);
|
||||
const out = result(value);
|
||||
incoming.push(`[ ${out}, %${B.currentLabel()} ]`);
|
||||
B.br(join);
|
||||
@@ -194,7 +175,7 @@ function inlineLoad(
|
||||
/** The inline part of a store site that replaces an own data property,
|
||||
* entered for an ordinary object `d` outside an intact parse region:
|
||||
* tests the first `ways` cache entries; a hit calls `store` with the
|
||||
* address of the entry's value word, which must end its block by
|
||||
* address of the property's slot, which must end its block by
|
||||
* branching to the site's join. */
|
||||
function inlineReplace(
|
||||
host: Host,
|
||||
@@ -209,7 +190,7 @@ function inlineReplace(
|
||||
B.br(miss);
|
||||
return;
|
||||
}
|
||||
const shape = load(host, "ptr", d, 80);
|
||||
const shape = load(host, "ptr", d, 80); // v.obj.layout
|
||||
const present = B.tmp();
|
||||
B.line(`${present} = icmp ne ptr ${shape}, null`);
|
||||
let next = B.newLabel("ic.w");
|
||||
@@ -230,21 +211,7 @@ function inlineReplace(
|
||||
B.condBr(ok, matched, next);
|
||||
B.startBlock(matched);
|
||||
const slot = load(host, "i64", ic, base + 32);
|
||||
const entry = entryAt(host, d, slot);
|
||||
const flags = load(host, "i8", entry, 40);
|
||||
const writable = load(host, "i8", entry, 41);
|
||||
const accessor = B.tmp();
|
||||
const data = B.tmp();
|
||||
const canWrite = B.tmp();
|
||||
const writes = B.tmp();
|
||||
B.line(`${accessor} = and i8 ${flags}, 1`);
|
||||
B.line(`${data} = icmp eq i8 ${accessor}, 0`);
|
||||
B.line(`${canWrite} = icmp ne i8 ${writable}, 0`);
|
||||
B.line(`${writes} = and i1 ${data}, ${canWrite}`);
|
||||
const hit = B.newLabel("ic.st");
|
||||
B.condBr(writes, hit, miss);
|
||||
B.startBlock(hit);
|
||||
store(gep(host, entry, 16));
|
||||
store(slotAt(host, d, slot));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -384,7 +384,7 @@ const RUNTIME_DEFINES: readonly {
|
||||
when: (opts: CcOptions) => boolean | undefined;
|
||||
}[] = [
|
||||
{ define: "SCR_TEXT_DECODER_LEGACY", when: (opts) => opts.textDecoderLegacy },
|
||||
// Checked objects carry the dynamic tier's shapes (scr_json.c's SCR_JS_TIER build); only
|
||||
// scr_json.c's SCR_JS_TIER build exports the dynamic tier's cache primitives; only
|
||||
// programs with dynamic-tier code select it.
|
||||
{ define: "SCR_JS_TIER", when: (opts) => opts.dynTier === true },
|
||||
{ define: "SCR_WARNING_REPLACE", when: (opts) => opts.warningReplace },
|
||||
|
||||
@@ -1369,7 +1369,7 @@ function scanRuntimeFeatures(
|
||||
for (const t of nodes.types) type(t);
|
||||
} else if (keepGoing()) everyModuleNode(mod, { expr, stmt: keepGoing, type });
|
||||
for (const { name, scan } of scans) if (scan.result()) features[name] = true;
|
||||
// Inline-cache sites of dynamic-tier functions use the tier's shapes
|
||||
// Inline-cache sites of dynamic-tier functions use the tier's caches
|
||||
// (scr_js_object.c, with the SCR_JS_TIER build of scr_json.c).
|
||||
if (!features.dynTier && mod.functions.some((fn) => fn.tier === "dynamic")) {
|
||||
const sites = new JsIcSites(mod);
|
||||
|
||||
@@ -187,9 +187,9 @@ export const OPTIONAL_RUNTIME_SOURCES = [
|
||||
|
||||
export function variantsFor(source) {
|
||||
const variants = [{ id: "default", when: {}, defines: [] }];
|
||||
// Programs with dynamic-tier code keep the tier's hidden classes current
|
||||
// in checked objects (two-tier design S4); every other program links the
|
||||
// ordinary object, byte for byte.
|
||||
// Programs with dynamic-tier code link the build that exports the
|
||||
// primitives the tier's inline caches use (two-tier design S4); every
|
||||
// other program links the ordinary object, byte for byte.
|
||||
if (source === "scr_json.c") {
|
||||
variants.push({ id: "dyn-tier", when: { dynTier: true }, defines: ["SCR_JS_TIER"] });
|
||||
}
|
||||
|
||||
@@ -5982,8 +5982,8 @@ void scr_arr_copy_metadata(ScrArr *a, ScrDyn *target) {
|
||||
target->v.arr.properties->v.obj.class_name = cls->name;
|
||||
ScrDyn *fields = scr_arr_class_fields(a);
|
||||
if (fields) {
|
||||
for (size_t i = 0; i < fields->v.obj.len; i++)
|
||||
scr_dyn_copy_entry(target, fields, &fields->v.obj.entries[i]);
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(fields); i++)
|
||||
scr_dyn_copy_prop_at(target, fields, i);
|
||||
scr_dyn_release(fields);
|
||||
}
|
||||
return;
|
||||
@@ -5994,7 +5994,7 @@ void scr_arr_copy_metadata(ScrArr *a, ScrDyn *target) {
|
||||
return;
|
||||
metadata = SCR_AX(a)->metadata;
|
||||
if (!metadata) return;
|
||||
for (size_t i = 0; i < metadata->v.obj.len; i++) scr_dyn_copy_entry(target, metadata, &metadata->v.obj.entries[i]);
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(metadata); i++) scr_dyn_copy_prop_at(target, metadata, i);
|
||||
}
|
||||
|
||||
ScrArr *scr_arr_named_keys(ScrArr *a) {
|
||||
@@ -6008,9 +6008,9 @@ ScrArr *scr_arr_named_keys(ScrArr *a) {
|
||||
scr_arr_push_ref(keys, scr_str_new(x->props[i].key, x->props[i].key_len));
|
||||
ScrDyn *fields = scr_arr_class_fields(a);
|
||||
if (fields) {
|
||||
for (size_t i = 0; i < fields->v.obj.len; i++) {
|
||||
const ScrDynEntry *entry = &fields->v.obj.entries[i];
|
||||
if (entry->enumerable) scr_arr_push_ref(keys, scr_str_new(entry->key, entry->key_len));
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(fields); i++) {
|
||||
ScrDynProp entry = scr_dyn_obj_prop_at(fields, i);
|
||||
if (entry.enumerable) scr_arr_push_ref(keys, scr_str_new(entry.key, entry.key_len));
|
||||
}
|
||||
scr_dyn_release(fields);
|
||||
return keys;
|
||||
@@ -6022,10 +6022,10 @@ ScrArr *scr_arr_named_keys(ScrArr *a) {
|
||||
return keys;
|
||||
}
|
||||
if (!metadata || metadata->kind != SCR_DYN_OBJ) return keys;
|
||||
for (size_t i = 0; i < metadata->v.obj.len; i++) {
|
||||
const ScrDynEntry *entry = &metadata->v.obj.entries[i];
|
||||
if (!entry->enumerable || scr_arr_prop_index(a, entry->key, entry->key_len) != SIZE_MAX) continue;
|
||||
scr_arr_push_ref(keys, scr_str_new(entry->key, entry->key_len));
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(metadata); i++) {
|
||||
ScrDynProp entry = scr_dyn_obj_prop_at(metadata, i);
|
||||
if (!entry.enumerable || scr_arr_prop_index(a, entry.key, entry.key_len) != SIZE_MAX) continue;
|
||||
scr_arr_push_ref(keys, scr_str_new(entry.key, entry.key_len));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
@@ -6038,9 +6038,9 @@ bool scr_arr_has_named_key(ScrArr *a, ScrStr *key) {
|
||||
if (metadata && metadata->kind == SCR_DYN_HANDLE && metadata->v.handle.tag == SCR_DYNH_MATCH_SOURCE)
|
||||
return true; /* index, input and groups, the only keys a pending result lists */
|
||||
if (!metadata || metadata->kind != SCR_DYN_OBJ) return false;
|
||||
for (size_t i = 0; i < metadata->v.obj.len; i++) {
|
||||
const ScrDynEntry *entry = &metadata->v.obj.entries[i];
|
||||
if (entry->key_len == key->len && memcmp(entry->key, key->data, key->len) == 0) return true;
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(metadata); i++) {
|
||||
ScrDynProp entry = scr_dyn_obj_prop_at(metadata, i);
|
||||
if (entry.key_len == key->len && memcmp(entry.key, key->data, key->len) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -6067,20 +6067,22 @@ void scr_arr_adopt_view_properties(ScrArr *a, ScrDyn *view, ScrArr *target) {
|
||||
from->prop_cap = cap;
|
||||
}
|
||||
ScrDyn *named = view && view->kind == SCR_DYN_ARR ? view->v.arr.properties : NULL;
|
||||
if (!named || named->v.obj.len == 0) return;
|
||||
if (!named || scr_dyn_obj_count(named) == 0) return;
|
||||
/* The view's numeric properties are copies of the native ones (every
|
||||
* write to them goes to the native array), so only the others move. */
|
||||
bool numeric = false;
|
||||
for (size_t i = 0; i < named->v.obj.len && !numeric; i++) {
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(named) && !numeric; i++) {
|
||||
double number;
|
||||
numeric = scr_arr_numeric_key(named->v.obj.entries[i].key, named->v.obj.entries[i].key_len, &number);
|
||||
size_t key_len;
|
||||
const char *key = scr_dyn_obj_key_at(named, i, &key_len);
|
||||
numeric = scr_arr_numeric_key(key, key_len, &number);
|
||||
}
|
||||
if (numeric) {
|
||||
ScrDyn *rest = scr_dyn_new_obj();
|
||||
for (size_t i = 0; i < named->v.obj.len; i++) {
|
||||
const ScrDynEntry *entry = &named->v.obj.entries[i];
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(named); i++) {
|
||||
ScrDynProp entry = scr_dyn_obj_prop_at(named, i);
|
||||
double number;
|
||||
if (!scr_arr_numeric_key(entry->key, entry->key_len, &number)) scr_dyn_copy_entry(rest, named, entry);
|
||||
if (!scr_arr_numeric_key(entry.key, entry.key_len, &number)) scr_dyn_copy_prop_at(rest, named, i);
|
||||
}
|
||||
named = rest;
|
||||
} else {
|
||||
@@ -6088,7 +6090,7 @@ void scr_arr_adopt_view_properties(ScrArr *a, ScrDyn *view, ScrArr *target) {
|
||||
}
|
||||
ScrArrExt *x = scr_arr_ext(a);
|
||||
ScrDyn *old = x->metadata;
|
||||
x->metadata = named->v.obj.len ? named : NULL;
|
||||
x->metadata = scr_dyn_obj_count(named) ? named : NULL;
|
||||
if (!x->metadata) scr_dyn_release(named);
|
||||
scr_dyn_release(old);
|
||||
}
|
||||
|
||||
@@ -718,7 +718,7 @@ static bool scr_assert_dyn_deep_eq(const ScrDyn *a, const ScrDyn *b) {
|
||||
if (a->v.arr.properties || b->v.arr.properties) {
|
||||
if (!a->v.arr.properties || !b->v.arr.properties) {
|
||||
const ScrDyn *table = a->v.arr.properties ? a->v.arr.properties : b->v.arr.properties;
|
||||
if (table->v.obj.len != 0) return false;
|
||||
if (scr_dyn_obj_count(table) != 0) return false;
|
||||
} else if (!scr_assert_dyn_deep_eq(a->v.arr.properties, b->v.arr.properties)) return false;
|
||||
}
|
||||
return true;
|
||||
@@ -738,11 +738,11 @@ static bool scr_assert_dyn_deep_eq(const ScrDyn *a, const ScrDyn *b) {
|
||||
} else if (a->null_proto != b->null_proto || a->prototype != b->prototype) {
|
||||
return false;
|
||||
}
|
||||
if (a->v.obj.len != b->v.obj.len) return false;
|
||||
for (size_t i = 0; i < a->v.obj.len; i++) {
|
||||
const ScrDynEntry *ent = &a->v.obj.entries[i];
|
||||
ScrDyn *bv = scr_dyn_obj_get(b, ent->key, ent->key_len);
|
||||
if (!bv || !scr_assert_dyn_deep_eq(ent->value, bv)) return false;
|
||||
if (scr_dyn_obj_count(a) != scr_dyn_obj_count(b)) return false;
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(a); i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(a, i);
|
||||
ScrDyn *bv = scr_dyn_obj_get(b, ent.key, ent.key_len);
|
||||
if (!bv || !scr_assert_dyn_deep_eq(ent.value, bv)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -782,30 +782,29 @@ static bool scr_assert_loose_nan(const ScrDyn *d) {
|
||||
return d->kind == SCR_DYN_NUM && d->v.num != d->v.num;
|
||||
}
|
||||
|
||||
/* An enumerable own entry, or NULL. Borrowed. */
|
||||
static const ScrDynEntry *scr_assert_loose_entry(const ScrDyn *d, const char *key, size_t len) {
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) {
|
||||
const ScrDynEntry *ent = &d->v.obj.entries[i];
|
||||
if (ent->key_len == len && memcmp(ent->key, key, len) == 0) return ent->enumerable ? ent : NULL;
|
||||
}
|
||||
return NULL;
|
||||
/* The value of an enumerable own property, or NULL. Borrowed. */
|
||||
static const ScrDyn *scr_assert_loose_entry(const ScrDyn *d, const char *key, size_t len) {
|
||||
size_t i = scr_dyn_obj_index_of(d, key, len);
|
||||
if (i == SIZE_MAX) return NULL;
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(d, i);
|
||||
return ent.enumerable ? ent.value : NULL;
|
||||
}
|
||||
|
||||
static bool scr_assert_loose_counted(const ScrDynEntry *ent) {
|
||||
return ent->enumerable && !(ent->key_len == 6 && memcmp(ent->key, "%error", 6) == 0);
|
||||
static bool scr_assert_loose_counted(ScrDynProp ent) {
|
||||
return ent.enumerable && !(ent.key_len == 6 && memcmp(ent.key, "%error", 6) == 0);
|
||||
}
|
||||
|
||||
/* keyCheck and objEquiv over two objects' enumerable own keys. */
|
||||
static bool scr_assert_loose_keys(const ScrDyn *a, const ScrDyn *b) {
|
||||
size_t na = 0, nb = 0;
|
||||
for (size_t i = 0; i < a->v.obj.len; i++) na += scr_assert_loose_counted(&a->v.obj.entries[i]);
|
||||
for (size_t i = 0; i < b->v.obj.len; i++) nb += scr_assert_loose_counted(&b->v.obj.entries[i]);
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(a); i++) na += scr_assert_loose_counted(scr_dyn_obj_prop_at(a, i));
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(b); i++) nb += scr_assert_loose_counted(scr_dyn_obj_prop_at(b, i));
|
||||
if (na != nb) return false;
|
||||
for (size_t i = 0; i < b->v.obj.len; i++) {
|
||||
const ScrDynEntry *ent = &b->v.obj.entries[i];
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(b); i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(b, i);
|
||||
if (!scr_assert_loose_counted(ent)) continue;
|
||||
const ScrDynEntry *other = scr_assert_loose_entry(a, ent->key, ent->key_len);
|
||||
if (!other || !scr_assert_loose_eq(other->value, ent->value)) return false;
|
||||
const ScrDyn *other = scr_assert_loose_entry(a, ent.key, ent.key_len);
|
||||
if (!other || !scr_assert_loose_eq(other, ent.value)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1180,8 +1179,8 @@ static bool scr_assert_loose_array(const ScrDyn *a, const ScrDyn *b) {
|
||||
if (pa || pb) {
|
||||
if (!pa || !pb) {
|
||||
const ScrDyn *table = pa ? pa : pb;
|
||||
for (size_t i = 0; i < table->v.obj.len; i++)
|
||||
if (scr_assert_loose_counted(&table->v.obj.entries[i])) return false;
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(table); i++)
|
||||
if (scr_assert_loose_counted(scr_dyn_obj_prop_at(table, i))) return false;
|
||||
} else if (!scr_assert_loose_keys(pa, pb)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1295,7 +1294,7 @@ static void scr_assert_cf_value(ScrAssertBuf *b, const ScrDyn *d, size_t indent,
|
||||
* or `{`, then its entries. */
|
||||
static void scr_assert_cf_object(ScrAssertBuf *b, const ScrDyn *d, size_t indent, size_t depth,
|
||||
const char *cls) {
|
||||
if (d->v.obj.len == 0) {
|
||||
if (scr_dyn_obj_count(d) == 0) {
|
||||
if (cls) {
|
||||
ab_cstr(b, cls);
|
||||
ab_cstr(b, " {}");
|
||||
@@ -1320,25 +1319,25 @@ static void scr_assert_cf_object(ScrAssertBuf *b, const ScrDyn *d, size_t indent
|
||||
} else if (d->null_proto) ab_cstr(b, "[Object: null prototype] ");
|
||||
/* Render every entry, then sort the RENDERED texts (Node's
|
||||
* sorted:true sorts formatted entries, quotes included). */
|
||||
ScrCfEntry *ents = malloc(d->v.obj.len * sizeof *ents);
|
||||
ScrCfEntry *ents = malloc(scr_dyn_obj_count(d) * sizeof *ents);
|
||||
if (!ents) scr_assert_oom();
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) {
|
||||
const ScrDynEntry *ent = &d->v.obj.entries[i];
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(d, i);
|
||||
ScrAssertBuf eb = {0};
|
||||
scr_assert_cf_key(&eb, ent->key, ent->key_len);
|
||||
scr_assert_cf_key(&eb, ent.key, ent.key_len);
|
||||
ab_cstr(&eb, ": ");
|
||||
scr_assert_cf_value(&eb, ent->value, indent + 2, depth - 1);
|
||||
scr_assert_cf_value(&eb, ent.value, indent + 2, depth - 1);
|
||||
ents[i].text = eb.data ? eb.data : malloc(1);
|
||||
if (!ents[i].text) scr_assert_oom();
|
||||
ents[i].len = eb.len;
|
||||
}
|
||||
qsort(ents, d->v.obj.len, sizeof *ents, scr_assert_cf_entry_cmp);
|
||||
qsort(ents, scr_dyn_obj_count(d), sizeof *ents, scr_assert_cf_entry_cmp);
|
||||
ab_char(b, '{');
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) {
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
|
||||
ab_char(b, '\n');
|
||||
scr_assert_cf_pad(b, indent + 2);
|
||||
ab_bytes(b, ents[i].text, ents[i].len);
|
||||
if (i + 1 < d->v.obj.len) ab_char(b, ',');
|
||||
if (i + 1 < scr_dyn_obj_count(d)) ab_char(b, ',');
|
||||
free(ents[i].text);
|
||||
}
|
||||
free(ents);
|
||||
@@ -1364,11 +1363,11 @@ static const char *scr_assert_cf_bytes_class(ScrBytesElem elem) {
|
||||
}
|
||||
|
||||
|
||||
static bool scr_assert_cf_error_extra(const ScrDynEntry *ent) {
|
||||
if (!ent->enumerable || (ent->key_len > 0 && ent->key[0] == '%')) return false;
|
||||
return !((ent->key_len == 4 && memcmp(ent->key, "name", 4) == 0) ||
|
||||
(ent->key_len == 7 && memcmp(ent->key, "message", 7) == 0) ||
|
||||
(ent->key_len == 5 && memcmp(ent->key, "stack", 5) == 0));
|
||||
static bool scr_assert_cf_error_extra(ScrDynProp ent) {
|
||||
if (!ent.enumerable || (ent.key_len > 0 && ent.key[0] == '%')) return false;
|
||||
return !((ent.key_len == 4 && memcmp(ent.key, "name", 4) == 0) ||
|
||||
(ent.key_len == 7 && memcmp(ent.key, "message", 7) == 0) ||
|
||||
(ent.key_len == 5 && memcmp(ent.key, "stack", 5) == 0));
|
||||
}
|
||||
|
||||
/* An encoded Error: Node's stackless `[Name: message]` (assertion_error.js
|
||||
@@ -1386,8 +1385,8 @@ static void scr_assert_cf_error(ScrAssertBuf *b, const ScrDyn *d, size_t indent,
|
||||
}
|
||||
ab_char(b, ']');
|
||||
size_t extra = 0;
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) {
|
||||
const ScrDynEntry *ent = &d->v.obj.entries[i];
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(d, i);
|
||||
if (scr_assert_cf_error_extra(ent)) extra++;
|
||||
}
|
||||
if (extra == 0) return;
|
||||
@@ -1395,13 +1394,13 @@ static void scr_assert_cf_error(ScrAssertBuf *b, const ScrDyn *d, size_t indent,
|
||||
ScrCfEntry *ents = malloc(extra * sizeof *ents);
|
||||
if (!ents) scr_assert_oom();
|
||||
size_t n = 0;
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) {
|
||||
const ScrDynEntry *ent = &d->v.obj.entries[i];
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(d, i);
|
||||
if (!scr_assert_cf_error_extra(ent)) continue;
|
||||
ScrAssertBuf eb = {0};
|
||||
scr_assert_cf_key(&eb, ent->key, ent->key_len);
|
||||
scr_assert_cf_key(&eb, ent.key, ent.key_len);
|
||||
ab_cstr(&eb, ": ");
|
||||
scr_assert_cf_value(&eb, ent->value, indent + 2, depth - 1);
|
||||
scr_assert_cf_value(&eb, ent.value, indent + 2, depth - 1);
|
||||
ents[n].text = eb.data ? eb.data : malloc(1);
|
||||
if (!ents[n].text) scr_assert_oom();
|
||||
ents[n++].len = eb.len;
|
||||
@@ -1989,11 +1988,11 @@ static bool scr_assert_partial_eq(const ScrDyn *a, const ScrDyn *b);
|
||||
|
||||
/* objEquiv over the expected side's enumerable own keys. */
|
||||
static bool scr_assert_partial_keys(const ScrDyn *a, const ScrDyn *b) {
|
||||
for (size_t i = 0; i < b->v.obj.len; i++) {
|
||||
const ScrDynEntry *ent = &b->v.obj.entries[i];
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(b); i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(b, i);
|
||||
if (!scr_assert_loose_counted(ent)) continue;
|
||||
const ScrDynEntry *other = scr_assert_loose_entry(a, ent->key, ent->key_len);
|
||||
if (!other || !scr_assert_partial_eq(other->value, ent->value)) return false;
|
||||
const ScrDyn *other = scr_assert_loose_entry(a, ent.key, ent.key_len);
|
||||
if (!other || !scr_assert_partial_eq(other, ent.value)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -2043,11 +2042,11 @@ static bool scr_assert_partial_array(const ScrDyn *a, const ScrDyn *b) {
|
||||
const ScrDyn *pb = b->v.arr.properties;
|
||||
if (pb) {
|
||||
const ScrDyn *pa = a->v.arr.properties;
|
||||
for (size_t i = 0; i < pb->v.obj.len; i++) {
|
||||
const ScrDynEntry *ent = &pb->v.obj.entries[i];
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(pb); i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(pb, i);
|
||||
if (!scr_assert_loose_counted(ent)) continue;
|
||||
const ScrDynEntry *other = pa ? scr_assert_loose_entry(pa, ent->key, ent->key_len) : NULL;
|
||||
if (!other || !scr_assert_partial_eq(other->value, ent->value)) return false;
|
||||
const ScrDyn *other = pa ? scr_assert_loose_entry(pa, ent.key, ent.key_len) : NULL;
|
||||
if (!other || !scr_assert_partial_eq(other, ent.value)) return false;
|
||||
}
|
||||
}
|
||||
size_t pos = 0;
|
||||
@@ -2434,11 +2433,11 @@ void scr_assert_loose_deep_result(bool equal, bool negated, ScrStr *msg, bool ha
|
||||
void scr_assert_expects_err_dyn(ScrDyn *actual, ScrDyn *expected, ScrStr *msg, bool has_msg) {
|
||||
if (actual->kind == SCR_DYN_OBJ && expected->kind == SCR_DYN_OBJ) {
|
||||
bool ok = true;
|
||||
for (size_t i = 0; i < expected->v.obj.len && ok; i++) {
|
||||
const ScrDynEntry *e = &expected->v.obj.entries[i];
|
||||
if (e->key_len == 6 && memcmp(e->key, "%error", 6) == 0) continue;
|
||||
ScrDyn *av = scr_dyn_obj_get(actual, e->key, e->key_len);
|
||||
if (av == NULL || !scr_assert_dyn_deep_eq(av, e->value)) ok = false;
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(expected) && ok; i++) {
|
||||
ScrDynProp e = scr_dyn_obj_prop_at(expected, i);
|
||||
if (e.key_len == 6 && memcmp(e.key, "%error", 6) == 0) continue;
|
||||
ScrDyn *av = scr_dyn_obj_get(actual, e.key, e.key_len);
|
||||
if (av == NULL || !scr_assert_dyn_deep_eq(av, e.value)) ok = false;
|
||||
}
|
||||
if (ok) return;
|
||||
}
|
||||
|
||||
@@ -441,8 +441,7 @@ ScrDyn *scr_buffer_constructor_value(void) {
|
||||
ScrDyn *table = scr_dyn_function_properties(scr_bv_buffer);
|
||||
ScrDyn *getter = scr_dyn_new_func(scr_closure_new(NULL, 0), scr_bv_prototype, 0, "native:function", "get prototype");
|
||||
scr_dyn_obj_set_accessor(table, "prototype", 9, getter, NULL);
|
||||
ScrDynEntry *entry = &table->v.obj.entries[table->v.obj.len - 1];
|
||||
entry->enumerable = entry->configurable = false;
|
||||
scr_dyn_obj_set_attrs_at(table, scr_dyn_obj_count(table) - 1, false, false, false);
|
||||
scr_dyn_obj_set(table, "poolSize", 8, scr_dyn_new_num(pool_size));
|
||||
ScrDyn *is_encoding = NULL;
|
||||
for (size_t i = 0; i < sizeof statics / sizeof statics[0]; i++) {
|
||||
|
||||
@@ -1757,11 +1757,11 @@ ScrStr *scr_fs_mkdtemp_sync_chk(const ScrDyn *prefix, const ScrDyn *opts, const
|
||||
if (!scr_fs_encoding_chk(opts)) return NULL;
|
||||
bool utf8 = true;
|
||||
if (opts->kind == SCR_DYN_OBJ) {
|
||||
for (size_t i = 0; i < opts->v.obj.len; i++) {
|
||||
const ScrDynEntry *e = &opts->v.obj.entries[i];
|
||||
if (scr_fs_dyn_absent(e->value)) continue;
|
||||
if (strcmp(e->key, "encoding") == 0) {
|
||||
if (!scr_fs_utf8_encoding(e->value)) utf8 = false;
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(opts); i++) {
|
||||
ScrDynProp e = scr_dyn_obj_prop_at(opts, i);
|
||||
if (scr_fs_dyn_absent(e.value)) continue;
|
||||
if (strcmp(e.key, "encoding") == 0) {
|
||||
if (!scr_fs_utf8_encoding(e.value)) utf8 = false;
|
||||
continue;
|
||||
}
|
||||
utf8 = false; /* an unmodeled effectful option */
|
||||
|
||||
@@ -507,8 +507,8 @@ static bool scr_console_holds_buffer(const ScrDyn *d) {
|
||||
if (scr_console_holds_buffer(d->v.arr.items[i])) return true;
|
||||
return false;
|
||||
case SCR_DYN_OBJ:
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) {
|
||||
const ScrDyn *value = d->v.obj.entries[i].value;
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
|
||||
const ScrDyn *value = scr_dyn_obj_value_at(d, i);
|
||||
if (value && scr_console_holds_buffer(value)) return true;
|
||||
}
|
||||
return false;
|
||||
@@ -626,7 +626,7 @@ static ScrStr **scr_table_own_keys(const ScrDyn *item, size_t *count) {
|
||||
return keys;
|
||||
}
|
||||
if (item->kind != SCR_DYN_OBJ) return NULL;
|
||||
size_t len = item->v.obj.len;
|
||||
size_t len = scr_dyn_obj_count(item);
|
||||
const char **names = calloc(len ? len : 1, sizeof *names);
|
||||
size_t *lens = calloc(len ? len : 1, sizeof *lens);
|
||||
size_t *order = calloc(len ? len : 1, sizeof *order);
|
||||
@@ -634,10 +634,10 @@ static ScrStr **scr_table_own_keys(const ScrDyn *item, size_t *count) {
|
||||
if (!names || !lens || !order || !keys) scr_trap("scriptc: out of memory\n");
|
||||
size_t n = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
const ScrDynEntry *ent = &item->v.obj.entries[i];
|
||||
if (!ent->enumerable) continue;
|
||||
names[n] = ent->key;
|
||||
lens[n] = ent->key_len;
|
||||
ScrDynProp prop = scr_dyn_obj_prop_at(item, i);
|
||||
if (!prop.enumerable) continue;
|
||||
names[n] = prop.key;
|
||||
lens[n] = prop.key_len;
|
||||
n++;
|
||||
}
|
||||
scr_table_key_order(names, lens, n, order);
|
||||
@@ -652,16 +652,14 @@ static ScrStr **scr_table_own_keys(const ScrDyn *item, size_t *count) {
|
||||
/* item[key] when it is an own property; NULL otherwise. +1. */
|
||||
static ScrDyn *scr_table_own_value(const ScrDyn *item, const ScrStr *key) {
|
||||
if (item->kind == SCR_DYN_OBJ) {
|
||||
for (size_t i = 0; i < item->v.obj.len; i++) {
|
||||
const ScrDynEntry *ent = &item->v.obj.entries[i];
|
||||
if (ent->key_len != key->len || memcmp(ent->key, key->data, key->len) != 0) continue;
|
||||
if (ent->accessor) {
|
||||
scr_table_fence("console.table rows with accessor properties have no lowering");
|
||||
return NULL;
|
||||
}
|
||||
return scr_dyn_retain(ent->value);
|
||||
size_t i = scr_dyn_obj_index_of(item, key->data, key->len);
|
||||
if (i == SIZE_MAX) return NULL;
|
||||
ScrDynProp prop = scr_dyn_obj_prop_at(item, i);
|
||||
if (prop.accessor) {
|
||||
scr_table_fence("console.table rows with accessor properties have no lowering");
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
return scr_dyn_retain(prop.value);
|
||||
}
|
||||
if (item->kind == SCR_DYN_ARR || item->kind == SCR_DYN_BYTES) {
|
||||
size_t len = item->kind == SCR_DYN_ARR ? item->v.arr.len : item->v.bytes->len;
|
||||
|
||||
@@ -63,10 +63,10 @@ static bool scr_decl_adopt_plain(const ScrDyn *n, const ScrDynPath *path, bool l
|
||||
else if (n->v.obj.source_access && !n->v.obj.source_identity)
|
||||
want = "a plain object";
|
||||
else {
|
||||
for (size_t i = 0; i < n->v.obj.len && !want; i++) {
|
||||
const ScrDynEntry *e = &n->v.obj.entries[i];
|
||||
if (e->accessor) want = "a plain object (this value has accessor properties)";
|
||||
else if (!e->writable || !e->enumerable || !e->configurable)
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(n) && !want; i++) {
|
||||
ScrDynProp e = scr_dyn_obj_prop_at(n, i);
|
||||
if (e.accessor) want = "a plain object (this value has accessor properties)";
|
||||
else if (!e.writable || !e.enumerable || !e.configurable)
|
||||
want = "a plain object (this value has non-default property attributes)";
|
||||
}
|
||||
}
|
||||
|
||||
+191
-27
@@ -514,9 +514,16 @@ struct ScrDyn {
|
||||
struct { ScrSym *value; void (*release)(ScrSym *); ScrStr *(*render)(ScrSym *); ScrStr *(*description)(ScrSym *); } symbol;
|
||||
struct { size_t len; size_t cap; ScrDyn **items; struct ScrDyn *properties; bool sealed; bool frozen; unsigned char *presence; struct ScrDynRegion *region; } arr; /* owned; NULL presence means dense; region: see ScrDynRegion */
|
||||
struct {
|
||||
size_t len;
|
||||
size_t cap;
|
||||
ScrDynEntry *entries;
|
||||
size_t len; /* own string-keyed properties, in both modes */
|
||||
size_t cap; /* capacity of entries (dictionary mode) or slots (layout mode) */
|
||||
/* Layout mode (`layout` non-NULL): slots[i] holds the value of the
|
||||
* property layout->keys[i] names, a writable, enumerable,
|
||||
* configurable data property. Dictionary mode (`layout` NULL):
|
||||
* entries hold keys, values and attributes. See ScrLayout. */
|
||||
union {
|
||||
ScrDynEntry *entries;
|
||||
struct ScrDyn **slots;
|
||||
};
|
||||
/* Optional identity of the typed record that produced this ordinary
|
||||
* snapshot. Empty records and callback interfaces can retain their
|
||||
* source across repeated static-to-dynamic crossings. */
|
||||
@@ -524,7 +531,7 @@ struct ScrDyn {
|
||||
* interfaces may request a fresh snapshot with the true arm. */
|
||||
void *source_identity;
|
||||
ScrDyn *(*source_access)(void *, bool materialize);
|
||||
struct ScrDynObjectIndex *index; /* optional wide-object lookup; owns no values */
|
||||
struct ScrDynObjectIndex *index; /* dictionary mode: optional wide-object lookup; owns no values */
|
||||
/* On a runtime-built class prototype (e.g. url.Url.prototype) and
|
||||
* on a compiled class's prototype object: the constructor name
|
||||
* (static or immortal text) util.inspect prints for objects
|
||||
@@ -533,19 +540,13 @@ struct ScrDyn {
|
||||
* exits, and those clear it before release; reuse of a freed object
|
||||
* resets it (scr_dyn_alloc). */
|
||||
const char *class_name;
|
||||
union {
|
||||
struct ScrDynRegion *region; /* see ScrDynRegion */
|
||||
/* While the node is pooled: the key bytes it keeps for reuse,
|
||||
* counted in the pool's key budget (scr_dyn_obj_drop_keys). */
|
||||
size_t cached_key_bytes;
|
||||
};
|
||||
/* The dynamic tier's hidden class (scr_js_object.c): the transition
|
||||
* tree node naming this object's key sequence, or NULL while it has
|
||||
* none. Only programs with dynamic-tier code read or maintain it:
|
||||
* their runtime is built with SCR_JS_TIER, where every allocation and
|
||||
* every change to the key set resets it. Other programs never look
|
||||
* at it, so it may hold anything there. */
|
||||
struct ScrJsShape *shape;
|
||||
struct ScrDynRegion *region; /* see ScrDynRegion */
|
||||
/* The object's layout in layout mode, NULL in dictionary mode. The
|
||||
* dynamic tier's inline caches key on it. */
|
||||
const struct ScrLayout *layout;
|
||||
/* Layout mode: bit i is the collector's snapshot of whether slot i's
|
||||
* value is traced (ScrDynEntry.value_traced in dictionary mode). */
|
||||
size_t slots_traced;
|
||||
} obj; /* owned */
|
||||
/* SCR_DYN_FUNC: the boxed closure (owned) + its call descriptor. `sig`
|
||||
* and `name` are static compiler-emitted literals (never freed); name
|
||||
@@ -624,10 +625,155 @@ _Static_assert(offsetof(ScrDyn, v.typed_ref.casts) == 16 + 8 * sizeof(void *),
|
||||
_Static_assert(sizeof(((ScrDyn *)0)->v.arr) <= sizeof(((ScrDyn *)0)->v.typed_ref) &&
|
||||
sizeof(((ScrDyn *)0)->v.obj) <= sizeof(((ScrDyn *)0)->v.typed_ref),
|
||||
"dyn region links must not grow the node");
|
||||
/* Dynamic-tier inline caches read the shape word in place (64-bit). */
|
||||
_Static_assert(sizeof(void *) != 8 || offsetof(ScrDyn, v.obj.shape) == 80, "v.obj.shape offset");
|
||||
/* Dynamic-tier inline caches read the layout word and slots in place (64-bit). */
|
||||
_Static_assert(sizeof(void *) != 8 || offsetof(ScrDyn, v.obj.layout) == 80, "v.obj.layout offset");
|
||||
_Static_assert(sizeof(void *) != 8 || offsetof(ScrDyn, v.obj.slots) == 32, "v.obj.slots offset");
|
||||
_Static_assert(sizeof(void *) != 8 || offsetof(ScrDyn, v.obj.region) == 72, "v.obj.region offset");
|
||||
|
||||
/* ── layouts ───────────────────────────────────────────────────────────
|
||||
* An ordinary object (SCR_DYN_OBJ) is in one of two modes.
|
||||
*
|
||||
* Layout mode: v.obj.layout names the object's key sequence, a node of the
|
||||
* thread's interned transition tree, and v.obj.slots holds one value per
|
||||
* key (8 bytes per property). Every property of a layout-mode object is a
|
||||
* writable, enumerable, configurable data property; objects of one key
|
||||
* sequence share one layout, so a layout pointer proves which key sits at
|
||||
* each slot and which keys the object lacks (the dynamic tier's inline
|
||||
* caches key on it). New objects start in layout mode at the thread's
|
||||
* empty layout.
|
||||
*
|
||||
* Dictionary mode: v.obj.layout is NULL and v.obj.entries holds keys,
|
||||
* values and attributes (ScrDynEntry). An object moves there, once and for
|
||||
* good, when it gets an accessor, a non-default attribute, a delete, more
|
||||
* than SCR_LAYOUT_MAX_KEYS keys, when the thread's layout budgets are
|
||||
* spent, when it is published to other threads, and whenever code that
|
||||
* reads entries directly touches it (scr_dyn_obj_dict_entries): the conversion
|
||||
* is unobservable, so code that has not been taught layouts stays correct.
|
||||
*
|
||||
* Layouts belong to one thread and live until its runtime state is torn
|
||||
* down; they are never owned by a parse region. Each holds one reference
|
||||
* to its key's bytes (scr_json.c's shared key storage). */
|
||||
#define SCR_LAYOUT_MAX_KEYS (8 * sizeof(size_t))
|
||||
|
||||
typedef struct ScrLayoutKey {
|
||||
char *key; /* shared key bytes; one reference belongs to the layout that added it */
|
||||
uint32_t len;
|
||||
uint32_t hash; /* low bits of scr_key_hash */
|
||||
} ScrLayoutKey;
|
||||
|
||||
typedef struct ScrLayout {
|
||||
const ScrLayoutKey *keys; /* keys[0..count); shared along a chain of layouts */
|
||||
uint32_t count;
|
||||
uint32_t lookup_mask; /* capacity - 1 of `lookup`; 0 while it has none */
|
||||
uint8_t *lookup; /* lazily built hash index: slot + 1, 0 = empty */
|
||||
const struct ScrLayout *parent;
|
||||
struct ScrLayout *last_child; /* the most recent transition from here */
|
||||
struct ScrLayoutKeys *store; /* the key array `keys` lives in */
|
||||
struct ScrLayout *all_next;
|
||||
bool owns_store;
|
||||
} ScrLayout;
|
||||
|
||||
/* The index of `key` in layout `layout`, or UINT32_MAX. */
|
||||
uint32_t scr_layout_find(const ScrLayout *layout, const char *key, size_t key_len);
|
||||
/* The layout that appends `key` (borrowed bytes) to `layout`, created on
|
||||
* first use; NULL past SCR_LAYOUT_MAX_KEYS or the thread's budgets. */
|
||||
const ScrLayout *scr_layout_add(const ScrLayout *layout, const char *key, size_t key_len);
|
||||
/* The thread's empty layout. */
|
||||
const ScrLayout *scr_layout_root(void);
|
||||
/* Moves a layout-mode object to dictionary mode (no-op otherwise). */
|
||||
void scr_dyn_to_dictionary(ScrDyn *object);
|
||||
|
||||
/* v.obj.entries of an object in dictionary mode, converting a layout-mode
|
||||
* object first: code that reads or writes entries directly goes through
|
||||
* this. */
|
||||
static inline __attribute__((always_inline)) ScrDynEntry *scr_dyn_obj_dict_entries(const ScrDyn *object) {
|
||||
if (__builtin_expect(object->v.obj.layout != NULL, 0)) scr_dyn_to_dictionary((ScrDyn *)object);
|
||||
return object->v.obj.entries;
|
||||
}
|
||||
|
||||
/* scr_dyn_obj_key_at and scr_dyn_obj_value_at out of line, for emitted
|
||||
* code (borrowed results). */
|
||||
const char *scr_dyn_member_key(const ScrDyn *object, size_t i);
|
||||
size_t scr_dyn_member_key_len(const ScrDyn *object, size_t i);
|
||||
ScrDyn *scr_dyn_member_value(const ScrDyn *object, size_t i);
|
||||
|
||||
/* ── own properties of ordinary objects ──────────────────────────────
|
||||
* The accessors are always inlined, so a binary that never sees an
|
||||
* ordinary object links no out-of-line copies. Code outside the object core (scr_json.c and scr_js_object.c) reads and
|
||||
* adjusts an SCR_DYN_OBJ's own string-keyed properties through these
|
||||
* accessors, never through v.obj.entries or v.obj.slots, so they work in
|
||||
* both modes. Index i runs over the properties in creation order,
|
||||
* 0 <= i < scr_dyn_obj_count. Keys and values are borrowed: they stay
|
||||
* valid until the object changes. */
|
||||
typedef struct {
|
||||
const char *key;
|
||||
size_t key_len;
|
||||
ScrDyn *value; /* data property; the undefined value for an accessor */
|
||||
ScrDyn *getter; /* accessor property; NULL when absent */
|
||||
ScrDyn *setter; /* accessor property; NULL when absent */
|
||||
bool accessor;
|
||||
bool writable;
|
||||
bool enumerable;
|
||||
bool configurable;
|
||||
} ScrDynProp;
|
||||
|
||||
static inline __attribute__((always_inline)) size_t scr_dyn_obj_count(const ScrDyn *o) {
|
||||
return o->v.obj.len;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline)) const char *scr_dyn_obj_key_at(const ScrDyn *o, size_t i, size_t *key_len) {
|
||||
const ScrLayout *layout = o->v.obj.layout;
|
||||
if (layout) {
|
||||
*key_len = layout->keys[i].len;
|
||||
return layout->keys[i].key;
|
||||
}
|
||||
*key_len = o->v.obj.entries[i].key_len;
|
||||
return o->v.obj.entries[i].key;
|
||||
}
|
||||
|
||||
/* The value of data property i (borrowed); the undefined value for an
|
||||
* accessor property, whose functions scr_dyn_obj_prop_at answers. */
|
||||
static inline __attribute__((always_inline)) ScrDyn *scr_dyn_obj_value_at(const ScrDyn *o, size_t i) {
|
||||
return o->v.obj.layout ? o->v.obj.slots[i] : o->v.obj.entries[i].value;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline)) ScrDynProp scr_dyn_obj_prop_at(const ScrDyn *o, size_t i) {
|
||||
const ScrLayout *layout = o->v.obj.layout;
|
||||
if (layout)
|
||||
return (ScrDynProp){layout->keys[i].key, layout->keys[i].len, o->v.obj.slots[i], NULL, NULL,
|
||||
false, true, true, true};
|
||||
const ScrDynEntry *e = &o->v.obj.entries[i];
|
||||
return (ScrDynProp){e->key, e->key_len, e->value, e->getter, e->setter,
|
||||
e->accessor, e->writable, e->enumerable, e->configurable};
|
||||
}
|
||||
|
||||
/* The index of own property `key`, or SIZE_MAX. A linear scan in
|
||||
* dictionary mode: for code that looks up options and headers, not for hot
|
||||
* property reads. */
|
||||
static inline size_t scr_dyn_obj_index_of(const ScrDyn *o, const char *key, size_t key_len) {
|
||||
if (o->v.obj.layout) {
|
||||
uint32_t i = scr_layout_find(o->v.obj.layout, key, key_len);
|
||||
return i == UINT32_MAX ? SIZE_MAX : i;
|
||||
}
|
||||
for (size_t i = 0; i < o->v.obj.len; i++) {
|
||||
const ScrDynEntry *e = &o->v.obj.entries[i];
|
||||
if (e->key_len == key_len && memcmp(e->key, key, key_len) == 0) return i;
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
/* Sets the attributes of property i (`writable` is ignored for an
|
||||
* accessor property): builtin objects the runtime builds restrict their
|
||||
* members this way right after adding them. */
|
||||
static inline void scr_dyn_obj_set_attrs_at(ScrDyn *o, size_t i, bool writable, bool enumerable,
|
||||
bool configurable) {
|
||||
if (o->v.obj.layout && writable && enumerable && configurable) return;
|
||||
ScrDynEntry *e = &scr_dyn_obj_dict_entries(o)[i];
|
||||
if (!e->accessor) e->writable = writable;
|
||||
e->enumerable = enumerable;
|
||||
e->configurable = configurable;
|
||||
}
|
||||
|
||||
/* ── intact parse regions ─────────────────────────────────────────────
|
||||
* Every heap dyn node carries a cycle header, so an ordinary release that
|
||||
* leaves an array or object alive buffers it as a possible cycle root, and
|
||||
@@ -792,6 +938,27 @@ ScrDyn *scr_dyn_get_own_property_symbols(const ScrDyn *value);
|
||||
/* The own property stored under a symbol key on an object (borrowed), or
|
||||
* NULL when it has none. */
|
||||
ScrDynEntry *scr_dyn_own_symbol_entry(const ScrDyn *object, const ScrDyn *key);
|
||||
/* The own symbol-keyed property `key` of `object` as a property view
|
||||
* (scr_dyn_obj_prop_at; `key` and `key_len` name its private storage
|
||||
* key); false when there is none. */
|
||||
static inline bool scr_dyn_own_symbol_prop(const ScrDyn *object, const ScrDyn *key, ScrDynProp *out) {
|
||||
const ScrDynEntry *e = scr_dyn_own_symbol_entry(object, key);
|
||||
if (!e) return false;
|
||||
*out = (ScrDynProp){e->key, e->key_len, e->value, e->getter, e->setter,
|
||||
e->accessor, e->writable, e->enumerable, e->configurable};
|
||||
return true;
|
||||
}
|
||||
/* Copies own property i of `source` to `target` (scr_dyn_copy_entry). */
|
||||
static inline void scr_dyn_copy_prop_at(ScrDyn *target, const ScrDyn *source, size_t i) {
|
||||
if (source->v.obj.layout) {
|
||||
ScrDynProp prop = scr_dyn_obj_prop_at(source, i);
|
||||
ScrDynEntry entry = {(char *)prop.key, prop.key_len, prop.value, NULL, NULL,
|
||||
false, false, false, true, true, true};
|
||||
scr_dyn_copy_entry(target, source, &entry);
|
||||
return;
|
||||
}
|
||||
scr_dyn_copy_entry(target, source, &source->v.obj.entries[i]);
|
||||
}
|
||||
/* The private storage name of a symbol key (+1). With it,
|
||||
* scr_dyn_symbol_init_own gives an object that has no symbol properties
|
||||
* yet its first one, a writable, enumerable, configurable data property
|
||||
@@ -2027,7 +2194,7 @@ void scr_dyn_trace_v(void *d, ScrTraceVisit visit, void *ctx);
|
||||
long scr_dyn_live_count(void);
|
||||
#endif
|
||||
|
||||
/* Checked-object primitives the dynamic tier's shapes and caches build on
|
||||
/* Checked-object primitives the dynamic tier's inline caches build on
|
||||
* (scr_js_object.c). They exist only in the SCR_JS_TIER build of
|
||||
* scr_json.c, which programs with dynamic-tier code link. */
|
||||
/* The builtin Object.prototype node, or NULL before its first use. */
|
||||
@@ -2035,12 +2202,9 @@ const ScrDyn *scr_dyn_tier_object_prototype(void);
|
||||
/* A read site's answer for a native array's length or a native iterator's
|
||||
* next (scr_js_get_probe_native): +1, or NULL. */
|
||||
ScrDyn *scr_dyn_tier_unshaped_get(const ScrDyn *receiver, const ScrStr *key);
|
||||
/* Shared key bytes for `key` (+1, NUL-terminated), and their release. */
|
||||
char *scr_dyn_tier_key_copy(const char *key, size_t key_len);
|
||||
void scr_dyn_tier_key_release(char *key, size_t key_len);
|
||||
/* Appends an own data property (writable, enumerable, configurable) that
|
||||
* `obj` does not have, sharing `key`'s bytes and taking ownership of
|
||||
* `value`, exactly as an ordinary insertion would; leaves the shape reset. */
|
||||
void scr_dyn_tier_append(ScrDyn *obj, char *key, size_t key_len, ScrDyn *value);
|
||||
/* Appends to a layout-mode object the property that `next`, a child of
|
||||
* its layout, adds, taking ownership of `value`, exactly as an ordinary
|
||||
* insertion would. */
|
||||
void scr_dyn_tier_append(ScrDyn *obj, const ScrLayout *next, ScrDyn *value);
|
||||
|
||||
#endif /* SCR_DYN_H */
|
||||
|
||||
@@ -60,17 +60,17 @@ static ScrDyn *hp_props_of(void *child) {
|
||||
return props;
|
||||
}
|
||||
|
||||
static ScrDynEntry *hp_entry(const ScrDyn *props, const char *key, size_t key_len) {
|
||||
if (!props) return NULL;
|
||||
for (size_t i = 0; i < props->v.obj.len; i++) {
|
||||
ScrDynEntry *e = &props->v.obj.entries[i];
|
||||
if (e->key_len == key_len && !memcmp(e->key, key, key_len)) return e;
|
||||
}
|
||||
return NULL;
|
||||
/* Own property `key` of the table object, in *out; false when absent. */
|
||||
static bool hp_entry(const ScrDyn *props, const char *key, size_t key_len, ScrDynProp *out) {
|
||||
if (!props) return false;
|
||||
size_t i = scr_dyn_obj_index_of(props, key, key_len);
|
||||
if (i == SIZE_MAX) return false;
|
||||
*out = scr_dyn_obj_prop_at(props, i);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool hp_has(const ScrDyn *props, const char *key, size_t key_len) {
|
||||
return hp_entry(props, key, key_len) != NULL;
|
||||
return props && scr_dyn_obj_index_of(props, key, key_len) != SIZE_MAX;
|
||||
}
|
||||
|
||||
/* A property the program added, read with the child as `this`: an
|
||||
@@ -78,10 +78,10 @@ static bool hp_has(const ScrDyn *props, const char *key, size_t key_len) {
|
||||
* it (`Object.defineProperty(child, "x", { get() { return this.pid; } })`).
|
||||
* +1, or NULL with the exception pending. */
|
||||
static ScrDyn *hp_read(ScrDyn *props, ScrDyn *self, const char *key, size_t key_len) {
|
||||
ScrDynEntry *e = hp_entry(props, key, key_len);
|
||||
if (!e || !e->accessor) return scr_dyn_obj_read(props, key, key_len);
|
||||
if (!e->getter) return scr_dyn_retain(scr_dyn_undefined());
|
||||
ScrDyn *getter = scr_dyn_retain(e->getter);
|
||||
ScrDynProp e;
|
||||
if (!hp_entry(props, key, key_len, &e) || !e.accessor) return scr_dyn_obj_read(props, key, key_len);
|
||||
if (!e.getter) return scr_dyn_retain(scr_dyn_undefined());
|
||||
ScrDyn *getter = scr_dyn_retain(e.getter);
|
||||
scr_dyn_this_push_dyn(self);
|
||||
ScrDyn *result = scr_dyn_call(getter, NULL, 0, "getter");
|
||||
scr_dyn_this_pop();
|
||||
@@ -132,7 +132,8 @@ static ScrDyn *hp_get(void *h, const char *key, size_t key_len) {
|
||||
|
||||
static bool hp_set(void *h, const char *key, size_t key_len, const ScrDyn *value) {
|
||||
ScrDyn *props = hp_find(h);
|
||||
ScrDynEntry *entry = hp_entry(props, key, key_len);
|
||||
ScrDynProp found;
|
||||
const ScrDynProp *entry = hp_entry(props, key, key_len, &found) ? &found : NULL;
|
||||
if (entry && entry->accessor) {
|
||||
/* An accessor's setter runs on the child. Without one the write throws
|
||||
* the strict-mode TypeError, as the other handle getters do. */
|
||||
|
||||
@@ -4821,14 +4821,14 @@ static ScrArr *sf_headers_convert_init(const ScrDyn *headers, bool *canonical_ou
|
||||
return converted;
|
||||
}
|
||||
if (headers->kind == SCR_DYN_OBJ) {
|
||||
for (size_t i = 0; i < headers->v.obj.len; i++) {
|
||||
const ScrDynEntry *e = &headers->v.obj.entries[i];
|
||||
ScrStr *raw_name = scr_str_new(e->key, e->key_len);
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(headers); i++) {
|
||||
ScrDynProp e = scr_dyn_obj_prop_at(headers, i);
|
||||
ScrStr *raw_name = scr_str_new(e.key, e.key_len);
|
||||
ScrStr *name_bytes = sf_header_value_bytestring(raw_name);
|
||||
ScrStr *name = name_bytes ? sf_latin1_to_utf8(name_bytes) : NULL;
|
||||
scr_str_release(name_bytes);
|
||||
scr_str_release(raw_name);
|
||||
ScrDyn *source = scr_dyn_retain(e->value);
|
||||
ScrDyn *source = scr_dyn_retain(e.value);
|
||||
ScrStr *raw_value = name ? scr_dyn_string_coerce_js(source) : NULL;
|
||||
scr_dyn_release(source);
|
||||
ScrStr *value_bytes = raw_value
|
||||
@@ -6088,11 +6088,11 @@ static int8_t sf_light_header_form(const ScrDyn *headers, bool json_body,
|
||||
}
|
||||
int8_t form = 0;
|
||||
if (headers->kind == SCR_DYN_OBJ) {
|
||||
ScrArr *out = scr_arr_new(SCR_ELEM_STR, 2 * headers->v.obj.len);
|
||||
ScrArr *out = scr_arr_new(SCR_ELEM_STR, 2 * scr_dyn_obj_count(headers));
|
||||
form = 3;
|
||||
for (size_t i = 0; i < headers->v.obj.len && form == 3; i++) {
|
||||
const ScrDynEntry *e = &headers->v.obj.entries[i];
|
||||
const ScrDyn *value = e->value;
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(headers) && form == 3; i++) {
|
||||
ScrDynProp e = scr_dyn_obj_prop_at(headers, i);
|
||||
const ScrDyn *value = e.value;
|
||||
size_t count = value->kind == SCR_DYN_ARR ? value->v.arr.len : 1;
|
||||
for (size_t j = 0; j < count; j++) {
|
||||
const ScrDyn *item = value->kind == SCR_DYN_ARR ? value->v.arr.items[j] : value;
|
||||
@@ -6106,7 +6106,7 @@ static int8_t sf_light_header_form(const ScrDyn *headers, bool json_body,
|
||||
form = 0;
|
||||
break;
|
||||
}
|
||||
scr_arr_push_ref(out, scr_str_new(e->key, e->key_len));
|
||||
scr_arr_push_ref(out, scr_str_new(e.key, e.key_len));
|
||||
scr_arr_push_ref(out, text);
|
||||
}
|
||||
}
|
||||
@@ -6666,27 +6666,27 @@ ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init) {
|
||||
return sf_reject_now(promise, "fetch failed");
|
||||
}
|
||||
if (init && init->kind == SCR_DYN_OBJ) {
|
||||
for (size_t i = 0; i < init->v.obj.len; i++) {
|
||||
const ScrDynEntry *entry = &init->v.obj.entries[i];
|
||||
bool supported = sf_name(entry->key, entry->key_len, "method") ||
|
||||
sf_name(entry->key, entry->key_len, "headers") ||
|
||||
sf_name(entry->key, entry->key_len, "body") ||
|
||||
sf_name(entry->key, entry->key_len, "duplex") ||
|
||||
sf_name(entry->key, entry->key_len, "redirect") ||
|
||||
sf_name(entry->key, entry->key_len, "signal");
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(init); i++) {
|
||||
ScrDynProp entry = scr_dyn_obj_prop_at(init, i);
|
||||
bool supported = sf_name(entry.key, entry.key_len, "method") ||
|
||||
sf_name(entry.key, entry.key_len, "headers") ||
|
||||
sf_name(entry.key, entry.key_len, "body") ||
|
||||
sf_name(entry.key, entry.key_len, "duplex") ||
|
||||
sf_name(entry.key, entry.key_len, "redirect") ||
|
||||
sf_name(entry.key, entry.key_len, "signal");
|
||||
bool recognized_unsupported =
|
||||
sf_name(entry->key, entry->key_len, "cache") ||
|
||||
sf_name(entry->key, entry->key_len, "credentials") ||
|
||||
sf_name(entry->key, entry->key_len, "dispatcher") ||
|
||||
sf_name(entry->key, entry->key_len, "integrity") ||
|
||||
sf_name(entry->key, entry->key_len, "keepalive") ||
|
||||
sf_name(entry->key, entry->key_len, "mode") ||
|
||||
sf_name(entry->key, entry->key_len, "priority") ||
|
||||
sf_name(entry->key, entry->key_len, "referrer") ||
|
||||
sf_name(entry->key, entry->key_len, "referrerPolicy") ||
|
||||
sf_name(entry->key, entry->key_len, "window");
|
||||
if (!supported && recognized_unsupported && entry->value &&
|
||||
entry->value->kind != SCR_DYN_UNDEF) {
|
||||
sf_name(entry.key, entry.key_len, "cache") ||
|
||||
sf_name(entry.key, entry.key_len, "credentials") ||
|
||||
sf_name(entry.key, entry.key_len, "dispatcher") ||
|
||||
sf_name(entry.key, entry.key_len, "integrity") ||
|
||||
sf_name(entry.key, entry.key_len, "keepalive") ||
|
||||
sf_name(entry.key, entry.key_len, "mode") ||
|
||||
sf_name(entry.key, entry.key_len, "priority") ||
|
||||
sf_name(entry.key, entry.key_len, "referrer") ||
|
||||
sf_name(entry.key, entry.key_len, "referrerPolicy") ||
|
||||
sf_name(entry.key, entry.key_len, "window");
|
||||
if (!supported && recognized_unsupported && entry.value &&
|
||||
entry.value->kind != SCR_DYN_UNDEF) {
|
||||
return sf_reject_now(promise, "fetch failed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2047,10 +2047,10 @@ void scr_http_res_write_head_dyn(ScrHttpRes *r, double status, const ScrDyn *hea
|
||||
scr_dyn_arg_type_fail("headers", "an instance of Object", headers);
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < headers->v.obj.len; i++) {
|
||||
const ScrDynEntry *e = &headers->v.obj.entries[i];
|
||||
const ScrDyn *v = e->value;
|
||||
ScrStr *name = scr_str_new(e->key, e->key_len);
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(headers); i++) {
|
||||
ScrDynProp e = scr_dyn_obj_prop_at(headers, i);
|
||||
const ScrDyn *v = e.value;
|
||||
ScrStr *name = scr_str_new(e.key, e.key_len);
|
||||
if (v->kind == SCR_DYN_STR) {
|
||||
scr_http_res_set_header(r, name, v->v.str);
|
||||
} else if (v->kind == SCR_DYN_NUM) {
|
||||
@@ -6200,10 +6200,10 @@ static bool scr_http_dynh_req_set(void *h, const char *key, size_t key_len, cons
|
||||
* consecutive same-name pairs). Numbers format through String(n). NULL =
|
||||
* a value this surface cannot honestly carry (exception pending). */
|
||||
static ScrArr *scr_http_dynh_pairs(const ScrDyn *headers) {
|
||||
ScrArr *pairs = scr_arr_new(SCR_ELEM_STR, headers->v.obj.len * 2);
|
||||
for (size_t i = 0; i < headers->v.obj.len; i++) {
|
||||
const ScrDynEntry *e = &headers->v.obj.entries[i];
|
||||
const ScrDyn *v = e->value;
|
||||
ScrArr *pairs = scr_arr_new(SCR_ELEM_STR, scr_dyn_obj_count(headers) * 2);
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(headers); i++) {
|
||||
ScrDynProp e = scr_dyn_obj_prop_at(headers, i);
|
||||
const ScrDyn *v = e.value;
|
||||
size_t reps = v->kind == SCR_DYN_ARR ? v->v.arr.len : 1;
|
||||
for (size_t k = 0; k < reps; k++) {
|
||||
const ScrDyn *one = v->kind == SCR_DYN_ARR ? v->v.arr.items[k] : v;
|
||||
@@ -6218,7 +6218,7 @@ static ScrArr *scr_http_dynh_pairs(const ScrDyn *headers) {
|
||||
scr_jb_puts(&b, "Invalid value \"");
|
||||
scr_jb_puts(&b, one->kind == SCR_DYN_UNDEF ? "undefined" : "object");
|
||||
scr_jb_puts(&b, "\" for header \"");
|
||||
for (size_t c = 0; c < e->key_len; c++) scr_jb_putc(&b, e->key[c]);
|
||||
for (size_t c = 0; c < e.key_len; c++) scr_jb_putc(&b, e.key[c]);
|
||||
scr_jb_putc(&b, '"');
|
||||
ScrStr *msg = scr_jb_finish(&b);
|
||||
scr_throw_error_msg_code(SCR_ERR_TYPE, msg->data, msg->len, "ERR_HTTP_INVALID_HEADER_VALUE");
|
||||
@@ -6226,7 +6226,7 @@ static ScrArr *scr_http_dynh_pairs(const ScrDyn *headers) {
|
||||
scr_arr_release(pairs);
|
||||
return NULL;
|
||||
}
|
||||
scr_arr_push_ref(pairs, scr_str_new(e->key, e->key_len));
|
||||
scr_arr_push_ref(pairs, scr_str_new(e.key, e.key_len));
|
||||
scr_arr_push_ref(pairs, vs);
|
||||
}
|
||||
}
|
||||
@@ -6945,8 +6945,8 @@ static ScrDyn *scr_http_dynh_agent_get(void *h, const char *key, size_t key_len)
|
||||
scr_dyn_arr_push(arr, item);
|
||||
}
|
||||
/* the list is most recent first: reverse each array into Node's order */
|
||||
for (size_t i = 0; i < obj->v.obj.len; i++) {
|
||||
ScrDyn *arr = obj->v.obj.entries[i].value;
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(obj); i++) {
|
||||
ScrDyn *arr = scr_dyn_obj_value_at(obj, i);
|
||||
for (size_t l = 0, r = arr->v.arr.len ? arr->v.arr.len - 1 : 0; l < r; l++, r--) {
|
||||
ScrDyn *t = arr->v.arr.items[l];
|
||||
arr->v.arr.items[l] = arr->v.arr.items[r];
|
||||
@@ -7449,13 +7449,13 @@ bool scr_http_req_opts_read(const ScrDyn *opts, bool secure, const char *api, Sc
|
||||
headers = headers_view;
|
||||
}
|
||||
if (headers != NULL && headers->kind == SCR_DYN_OBJ) {
|
||||
for (size_t i = 0; i < headers->v.obj.len; i++) {
|
||||
const ScrDynEntry *e = &headers->v.obj.entries[i];
|
||||
ScrStr *name = scr_str_new(e->key, e->key_len);
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(headers); i++) {
|
||||
ScrDynProp e = scr_dyn_obj_prop_at(headers, i);
|
||||
ScrStr *name = scr_str_new(e.key, e.key_len);
|
||||
ScrStr *label = scr_str_new("", 0);
|
||||
scr_http_validate_header_name(name, label);
|
||||
scr_str_release(label);
|
||||
const ScrDyn *v = e->value;
|
||||
const ScrDyn *v = e.value;
|
||||
if (!scr_exc_pending()) {
|
||||
if (v->kind == SCR_DYN_ARR) {
|
||||
for (size_t k = 0; k < v->v.arr.len && !scr_exc_pending(); k++)
|
||||
|
||||
@@ -2559,9 +2559,9 @@ ScrNetServer *scr_http2_create_secure_server_dyn(const ScrDyn *opts /*borrowed*/
|
||||
ScrHttpReqFn fn) {
|
||||
bool allow_http1 = false;
|
||||
if (opts != NULL && opts->kind == SCR_DYN_OBJ) {
|
||||
for (size_t i = 0; i < opts->v.obj.len; i++) {
|
||||
if (strcmp(opts->v.obj.entries[i].key, "allowHTTP1") == 0) {
|
||||
allow_http1 = scr_dyn_truthy(opts->v.obj.entries[i].value);
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(opts); i++) {
|
||||
if (strcmp(scr_dyn_obj_prop_at(opts, i).key, "allowHTTP1") == 0) {
|
||||
allow_http1 = scr_dyn_truthy(scr_dyn_obj_value_at(opts, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3105,15 +3105,15 @@ static void scr_h2_dyn_fire_goaway(ScrClosure *cb, double code, double last) {
|
||||
/* Flatten a dyn headers object into the flat pairs array (numbers via
|
||||
* String(n); the :status/:method/... pseudo-headers pass through). */
|
||||
static ScrArr *scr_h2_dom_to_pairs(const ScrDyn *headers) {
|
||||
ScrArr *pairs = scr_arr_new(SCR_ELEM_STR, headers->v.obj.len * 2);
|
||||
for (size_t i = 0; i < headers->v.obj.len; i++) {
|
||||
const ScrDynEntry *e = &headers->v.obj.entries[i];
|
||||
const ScrDyn *v = e->value;
|
||||
ScrArr *pairs = scr_arr_new(SCR_ELEM_STR, scr_dyn_obj_count(headers) * 2);
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(headers); i++) {
|
||||
ScrDynProp e = scr_dyn_obj_prop_at(headers, i);
|
||||
const ScrDyn *v = e.value;
|
||||
ScrStr *vs;
|
||||
if (v->kind == SCR_DYN_STR) vs = scr_str_retain(v->v.str);
|
||||
else if (v->kind == SCR_DYN_NUM) vs = scr_f64_to_scrstr(v->v.num);
|
||||
else continue; /* undefined/null header: dropped, Node's stance */
|
||||
scr_arr_push_ref(pairs, scr_str_new(e->key, e->key_len));
|
||||
scr_arr_push_ref(pairs, scr_str_new(e.key, e.key_len));
|
||||
scr_arr_push_ref(pairs, vs);
|
||||
}
|
||||
return pairs;
|
||||
|
||||
@@ -973,21 +973,23 @@ ScrStr *scr_insp_regex(ScrRegex *re) {
|
||||
* the k-th symbol key. */
|
||||
static size_t insp_view_property_slots(const ScrDyn *props) {
|
||||
if (!props) return 0;
|
||||
return props->v.obj.len + (props->symbol_keys ? props->symbol_keys->v.arr.len : 0);
|
||||
return scr_dyn_obj_count(props) + (props->symbol_keys ? props->symbol_keys->v.arr.len : 0);
|
||||
}
|
||||
|
||||
static ScrStr *insp_accessor_tag(const ScrDynEntry *ent);
|
||||
static ScrStr *insp_accessor_tag(const ScrDynProp *ent);
|
||||
static void insp_key_into(InspBuf *b, const char *key, size_t len);
|
||||
|
||||
/* Slot k's `key: value` entry when it is enumerable, else NULL. */
|
||||
static ScrStr *insp_view_property(ScrDyn *props, size_t k, double recurse, double depth) {
|
||||
const ScrDynEntry *ent;
|
||||
ScrDynProp found;
|
||||
const ScrDynProp *ent = NULL;
|
||||
ScrStr *key = NULL;
|
||||
if (k < props->v.obj.len) {
|
||||
ent = &props->v.obj.entries[k];
|
||||
if (k < scr_dyn_obj_count(props)) {
|
||||
found = scr_dyn_obj_prop_at(props, k);
|
||||
ent = &found;
|
||||
} else {
|
||||
ScrDyn *symbol = props->symbol_keys->v.arr.items[k - props->v.obj.len];
|
||||
ent = scr_dyn_own_symbol_entry(props, symbol);
|
||||
ScrDyn *symbol = props->symbol_keys->v.arr.items[k - scr_dyn_obj_count(props)];
|
||||
if (scr_dyn_own_symbol_prop(props, symbol, &found)) ent = &found;
|
||||
if (ent && ent->enumerable) key = scr_insp_dyn(symbol, recurse, depth);
|
||||
}
|
||||
if (!ent || !ent->enumerable) return NULL;
|
||||
@@ -1005,10 +1007,11 @@ static ScrStr *insp_view_property(ScrDyn *props, size_t k, double recurse, doubl
|
||||
static size_t insp_view_property_count(ScrDyn *props) {
|
||||
size_t count = 0;
|
||||
for (size_t k = 0; k < insp_view_property_slots(props); k++) {
|
||||
if (k < props->v.obj.len) count += props->v.obj.entries[k].enumerable;
|
||||
if (k < scr_dyn_obj_count(props)) count += scr_dyn_obj_prop_at(props, k).enumerable;
|
||||
else {
|
||||
const ScrDynEntry *ent = scr_dyn_own_symbol_entry(props, props->symbol_keys->v.arr.items[k - props->v.obj.len]);
|
||||
count += ent && ent->enumerable;
|
||||
ScrDynProp ent;
|
||||
count += scr_dyn_own_symbol_prop(props, props->symbol_keys->v.arr.items[k - scr_dyn_obj_count(props)], &ent) &&
|
||||
ent.enumerable;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
@@ -1157,11 +1160,11 @@ static ScrDyn *insp_error_cause(ScrError *e, bool *enumerable, ScrDyn **view) {
|
||||
ScrDyn *node = scr_errdyn_view_of(e);
|
||||
if (node != NULL && node->kind == SCR_DYN_TYPED_REF) node = *view = scr_dyn_typed_ref_materialize(node);
|
||||
if (node != NULL && node->kind == SCR_DYN_OBJ) {
|
||||
for (size_t i = 0; i < node->v.obj.len; i++) {
|
||||
ScrDynEntry *ent = &node->v.obj.entries[i];
|
||||
if (ent->key_len == 5 && memcmp(ent->key, "cause", 5) == 0) {
|
||||
*enumerable = ent->enumerable;
|
||||
return ent->value;
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(node); i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(node, i);
|
||||
if (ent.key_len == 5 && memcmp(ent.key, "cause", 5) == 0) {
|
||||
*enumerable = ent.enumerable;
|
||||
return ent.value;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
@@ -1176,9 +1179,9 @@ static ScrDyn *insp_error_cause(ScrError *e, bool *enumerable, ScrDyn **view) {
|
||||
* other non-enumerable property of an error. */
|
||||
bool scr_insp_error_bag_has_properties(ScrDyn *bag) {
|
||||
if (bag == NULL || bag->kind != SCR_DYN_OBJ) return false;
|
||||
for (size_t i = 0; i < bag->v.obj.len; i++) {
|
||||
const ScrDynEntry *ent = &bag->v.obj.entries[i];
|
||||
if (ent->enumerable && !(ent->key_len == 5 && memcmp(ent->key, "cause", 5) == 0)) return true;
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(bag); i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(bag, i);
|
||||
if (ent.enumerable && !(ent.key_len == 5 && memcmp(ent.key, "cause", 5) == 0)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1282,7 +1285,7 @@ ScrStr *scr_insp_error(ScrError *e, double recurse, double depth) {
|
||||
* (/^[a-zA-Z_][a-zA-Z_0-9]*$/, '__proto__' excepted), quoted otherwise. */
|
||||
/* util.inspect's text for an accessor property: [Getter], [Setter] or
|
||||
* [Getter/Setter], by which of the two are functions. +1. */
|
||||
static ScrStr *insp_accessor_tag(const ScrDynEntry *ent) {
|
||||
static ScrStr *insp_accessor_tag(const ScrDynProp *ent) {
|
||||
bool get = ent->getter && ent->getter->kind == SCR_DYN_FUNC;
|
||||
bool set = ent->setter && ent->setter->kind == SCR_DYN_FUNC;
|
||||
const char *text = get && set ? "[Getter/Setter]" : get ? "[Getter]" : set ? "[Setter]" : "undefined";
|
||||
@@ -1371,12 +1374,12 @@ static ScrDyn *insp_assertion_ellipsis(ScrDyn *value) {
|
||||
* Node's `{ ... }` tail. */
|
||||
/* An own non-enumerable entry of the error encoding, NULL when absent or
|
||||
* enumerable (an enumerable one prints with the other own keys). */
|
||||
static const ScrDynEntry *insp_error_hidden_entry(const ScrDyn *d, const char *key, size_t len) {
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) {
|
||||
const ScrDynEntry *ent = &d->v.obj.entries[i];
|
||||
if (ent->key_len == len && memcmp(ent->key, key, len) == 0) return ent->enumerable ? NULL : ent;
|
||||
}
|
||||
return NULL;
|
||||
static const ScrDynProp *insp_error_hidden_entry(const ScrDyn *d, const char *key, size_t len,
|
||||
ScrDynProp *out) {
|
||||
size_t i = scr_dyn_obj_index_of(d, key, len);
|
||||
if (i == SIZE_MAX) return NULL;
|
||||
*out = scr_dyn_obj_prop_at(d, i);
|
||||
return out->enumerable ? NULL : out;
|
||||
}
|
||||
|
||||
static ScrStr *scr_insp_dyn_error(ScrDyn *d, double recurse, double depth) {
|
||||
@@ -1390,19 +1393,20 @@ static ScrStr *scr_insp_dyn_error(ScrDyn *d, double recurse, double depth) {
|
||||
memcmp(name->v.str->data, "AssertionError", 14) == 0;
|
||||
double value_depth = assertion ? recurse : depth;
|
||||
size_t extra = 0;
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) {
|
||||
ScrDynEntry *ent = &d->v.obj.entries[i];
|
||||
if (!ent->enumerable) continue;
|
||||
if ((ent->key_len == 4 && memcmp(ent->key, "name", 4) == 0) ||
|
||||
(ent->key_len == 7 && memcmp(ent->key, "message", 7) == 0) ||
|
||||
(ent->key_len == 5 && memcmp(ent->key, "stack", 5) == 0))
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(d, i);
|
||||
if (!ent.enumerable) continue;
|
||||
if ((ent.key_len == 4 && memcmp(ent.key, "name", 4) == 0) ||
|
||||
(ent.key_len == 7 && memcmp(ent.key, "message", 7) == 0) ||
|
||||
(ent.key_len == 5 && memcmp(ent.key, "stack", 5) == 0))
|
||||
continue;
|
||||
extra++;
|
||||
}
|
||||
/* formatError shows a non-enumerable `cause`, and an AggregateError's
|
||||
* `errors` array, after the enumerable keys: `[cause]`, `[errors]`. */
|
||||
const ScrDynEntry *cause = insp_error_hidden_entry(d, "cause", 5);
|
||||
const ScrDynEntry *errors = insp_error_hidden_entry(d, "errors", 6);
|
||||
ScrDynProp cause_prop, errors_prop;
|
||||
const ScrDynProp *cause = insp_error_hidden_entry(d, "cause", 5, &cause_prop);
|
||||
const ScrDynProp *errors = insp_error_hidden_entry(d, "errors", 6, &errors_prop);
|
||||
if (errors && errors->value->kind != SCR_DYN_ARR) errors = NULL;
|
||||
extra += (cause != NULL) + (errors != NULL);
|
||||
InspBuf base = {0};
|
||||
@@ -1426,20 +1430,20 @@ static ScrStr *scr_insp_dyn_error(ScrDyn *d, double recurse, double depth) {
|
||||
if (extra == 0) return base_str;
|
||||
scr_insp_begin(recurse + 1);
|
||||
scr_insp_seen_push(d);
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) {
|
||||
ScrDynEntry *ent = &d->v.obj.entries[i];
|
||||
if (!ent->enumerable) continue;
|
||||
if ((ent->key_len == 4 && memcmp(ent->key, "name", 4) == 0) ||
|
||||
(ent->key_len == 7 && memcmp(ent->key, "message", 7) == 0) ||
|
||||
(ent->key_len == 5 && memcmp(ent->key, "stack", 5) == 0))
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(d, i);
|
||||
if (!ent.enumerable) continue;
|
||||
if ((ent.key_len == 4 && memcmp(ent.key, "name", 4) == 0) ||
|
||||
(ent.key_len == 7 && memcmp(ent.key, "message", 7) == 0) ||
|
||||
(ent.key_len == 5 && memcmp(ent.key, "stack", 5) == 0))
|
||||
continue;
|
||||
bool shortened = assertion && ((ent->key_len == 6 && memcmp(ent->key, "actual", 6) == 0) ||
|
||||
(ent->key_len == 8 && memcmp(ent->key, "expected", 8) == 0));
|
||||
ScrDyn *shown = shortened ? insp_assertion_ellipsis(ent->value) : scr_dyn_retain(ent->value);
|
||||
bool shortened = assertion && ((ent.key_len == 6 && memcmp(ent.key, "actual", 6) == 0) ||
|
||||
(ent.key_len == 8 && memcmp(ent.key, "expected", 8) == 0));
|
||||
ScrDyn *shown = shortened ? insp_assertion_ellipsis(ent.value) : scr_dyn_retain(ent.value);
|
||||
ScrStr *val = scr_insp_dyn(shown, recurse + 1, value_depth);
|
||||
scr_dyn_release(shown);
|
||||
InspBuf eb = {0};
|
||||
insp_key_into(&eb, ent->key, ent->key_len);
|
||||
insp_key_into(&eb, ent.key, ent.key_len);
|
||||
ib_cstr(&eb, ": ");
|
||||
ib_bytes(&eb, val->data, val->len);
|
||||
scr_str_release(val);
|
||||
@@ -1447,7 +1451,7 @@ static ScrStr *scr_insp_dyn_error(ScrDyn *d, double recurse, double depth) {
|
||||
scr_insp_entry(entry, false);
|
||||
scr_str_release(entry);
|
||||
}
|
||||
const ScrDynEntry *hidden[] = {cause, errors};
|
||||
const ScrDynProp *hidden[] = {cause, errors};
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
if (!hidden[i]) continue;
|
||||
ScrStr *val = scr_insp_dyn(hidden[i]->value, recurse + 1, value_depth);
|
||||
@@ -1690,9 +1694,9 @@ static ScrStr *scr_insp_dyn_collection(ScrDyn *d, double recurse, double depth,
|
||||
|
||||
/* An own symbol-keyed property of `d` (borrowed) this render shows, or
|
||||
* NULL: enumerable ones, and under showHidden the non-enumerable ones too. */
|
||||
static ScrDynEntry *scr_insp_symbol_entry(ScrDyn *d, size_t i) {
|
||||
ScrDynEntry *ent = scr_dyn_own_symbol_entry(d, d->symbol_keys->v.arr.items[i]);
|
||||
return ent && (ent->enumerable || g_show_hidden) ? ent : NULL;
|
||||
static bool scr_insp_symbol_entry(ScrDyn *d, size_t i, ScrDynProp *out) {
|
||||
return scr_dyn_own_symbol_prop(d, d->symbol_keys->v.arr.items[i], out) &&
|
||||
(out->enumerable || g_show_hidden);
|
||||
}
|
||||
|
||||
/* An ordinary object: `Name { key: value }` with its constructor name
|
||||
@@ -1700,15 +1704,21 @@ static ScrDynEntry *scr_insp_symbol_entry(ScrDyn *d, size_t i) {
|
||||
* enumerable string keys, then the own enumerable symbol keys. */
|
||||
/* An own entry this render shows: enumerable ones, and under showHidden
|
||||
* the non-enumerable ones too (never the encoding's `%` markers). */
|
||||
static bool insp_obj_shows(const ScrDynEntry *ent) {
|
||||
static bool insp_obj_shows(const ScrDynProp *ent) {
|
||||
return ent->enumerable || (g_show_hidden && !(ent->key_len && ent->key[0] == '%'));
|
||||
}
|
||||
|
||||
static ScrStr *scr_insp_obj(ScrDyn *d, double recurse, double depth, const char *cls) {
|
||||
size_t shown = 0;
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) shown += insp_obj_shows(&d->v.obj.entries[i]);
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(d, i);
|
||||
shown += insp_obj_shows(&ent);
|
||||
}
|
||||
size_t nsymbols = d->symbol_keys ? d->symbol_keys->v.arr.len : 0;
|
||||
for (size_t i = 0; i < nsymbols; i++) shown += scr_insp_symbol_entry(d, i) != NULL;
|
||||
for (size_t i = 0; i < nsymbols; i++) {
|
||||
ScrDynProp ent;
|
||||
shown += scr_insp_symbol_entry(d, i, &ent);
|
||||
}
|
||||
if (shown == 0) {
|
||||
if (cls) return scr_insp_class_tag(cls, " {}");
|
||||
return d->null_proto ? scr_str_new("[Object: null prototype] {}", 27)
|
||||
@@ -1720,17 +1730,17 @@ static ScrStr *scr_insp_obj(ScrDyn *d, double recurse, double depth, const char
|
||||
: insp_styled_lit("[Object]", 8, INSP_SPECIAL);
|
||||
}
|
||||
scr_insp_begin(recurse + 1);
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) {
|
||||
ScrDynEntry *ent = &d->v.obj.entries[i];
|
||||
if (!insp_obj_shows(ent)) continue;
|
||||
ScrStr *val = ent->accessor ? insp_accessor_tag(ent) : scr_insp_dyn(ent->value, recurse + 1, depth);
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(d, i);
|
||||
if (!insp_obj_shows(&ent)) continue;
|
||||
ScrStr *val = ent.accessor ? insp_accessor_tag(&ent) : scr_insp_dyn(ent.value, recurse + 1, depth);
|
||||
InspBuf eb = {0};
|
||||
if (ent->enumerable) {
|
||||
insp_key_into(&eb, ent->key, ent->key_len);
|
||||
if (ent.enumerable) {
|
||||
insp_key_into(&eb, ent.key, ent.key_len);
|
||||
} else {
|
||||
/* formatProperty's hidden key: the key's rendering in brackets. */
|
||||
ib_char(&eb, '[');
|
||||
insp_key_into(&eb, ent->key, ent->key_len);
|
||||
insp_key_into(&eb, ent.key, ent.key_len);
|
||||
ib_char(&eb, ']');
|
||||
}
|
||||
ib_cstr(&eb, ": ");
|
||||
@@ -1742,8 +1752,9 @@ static ScrStr *scr_insp_obj(ScrDyn *d, double recurse, double depth, const char
|
||||
if (scr_exc_pending()) break;
|
||||
}
|
||||
for (size_t i = 0; i < nsymbols; i++) {
|
||||
ScrDynEntry *ent = scr_insp_symbol_entry(d, i);
|
||||
if (!ent) continue;
|
||||
ScrDynProp found;
|
||||
if (!scr_insp_symbol_entry(d, i, &found)) continue;
|
||||
const ScrDynProp *ent = &found;
|
||||
ScrStr *key = scr_insp_dyn(d->symbol_keys->v.arr.items[i], recurse + 1, depth);
|
||||
ScrStr *val = ent->accessor ? insp_accessor_tag(ent) : scr_insp_dyn(ent->value, recurse + 1, depth);
|
||||
InspBuf eb = {0};
|
||||
@@ -2063,7 +2074,7 @@ static ScrDyn *insp_inspect_value(void) {
|
||||
ScrDyn *set = scr_dyn_new_func(scr_closure_new((void *)members[i], 0), insp_inspect_member_call, 1,
|
||||
"native:function", members[i]);
|
||||
scr_dyn_obj_set_accessor(table, members[i], strlen(members[i]), get, set);
|
||||
table->v.obj.entries[table->v.obj.len - 1].enumerable = false;
|
||||
scr_dyn_obj_set_attrs_at(table, scr_dyn_obj_count(table) - 1, true, false, true);
|
||||
}
|
||||
scr_dyn_release(table);
|
||||
}
|
||||
@@ -2100,15 +2111,15 @@ static ScrDyn *insp_user_options(double depth) {
|
||||
insp_opt_set(options, "getters", scr_dyn_new_bool(false));
|
||||
insp_opt_set(options, "numericSeparator", scr_dyn_new_bool(false));
|
||||
if (g_user_options && g_user_options->kind == SCR_DYN_OBJ) {
|
||||
for (size_t i = 0; i < g_user_options->v.obj.len; i++) {
|
||||
const ScrDynEntry *ent = &g_user_options->v.obj.entries[i];
|
||||
if (!ent->enumerable) continue;
|
||||
ScrDyn *value = scr_dyn_obj_read((ScrDyn *)g_user_options, ent->key, ent->key_len);
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(g_user_options); i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(g_user_options, i);
|
||||
if (!ent.enumerable) continue;
|
||||
ScrDyn *value = scr_dyn_obj_read((ScrDyn *)g_user_options, ent.key, ent.key_len);
|
||||
if (!value) {
|
||||
scr_dyn_release(options);
|
||||
return NULL;
|
||||
}
|
||||
scr_dyn_obj_set(options, ent->key, ent->key_len, value);
|
||||
scr_dyn_obj_set(options, ent.key, ent.key_len, value);
|
||||
}
|
||||
}
|
||||
return options;
|
||||
@@ -2343,9 +2354,12 @@ static ScrStr *scr_insp_dyn_func(ScrDyn *d, double recurse, double depth) {
|
||||
}
|
||||
size_t shown = 0;
|
||||
if (table && table->kind == SCR_DYN_OBJ) {
|
||||
for (size_t i = 0; i < table->v.obj.len; i++) shown += table->v.obj.entries[i].enumerable;
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(table); i++) shown += scr_dyn_obj_prop_at(table, i).enumerable;
|
||||
size_t nsymbols = table->symbol_keys ? table->symbol_keys->v.arr.len : 0;
|
||||
for (size_t i = 0; i < nsymbols; i++) shown += scr_insp_symbol_entry(table, i) != NULL;
|
||||
for (size_t i = 0; i < nsymbols; i++) {
|
||||
ScrDynProp ent;
|
||||
shown += scr_insp_symbol_entry(table, i, &ent);
|
||||
}
|
||||
}
|
||||
if (shown == 0) {
|
||||
scr_dyn_release(table);
|
||||
@@ -2498,7 +2512,10 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
|
||||
* own `length` comes first, and non-enumerable properties show too. */
|
||||
const ScrDyn *props = d->v.arr.properties;
|
||||
size_t nprops = 0;
|
||||
for (size_t i = 0; props && i < props->v.obj.len; i++) nprops += insp_obj_shows(&props->v.obj.entries[i]);
|
||||
for (size_t i = 0; props && i < scr_dyn_obj_count(props); i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(props, i);
|
||||
nprops += insp_obj_shows(&ent);
|
||||
}
|
||||
/* An instance of a class that extends Array: Node's `Name(length) `
|
||||
* prefix (scr_arr_copy_metadata names the class). */
|
||||
const char *cls = props ? props->v.obj.class_name : NULL;
|
||||
@@ -2562,15 +2579,15 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
|
||||
scr_str_release(s);
|
||||
entries++;
|
||||
}
|
||||
for (size_t k = 0; props && k < props->v.obj.len; k++) {
|
||||
const ScrDynEntry *ent = &props->v.obj.entries[k];
|
||||
if (!insp_obj_shows(ent)) continue;
|
||||
ScrStr *val = ent->accessor ? insp_accessor_tag(ent) : scr_insp_dyn(ent->value, recurse + 1, depth);
|
||||
for (size_t k = 0; props && k < scr_dyn_obj_count(props); k++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(props, k);
|
||||
if (!insp_obj_shows(&ent)) continue;
|
||||
ScrStr *val = ent.accessor ? insp_accessor_tag(&ent) : scr_insp_dyn(ent.value, recurse + 1, depth);
|
||||
InspBuf eb = {0};
|
||||
/* formatProperty's hidden key: the key's rendering in brackets. */
|
||||
if (!ent->enumerable) ib_char(&eb, '[');
|
||||
insp_key_into(&eb, ent->key, ent->key_len);
|
||||
if (!ent->enumerable) ib_char(&eb, ']');
|
||||
if (!ent.enumerable) ib_char(&eb, '[');
|
||||
insp_key_into(&eb, ent.key, ent.key_len);
|
||||
if (!ent.enumerable) ib_char(&eb, ']');
|
||||
ib_cstr(&eb, ": ");
|
||||
ib_bytes(&eb, val->data, val->len);
|
||||
scr_str_release(val);
|
||||
@@ -2753,8 +2770,8 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
|
||||
scr_str_release(iso);
|
||||
ScrStr *prefix = ib_take(&head);
|
||||
bool fields = false;
|
||||
for (size_t i = 0; i < materialized->v.obj.len && !fields; i++)
|
||||
fields = materialized->v.obj.entries[i].enumerable;
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(materialized) && !fields; i++)
|
||||
fields = scr_dyn_obj_prop_at(materialized, i).enumerable;
|
||||
if (!fields || recurse > depth) {
|
||||
scr_dyn_release(materialized);
|
||||
return prefix;
|
||||
@@ -3323,12 +3340,12 @@ bool scr_insp_format_read_options(const ScrDyn *options, ScrInspFormatOptions *o
|
||||
"maxArrayLength", "maxStringLength", "breakLength", "compact", "sorted",
|
||||
"getters", "numericSeparator", "stylize",
|
||||
};
|
||||
for (size_t i = 0; i < options->v.obj.len && !o->user_options; i++) {
|
||||
const ScrDynEntry *ent = &options->v.obj.entries[i];
|
||||
if (!ent->enumerable) continue;
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(options) && !o->user_options; i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(options, i);
|
||||
if (!ent.enumerable) continue;
|
||||
bool read = false;
|
||||
for (size_t k = 0; k < sizeof known / sizeof *known && !read; k++)
|
||||
read = ent->key_len == strlen(known[k]) && memcmp(ent->key, known[k], ent->key_len) == 0;
|
||||
read = ent.key_len == strlen(known[k]) && memcmp(ent.key, known[k], ent.key_len) == 0;
|
||||
if (!read) o->user_options = options;
|
||||
}
|
||||
}
|
||||
@@ -3436,10 +3453,10 @@ static ScrStr *insp_table_cell(ScrDyn *v) {
|
||||
}
|
||||
const char *base = v->null_proto ? "[Object: null prototype]" : "Object";
|
||||
size_t n = 0;
|
||||
for (size_t i = 0; i < v->v.obj.len; i++) {
|
||||
ScrDynEntry *ent = &v->v.obj.entries[i];
|
||||
if (!ent->enumerable) continue;
|
||||
if (ent->accessor) {
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(v); i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(v, i);
|
||||
if (!ent.enumerable) continue;
|
||||
if (ent.accessor) {
|
||||
static const char message[] = "console.table cells holding accessor properties have no lowering";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
|
||||
return scr_str_new("", 0);
|
||||
@@ -3463,12 +3480,12 @@ static ScrStr *insp_table_cell(ScrDyn *v) {
|
||||
}
|
||||
ScrStr *items[2];
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < v->v.obj.len; i++) {
|
||||
ScrDynEntry *ent = &v->v.obj.entries[i];
|
||||
if (!ent->enumerable) continue;
|
||||
ScrStr *val = scr_insp_dyn(ent->value, 1, 0);
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(v); i++) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(v, i);
|
||||
if (!ent.enumerable) continue;
|
||||
ScrStr *val = scr_insp_dyn(ent.value, 1, 0);
|
||||
InspBuf eb = {0};
|
||||
insp_key_into(&eb, ent->key, ent->key_len);
|
||||
insp_key_into(&eb, ent.key, ent.key_len);
|
||||
ib_cstr(&eb, ": ");
|
||||
ib_str(&eb, val);
|
||||
scr_str_release(val);
|
||||
|
||||
@@ -180,45 +180,37 @@ ScrJsVal scr_js_increment(ScrJsVal value, bool increment);
|
||||
double scr_js_to_number(ScrJsVal value);
|
||||
bool scr_js_truthy(ScrJsVal value);
|
||||
|
||||
/* ── shapes and inline caches (scr_js_object.c) ─────────────────────────
|
||||
* A checked object's hidden class (ScrDyn.v.obj.shape) names its key
|
||||
* sequence: a node of the thread's transition tree, whose depth is the
|
||||
* entry count and whose last key sits at entry index depth - 1. Shapes are
|
||||
* per thread and live until the thread's runtime state is torn down; a
|
||||
* thread's shape count and the key bytes they hold are bounded, and an
|
||||
* object whose key sequence would need a shape past either bound stays
|
||||
* unshaped, as do immortal objects, which threads share. The tier
|
||||
* computes an object's shape on a cache miss; the SCR_JS_TIER build of
|
||||
* scr_json.c resets it on every change to the key set.
|
||||
* Attributes stay in the entries and the prototype in the node: cache hits
|
||||
* test both live, so a shape only ever proves which key sits at which entry
|
||||
* index and which keys an object lacks.
|
||||
/* ── inline caches (scr_js_object.c) ───────────────────────────────────
|
||||
* Caches key on object layouts (scr_dyn.h): a layout-mode object's layout
|
||||
* names its key sequence, and all its properties are writable data
|
||||
* properties, so a layout proves which key sits at which slot and which
|
||||
* keys the object lacks. Dictionary-mode objects (layout NULL), including
|
||||
* immortal objects, which threads share, never hit. The prototype stays in
|
||||
* the node: cache hits test it live.
|
||||
*
|
||||
* Each property site of compiled dynamic-tier code owns one zeroed,
|
||||
* thread-local ScrJsIc with static storage: the reset that frees a
|
||||
* thread's shapes (at thread exit, and when a library session ends) clears
|
||||
* thread's layouts (at thread exit, and when a library session ends) clears
|
||||
* every record that holds entries. The code at the site is fixed: it tests
|
||||
* entry 0 inline and calls a probe for everything else. Probes never run
|
||||
* user code: they answer from plain data properties or return a miss
|
||||
* (NULL / false) with nothing done, and the site then runs its generic
|
||||
* path.
|
||||
*
|
||||
* load entry: shape = receiver shape; target = NULL for an own property
|
||||
* (slot indexes the receiver's entries), else the shape of
|
||||
* `holder`, the receiver's prototype, whose entry `slot` holds it.
|
||||
* store entry: shape = receiver shape before the store; target == shape
|
||||
* replaces entry `slot`; any other target adds the key as entry
|
||||
* `slot` and moves the receiver to `target`. An add requires the
|
||||
* receiver's prototype to be `holder` (NULL or an object whose
|
||||
* shape is holder_shape and that lacks the key).
|
||||
* keyed entry: slot only; a hit compares the key bytes stored there. */
|
||||
typedef struct ScrJsShape ScrJsShape;
|
||||
|
||||
* load entry: layout = receiver layout; target = NULL for an own
|
||||
* property (slot indexes the receiver's slots), else the layout of
|
||||
* `holder`, the receiver's prototype, whose slot `slot` holds it.
|
||||
* store entry: layout = receiver layout before the store; target ==
|
||||
* layout replaces slot `slot`; any other target adds the key as
|
||||
* slot `slot` and moves the receiver to `target`. An add requires
|
||||
* the receiver's prototype to be `holder` (NULL or an object whose
|
||||
* layout is holder_layout and that lacks the key).
|
||||
* keyed entry: slot only; a hit compares the key stored there. */
|
||||
typedef struct ScrJsIcEntry {
|
||||
const ScrJsShape *shape;
|
||||
const ScrJsShape *target;
|
||||
const ScrLayout *layout;
|
||||
const ScrLayout *target;
|
||||
const ScrDyn *holder;
|
||||
const ScrJsShape *holder_shape;
|
||||
const ScrLayout *holder_layout;
|
||||
size_t slot;
|
||||
} ScrJsIcEntry;
|
||||
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
/* scriptc runtime — the dynamic tier's hidden classes and inline caches
|
||||
* (scr_js.h; two-tier design stage S4).
|
||||
/* scriptc runtime — the dynamic tier's inline caches (scr_js.h; two-tier
|
||||
* design stage S4).
|
||||
*
|
||||
* Shapes are attached to the existing checked objects (ScrDyn OBJ nodes)
|
||||
* rather than replacing their entry storage: static code and the shared
|
||||
* runtime read those entries in place, so a second object representation
|
||||
* would have to be understood or converted at every boundary. A shape is a
|
||||
* node of the thread's transition tree over key sequences; an object's
|
||||
* shape proves which key sits at each entry index and which keys the
|
||||
* object lacks. Everything else a cache hit depends on is tested live: the
|
||||
* node kind, the accessor and writable bits of the entry it uses, and the
|
||||
* prototype pointer.
|
||||
* Caches key on object layouts (scr_dyn.h): a layout-mode object's layout
|
||||
* proves which key sits at each slot and which keys the object lacks, and
|
||||
* every property of such an object is a writable data property, so a hit
|
||||
* tests the layout word and, for prototype hits, the prototype pointer.
|
||||
* Dictionary-mode objects (layout NULL) never hit and take the generic
|
||||
* paths.
|
||||
*
|
||||
* Linked only into programs with dynamic-tier code (the dynTier runtime
|
||||
* feature), together with the SCR_JS_TIER build of scr_json.c, which
|
||||
* resets an object's shape on every change to its key set. */
|
||||
* feature). */
|
||||
#include "scr_js.h"
|
||||
#include "scr_key.h"
|
||||
|
||||
@@ -24,61 +20,26 @@
|
||||
* backend/llvm/js-ic.ts). */
|
||||
#if SCR_JS_IMMEDIATES
|
||||
_Static_assert(offsetof(ScrDyn, kind) == 8, "kind offset");
|
||||
_Static_assert(offsetof(ScrDyn, v.obj.len) == 16 && offsetof(ScrDyn, v.obj.entries) == 32,
|
||||
"object entry offsets");
|
||||
_Static_assert(offsetof(ScrDyn, v.obj.len) == 16 && offsetof(ScrDyn, v.obj.slots) == 32 &&
|
||||
offsetof(ScrDyn, v.obj.layout) == 80, "object slot offsets");
|
||||
_Static_assert(offsetof(ScrDyn, v.arr.len) == 16 && offsetof(ScrDyn, v.arr.items) == 32 &&
|
||||
offsetof(ScrDyn, v.arr.presence) == 56, "array element offsets");
|
||||
_Static_assert(offsetof(ScrDyn, prototype) == 96, "prototype offset");
|
||||
_Static_assert(sizeof(ScrDynEntry) == 48 && offsetof(ScrDynEntry, value) == 16 &&
|
||||
offsetof(ScrDynEntry, writable) == 41, "entry layout");
|
||||
_Static_assert(sizeof(ScrJsIcEntry) == 40 && sizeof(ScrJsIc) == 168, "cache layout");
|
||||
#endif
|
||||
|
||||
/* Objects with more entries stay unshaped (dictionary mode): their reads
|
||||
* take the generic path and its hash index. */
|
||||
#define SCR_JS_SHAPE_MAX 64
|
||||
|
||||
/* Shapes live until the thread exits or a library session ends, so a thread
|
||||
* creates at most this many, holding at most this many key bytes. Once
|
||||
* either budget is spent, objects whose key sequence has no shape yet stay
|
||||
* unshaped; shapes that exist keep serving. */
|
||||
#define SCR_JS_SHAPE_BUDGET ((size_t)1 << 16)
|
||||
#define SCR_JS_SHAPE_KEY_BYTES_BUDGET ((size_t)4 << 20)
|
||||
|
||||
struct ScrJsShape {
|
||||
const ScrJsShape *parent;
|
||||
char *key; /* one reference to shared key bytes (scr_json.c) */
|
||||
size_t key_len;
|
||||
uint64_t hash;
|
||||
uint32_t count; /* entries: the depth in the tree */
|
||||
ScrJsShape *all_next;
|
||||
};
|
||||
|
||||
/* The root is the empty key sequence. Transitions live in one open
|
||||
* addressing table per thread keyed by (parent, key). */
|
||||
static SCR_TL ScrJsShape scr_js_root;
|
||||
static SCR_TL ScrJsShape *scr_js_all_shapes;
|
||||
static SCR_TL ScrJsShape **scr_js_transitions;
|
||||
static SCR_TL size_t scr_js_transition_mask; /* capacity - 1; 0 when absent */
|
||||
static SCR_TL size_t scr_js_transition_count;
|
||||
static SCR_TL size_t scr_js_shape_key_bytes;
|
||||
static SCR_TL bool scr_js_shapes_registered;
|
||||
|
||||
/* The cache records that hold entries. Entries name shapes and prototype
|
||||
* objects, so the reset that frees the shapes also clears these records: a
|
||||
* library's next session runs the same sites with the same records. */
|
||||
/* The cache records that hold entries. Entries name layouts and prototype
|
||||
* objects, so the reset of a thread's runtime state (which frees its
|
||||
* layouts) also clears these records: a library's next session runs the
|
||||
* same sites with the same records. */
|
||||
static SCR_TL ScrJsIc **scr_js_filled_ics;
|
||||
static SCR_TL size_t scr_js_filled_count;
|
||||
static SCR_TL size_t scr_js_filled_capacity;
|
||||
|
||||
/* The shape word of an object whose key sequence has no shape within the
|
||||
* budgets. No cache entry names it, so its accesses take the probes,
|
||||
* which answer with a miss until the key set changes. */
|
||||
static ScrJsShape scr_js_unshaped;
|
||||
static SCR_TL bool scr_js_ics_registered;
|
||||
|
||||
/* The second-level cache of load sites whose own record is full: one entry
|
||||
* per (receiver shape, key bytes) slot, filled by the probe and checked like
|
||||
* a site entry. It names shapes, so the shape reset clears it. */
|
||||
* per (receiver layout, key bytes) slot, filled by the probe and checked
|
||||
* like a site entry. It names layouts, so the reset clears it. */
|
||||
#define SCR_JS_SHARED_LOADS 2048
|
||||
typedef struct ScrJsSharedLoad {
|
||||
const char *key;
|
||||
@@ -86,7 +47,7 @@ typedef struct ScrJsSharedLoad {
|
||||
} ScrJsSharedLoad;
|
||||
static SCR_TL ScrJsSharedLoad *scr_js_shared_loads;
|
||||
|
||||
static void scr_js_shapes_reset(void) {
|
||||
static void scr_js_ics_reset(void) {
|
||||
for (size_t i = 0; i < scr_js_filled_count; i++)
|
||||
memset(scr_js_filled_ics[i], 0, sizeof *scr_js_filled_ics[i]);
|
||||
free(scr_js_shared_loads);
|
||||
@@ -95,130 +56,33 @@ static void scr_js_shapes_reset(void) {
|
||||
scr_js_filled_ics = NULL;
|
||||
scr_js_filled_count = 0;
|
||||
scr_js_filled_capacity = 0;
|
||||
while (scr_js_all_shapes) {
|
||||
ScrJsShape *shape = scr_js_all_shapes;
|
||||
scr_js_all_shapes = shape->all_next;
|
||||
scr_dyn_tier_key_release(shape->key, shape->key_len);
|
||||
free(shape);
|
||||
}
|
||||
free(scr_js_transitions);
|
||||
scr_js_transitions = NULL;
|
||||
scr_js_transition_mask = 0;
|
||||
scr_js_transition_count = 0;
|
||||
scr_js_shape_key_bytes = 0;
|
||||
scr_js_shapes_registered = false;
|
||||
scr_js_ics_registered = false;
|
||||
}
|
||||
|
||||
static size_t scr_js_transition_slot(const ScrJsShape *parent, uint64_t hash) {
|
||||
return (size_t)scr_key_mix(hash ^ (uint64_t)(uintptr_t)parent);
|
||||
}
|
||||
|
||||
static void scr_js_transition_insert(ScrJsShape *child) {
|
||||
size_t i = scr_js_transition_slot(child->parent, child->hash) & scr_js_transition_mask;
|
||||
while (scr_js_transitions[i]) i = (i + 1) & scr_js_transition_mask;
|
||||
scr_js_transitions[i] = child;
|
||||
}
|
||||
|
||||
static void scr_js_transitions_grow(void) {
|
||||
size_t old_cap = scr_js_transition_mask ? scr_js_transition_mask + 1 : 0;
|
||||
size_t cap = old_cap ? old_cap * 2 : 256;
|
||||
ScrJsShape **old = scr_js_transitions;
|
||||
scr_js_transitions = calloc(cap, sizeof *scr_js_transitions);
|
||||
if (!scr_js_transitions) scr_trap("scriptc: out of memory\n");
|
||||
scr_js_transition_mask = cap - 1;
|
||||
for (size_t i = 0; i < old_cap; i++)
|
||||
if (old[i]) scr_js_transition_insert(old[i]);
|
||||
free(old);
|
||||
}
|
||||
|
||||
/* The child of `parent` for appending `key` (borrowed bytes), created on
|
||||
* first use; NULL when creating it would exceed a budget. */
|
||||
static const ScrJsShape *scr_js_transition(const ScrJsShape *parent, const char *key, size_t key_len) {
|
||||
uint64_t hash = scr_key_hash(key, key_len);
|
||||
if (scr_js_transition_mask) {
|
||||
size_t i = scr_js_transition_slot(parent, hash) & scr_js_transition_mask;
|
||||
for (ScrJsShape *child; (child = scr_js_transitions[i]); i = (i + 1) & scr_js_transition_mask) {
|
||||
if (child->parent == parent && child->hash == hash && child->key_len == key_len &&
|
||||
scr_key_equal(child->key, key, key_len))
|
||||
return child;
|
||||
}
|
||||
}
|
||||
if (scr_js_transition_count >= SCR_JS_SHAPE_BUDGET ||
|
||||
key_len > SCR_JS_SHAPE_KEY_BYTES_BUDGET - scr_js_shape_key_bytes)
|
||||
return NULL;
|
||||
if ((scr_js_transition_count + 1) * 2 > (scr_js_transition_mask ? scr_js_transition_mask + 1 : 0))
|
||||
scr_js_transitions_grow();
|
||||
ScrJsShape *child = malloc(sizeof *child);
|
||||
if (!child) scr_trap("scriptc: out of memory\n");
|
||||
*child = (ScrJsShape){parent, scr_dyn_tier_key_copy(key, key_len), key_len, hash, parent->count + 1,
|
||||
scr_js_all_shapes};
|
||||
scr_js_all_shapes = child;
|
||||
if (!scr_js_shapes_registered) {
|
||||
scr_js_shapes_registered = true;
|
||||
scr_atexit(scr_js_shapes_reset);
|
||||
}
|
||||
scr_js_transition_insert(child);
|
||||
scr_js_transition_count++;
|
||||
scr_js_shape_key_bytes += key_len;
|
||||
return child;
|
||||
}
|
||||
|
||||
/* An object's shape, computed from its entries when it has none; NULL for
|
||||
* objects that stay unshaped (other kinds, immortal objects, the builtin
|
||||
* Object.prototype, dictionary-sized objects, key sequences past the
|
||||
* budgets). Immortal objects (rc == SIZE_MAX: published graphs, which
|
||||
* threads share by pointer) are never shaped: shapes belong to one thread
|
||||
* and die with it, and no thread writes an immortal object's header. */
|
||||
#ifdef SCR_JS_VERIFY
|
||||
/* Debugging aid (build the runtime with -DSCR_JS_VERIFY and compile with
|
||||
* SCRIPTC_JS_IC_WAYS=0 so every access probes): traps when an object's
|
||||
* shape no longer names its key sequence, i.e. when some mutation of the
|
||||
* key set missed its SCR_JS_SHAPE_RESET. */
|
||||
static void scr_js_verify(const ScrDyn *object, const ScrJsShape *shape) {
|
||||
size_t len = object->v.obj.len;
|
||||
if (shape->count != len) scr_trap("scriptc: stale shape (entry count)\n");
|
||||
for (const ScrJsShape *s = shape; s->parent; s = s->parent) {
|
||||
const ScrDynEntry *entry = &object->v.obj.entries[s->count - 1];
|
||||
if (entry->key_len != s->key_len || !scr_key_equal(entry->key, s->key, s->key_len))
|
||||
scr_trap("scriptc: stale shape (key)\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static const ScrJsShape *scr_js_shape_of(ScrDyn *object) {
|
||||
/* An object's layout, or NULL for objects caches leave alone: other kinds,
|
||||
* dictionary-mode objects (immortal objects, which threads share, are
|
||||
* always in dictionary mode), and the builtin Object.prototype. */
|
||||
static inline const ScrLayout *scr_js_layout_of(const ScrDyn *object) {
|
||||
if (object->kind != SCR_DYN_OBJ || object->rc == SIZE_MAX) return NULL;
|
||||
const ScrJsShape *shape = object->v.obj.shape;
|
||||
if (shape == &scr_js_unshaped) return NULL;
|
||||
#ifdef SCR_JS_VERIFY
|
||||
if (shape) scr_js_verify(object, shape);
|
||||
#endif
|
||||
if (shape) return shape;
|
||||
size_t len = object->v.obj.len;
|
||||
if (len > SCR_JS_SHAPE_MAX || object == scr_dyn_tier_object_prototype()) return NULL;
|
||||
shape = &scr_js_root;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
ScrDynEntry *entry = &object->v.obj.entries[i];
|
||||
shape = scr_js_transition(shape, entry->key, entry->key_len);
|
||||
if (!shape) {
|
||||
object->v.obj.shape = &scr_js_unshaped;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
object->v.obj.shape = (ScrJsShape *)shape;
|
||||
return shape;
|
||||
const ScrLayout *layout = object->v.obj.layout;
|
||||
if (!layout || object == scr_dyn_tier_object_prototype()) return NULL;
|
||||
return layout;
|
||||
}
|
||||
|
||||
/* The entry index of `key` in an object of shape `shape`, or SIZE_MAX. */
|
||||
static size_t scr_js_shape_find(const ScrJsShape *shape, const char *key, size_t key_len) {
|
||||
for (; shape->parent; shape = shape->parent)
|
||||
if (shape->key_len == key_len && scr_key_equal(shape->key, key, key_len)) return shape->count - 1;
|
||||
return SIZE_MAX;
|
||||
/* The slot of `key` in an object of layout `layout`, or SIZE_MAX. */
|
||||
static size_t scr_js_layout_find(const ScrLayout *layout, const char *key, size_t key_len) {
|
||||
uint32_t slot = scr_layout_find(layout, key, key_len);
|
||||
return slot == UINT32_MAX ? SIZE_MAX : slot;
|
||||
}
|
||||
|
||||
/* Fills a cache entry: `stale` (a matching entry whose hit failed) when
|
||||
* there is one, else a free or replaceable one. */
|
||||
static void scr_js_ic_add(ScrJsIc *ic, uint32_t stale, ScrJsIcEntry entry) {
|
||||
if (ic->count == 0) {
|
||||
if (!scr_js_ics_registered) {
|
||||
scr_js_ics_registered = true;
|
||||
scr_atexit(scr_js_ics_reset);
|
||||
}
|
||||
if (scr_js_filled_count == scr_js_filled_capacity) {
|
||||
size_t capacity = scr_js_filled_capacity ? scr_js_filled_capacity * 2 : 64;
|
||||
ScrJsIc **grown = realloc(scr_js_filled_ics, capacity * sizeof *grown);
|
||||
@@ -243,26 +107,25 @@ static void scr_js_ic_add(ScrJsIc *ic, uint32_t stale, ScrJsIcEntry entry) {
|
||||
|
||||
/* ── loads ────────────────────────────────────────────────────────────── */
|
||||
|
||||
/* The cached answer of an entry whose receiver shape matched, or NULL. */
|
||||
/* The cached answer of an entry whose receiver layout matched, or NULL. */
|
||||
static ScrDyn *scr_js_load_hit(ScrDyn *receiver, const ScrJsIcEntry *entry) {
|
||||
const ScrDyn *holder = receiver;
|
||||
if (entry->target) {
|
||||
holder = entry->holder;
|
||||
if (receiver->prototype != holder || holder->kind != SCR_DYN_OBJ ||
|
||||
holder->v.obj.shape != entry->target)
|
||||
holder->v.obj.layout != entry->target)
|
||||
return NULL;
|
||||
}
|
||||
const ScrDynEntry *found = &holder->v.obj.entries[entry->slot];
|
||||
return found->accessor ? NULL : scr_dyn_retain(found->value);
|
||||
return scr_dyn_retain(holder->v.obj.slots[entry->slot]);
|
||||
}
|
||||
|
||||
/* An absent entry: its target is this tag and its holder is a placeholder
|
||||
* that is never a prototype, so the inline test of entry 0 in compiled code
|
||||
* (a prototype hit needs the receiver's prototype to be the holder) always
|
||||
* sends such a receiver to the probe. Its holder_shape and slot name the one
|
||||
* prototype the read walked past (shape and object), or NULL and 0 when the
|
||||
* receiver had no prototype. */
|
||||
static const ScrJsShape scr_js_absent;
|
||||
* sends such a receiver to the probe. Its holder_layout and slot name the
|
||||
* one prototype the read walked past (layout and object), or NULL and 0 when
|
||||
* the receiver had no prototype. */
|
||||
static const ScrLayout scr_js_absent;
|
||||
static const ScrDyn scr_js_absent_holder;
|
||||
|
||||
/* Keys whose read on a plain object is not just its [[Get]]: the members of
|
||||
@@ -288,7 +151,7 @@ static bool scr_js_absent_cacheable(const ScrStr *key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static ScrJsSharedLoad *scr_js_shared_load(const ScrJsShape *shape, const ScrStr *key) {
|
||||
static ScrJsSharedLoad *scr_js_shared_load(const ScrLayout *shape, const ScrStr *key) {
|
||||
if (!scr_js_shared_loads) {
|
||||
scr_js_shared_loads = calloc(SCR_JS_SHARED_LOADS, sizeof *scr_js_shared_loads);
|
||||
if (!scr_js_shared_loads) scr_trap("scriptc: out of memory\n");
|
||||
@@ -305,7 +168,7 @@ static bool scr_js_absent_hit(const ScrDyn *receiver, const ScrJsIcEntry *entry)
|
||||
const ScrDyn *walked = (const ScrDyn *)entry->slot;
|
||||
if (!walked) return prototype == NULL;
|
||||
return prototype == walked && walked->kind == SCR_DYN_OBJ &&
|
||||
walked->v.obj.shape == entry->holder_shape && walked->prototype == NULL;
|
||||
walked->v.obj.layout == entry->holder_layout && walked->prototype == NULL;
|
||||
}
|
||||
|
||||
/* The answer of a matching entry for `receiver`, or NULL when the entry no
|
||||
@@ -344,11 +207,11 @@ static void scr_js_load_fill(ScrJsIc *ic, uint32_t stale, ScrJsSharedLoad *share
|
||||
* methods to the slow path, which names the missing function. */
|
||||
static __attribute__((noinline)) ScrDyn *scr_js_probe_load(ScrDyn *receiver, const ScrStr *key,
|
||||
ScrJsIc *ic, bool absent) {
|
||||
const ScrJsShape *shape = scr_js_shape_of(receiver);
|
||||
const ScrLayout *shape = scr_js_layout_of(receiver);
|
||||
if (!shape) return NULL;
|
||||
uint32_t stale = UINT32_MAX;
|
||||
for (uint32_t i = 0; i < ic->count; i++) {
|
||||
if (ic->entry[i].shape != shape) continue;
|
||||
if (ic->entry[i].layout != shape) continue;
|
||||
ScrDyn *value = scr_js_entry_answer(receiver, &ic->entry[i]);
|
||||
if (value) return value;
|
||||
if (stale == UINT32_MAX) stale = i;
|
||||
@@ -356,18 +219,16 @@ static __attribute__((noinline)) ScrDyn *scr_js_probe_load(ScrDyn *receiver, con
|
||||
ScrJsSharedLoad *shared = NULL;
|
||||
if (ic->count == SCR_JS_IC_WAYS) {
|
||||
shared = scr_js_shared_load(shape, key);
|
||||
if (shared->key == key->data && shared->entry.shape == shape &&
|
||||
if (shared->key == key->data && shared->entry.layout == shape &&
|
||||
(absent || shared->entry.target != &scr_js_absent)) {
|
||||
ScrDyn *value = scr_js_entry_answer(receiver, &shared->entry);
|
||||
if (value) return value;
|
||||
}
|
||||
}
|
||||
size_t slot = scr_js_shape_find(shape, key->data, key->len);
|
||||
size_t slot = scr_js_layout_find(shape, key->data, key->len);
|
||||
if (slot != SIZE_MAX) {
|
||||
const ScrDynEntry *found = &receiver->v.obj.entries[slot];
|
||||
if (found->accessor) return NULL;
|
||||
scr_js_load_fill(ic, stale, shared, key, (ScrJsIcEntry){shape, NULL, NULL, NULL, slot});
|
||||
return scr_dyn_retain(found->value);
|
||||
return scr_dyn_retain(receiver->v.obj.slots[slot]);
|
||||
}
|
||||
absent = absent && scr_js_absent_cacheable(key);
|
||||
/* One prototype link: an ordinary object other than the builtin
|
||||
@@ -379,9 +240,9 @@ static __attribute__((noinline)) ScrDyn *scr_js_probe_load(ScrDyn *receiver, con
|
||||
(ScrJsIcEntry){shape, &scr_js_absent, &scr_js_absent_holder, NULL, 0});
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
const ScrJsShape *holder_shape = scr_js_shape_of(holder);
|
||||
const ScrLayout *holder_shape = scr_js_layout_of(holder);
|
||||
if (!holder_shape) return NULL;
|
||||
slot = scr_js_shape_find(holder_shape, key->data, key->len);
|
||||
slot = scr_js_layout_find(holder_shape, key->data, key->len);
|
||||
if (slot == SIZE_MAX) {
|
||||
if (!absent || holder->prototype != NULL) return NULL;
|
||||
scr_js_load_fill(ic, stale, shared, key,
|
||||
@@ -389,13 +250,11 @@ static __attribute__((noinline)) ScrDyn *scr_js_probe_load(ScrDyn *receiver, con
|
||||
(size_t)holder});
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
const ScrDynEntry *found = &holder->v.obj.entries[slot];
|
||||
if (found->accessor) return NULL;
|
||||
scr_js_load_fill(ic, stale, shared, key, (ScrJsIcEntry){shape, holder_shape, holder, NULL, slot});
|
||||
return scr_dyn_retain(found->value);
|
||||
return scr_dyn_retain(holder->v.obj.slots[slot]);
|
||||
}
|
||||
|
||||
/* Receivers other than ordinary objects have no shape: they miss before
|
||||
/* Receivers other than ordinary objects have no layout: they miss before
|
||||
* the probe sets up its frame. */
|
||||
ScrDyn *scr_js_load_probe(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic) {
|
||||
if (receiver->kind != SCR_DYN_OBJ) return NULL;
|
||||
@@ -427,62 +286,56 @@ static bool scr_js_error_synced_key(const ScrStr *key) {
|
||||
}
|
||||
|
||||
static bool scr_js_replace(ScrDyn *receiver, size_t slot, ScrDyn *value) {
|
||||
ScrDynEntry *found = &receiver->v.obj.entries[slot];
|
||||
if (found->accessor || !found->writable || found->fenced) return false;
|
||||
scr_dyn_region_store(receiver, value);
|
||||
ScrDyn *old = found->value;
|
||||
found->value = scr_dyn_retain(value);
|
||||
ScrDyn **found = &receiver->v.obj.slots[slot];
|
||||
ScrDyn *old = *found;
|
||||
*found = scr_dyn_retain(value);
|
||||
scr_dyn_release(old);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Whether an add may skip the generic path's prototype-chain walk: the
|
||||
* chain is empty, or one ordinary object that lacks the key and ends it. */
|
||||
static bool scr_js_add_chain(const ScrDyn *prototype, const ScrJsShape *prototype_shape,
|
||||
static bool scr_js_add_chain(const ScrDyn *prototype, const ScrLayout *prototype_shape,
|
||||
const ScrStr *key) {
|
||||
if (!prototype) return true;
|
||||
return prototype_shape && !prototype->prototype &&
|
||||
scr_js_shape_find(prototype_shape, key->data, key->len) == SIZE_MAX;
|
||||
scr_js_layout_find(prototype_shape, key->data, key->len) == SIZE_MAX;
|
||||
}
|
||||
|
||||
static bool scr_js_append_shaped(ScrDyn *receiver, const ScrJsShape *target, ScrDyn *value) {
|
||||
static bool scr_js_append_shaped(ScrDyn *receiver, const ScrLayout *target, ScrDyn *value) {
|
||||
if (receiver->non_extensible) return false;
|
||||
scr_dyn_tier_append(receiver, target->key, target->key_len, scr_dyn_retain(value));
|
||||
receiver->v.obj.shape = (ScrJsShape *)target;
|
||||
scr_dyn_tier_append(receiver, target, scr_dyn_retain(value));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool scr_js_store_probe(ScrDyn *receiver, const ScrStr *key, ScrDyn *value, ScrJsIc *ic) {
|
||||
const ScrJsShape *shape = scr_js_shape_of(receiver);
|
||||
const ScrLayout *shape = scr_js_layout_of(receiver);
|
||||
if (!shape || scr_js_error_synced_key(key)) return false;
|
||||
uint32_t stale = UINT32_MAX;
|
||||
for (uint32_t i = 0; i < ic->count; i++) {
|
||||
const ScrJsIcEntry *entry = &ic->entry[i];
|
||||
if (entry->shape != shape) continue;
|
||||
if (entry->layout != shape) continue;
|
||||
if (entry->target == shape) {
|
||||
if (scr_js_replace(receiver, entry->slot, value)) return true;
|
||||
} else if (receiver->prototype == entry->holder &&
|
||||
(!entry->holder || (entry->holder->kind == SCR_DYN_OBJ &&
|
||||
entry->holder->v.obj.shape == entry->holder_shape &&
|
||||
entry->holder->v.obj.layout == entry->holder_layout &&
|
||||
!entry->holder->prototype))) {
|
||||
if (scr_js_append_shaped(receiver, entry->target, value)) return true;
|
||||
}
|
||||
stale = i;
|
||||
break;
|
||||
}
|
||||
size_t slot = scr_js_shape_find(shape, key->data, key->len);
|
||||
size_t slot = scr_js_layout_find(shape, key->data, key->len);
|
||||
if (slot != SIZE_MAX) {
|
||||
const ScrDynEntry *found = &receiver->v.obj.entries[slot];
|
||||
if (found->accessor || !found->writable || found->fenced) return false;
|
||||
scr_js_ic_add(ic, stale, (ScrJsIcEntry){shape, shape, NULL, NULL, slot});
|
||||
return scr_js_replace(receiver, slot, value);
|
||||
}
|
||||
ScrDyn *prototype = receiver->prototype;
|
||||
const ScrJsShape *prototype_shape = prototype ? scr_js_shape_of(prototype) : NULL;
|
||||
if (!scr_js_add_chain(prototype, prototype_shape, key) || receiver->non_extensible ||
|
||||
shape->count >= SCR_JS_SHAPE_MAX)
|
||||
return false;
|
||||
const ScrJsShape *target = scr_js_transition(shape, key->data, key->len);
|
||||
const ScrLayout *prototype_shape = prototype ? scr_js_layout_of(prototype) : NULL;
|
||||
if (!scr_js_add_chain(prototype, prototype_shape, key) || receiver->non_extensible) return false;
|
||||
const ScrLayout *target = scr_layout_add(shape, key->data, key->len);
|
||||
if (!target) return false;
|
||||
scr_js_ic_add(ic, stale, (ScrJsIcEntry){shape, target, prototype, prototype_shape, target->count - 1});
|
||||
return scr_js_append_shaped(receiver, target, value);
|
||||
@@ -505,20 +358,18 @@ ScrDyn *scr_js_keyed_probe(ScrDyn *receiver, const ScrDyn *key, ScrJsIc *ic) {
|
||||
if (key->kind == SCR_DYN_NUM) return scr_js_index_probe(receiver, key->v.num);
|
||||
if (key->kind != SCR_DYN_STR || receiver->kind != SCR_DYN_OBJ) return NULL;
|
||||
const ScrStr *name = key->v.str;
|
||||
const ScrLayout *shape = scr_js_layout_of(receiver);
|
||||
if (!shape) return NULL;
|
||||
size_t len = receiver->v.obj.len;
|
||||
for (uint32_t i = 0; i < ic->count; i++) {
|
||||
size_t slot = ic->entry[i].slot;
|
||||
if (slot >= len) continue;
|
||||
const ScrDynEntry *found = &receiver->v.obj.entries[slot];
|
||||
if (found->key_len == name->len && scr_key_equal(found->key, name->data, name->len))
|
||||
return found->accessor ? NULL : scr_dyn_retain(found->value);
|
||||
const ScrLayoutKey *found = &shape->keys[slot];
|
||||
if (found->len == name->len && scr_key_equal(found->key, name->data, name->len))
|
||||
return scr_dyn_retain(receiver->v.obj.slots[slot]);
|
||||
}
|
||||
const ScrJsShape *shape = scr_js_shape_of(receiver);
|
||||
if (!shape) return NULL;
|
||||
size_t slot = scr_js_shape_find(shape, name->data, name->len);
|
||||
size_t slot = scr_js_layout_find(shape, name->data, name->len);
|
||||
if (slot == SIZE_MAX) return NULL;
|
||||
const ScrDynEntry *found = &receiver->v.obj.entries[slot];
|
||||
if (found->accessor) return NULL;
|
||||
scr_js_ic_add(ic, UINT32_MAX, (ScrJsIcEntry){NULL, NULL, NULL, NULL, slot});
|
||||
return scr_dyn_retain(found->value);
|
||||
return scr_dyn_retain(receiver->v.obj.slots[slot]);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Mirrors scr_js_object.c's budgets. */
|
||||
/* Mirrors scr_json.c's layout budgets. */
|
||||
#define SHAPE_BUDGET ((size_t)1 << 16)
|
||||
#define KEY_BYTES_BUDGET ((size_t)4 << 20)
|
||||
|
||||
@@ -63,7 +63,8 @@ static void budget_bounds_shapes(size_t key_len, size_t budget) {
|
||||
scr_str_release(key);
|
||||
scr_dyn_release(object);
|
||||
}
|
||||
assert(shaped == budget);
|
||||
/* Objects the earlier checks built hold a few layouts already. */
|
||||
assert(shaped <= budget && shaped + 64 > budget);
|
||||
|
||||
/* A key sequence shaped before the budget ran out. */
|
||||
ScrStr *first = key_of(bytes, key_len, 0);
|
||||
@@ -73,7 +74,7 @@ static void budget_bounds_shapes(size_t key_len, size_t budget) {
|
||||
/* A store probe that would add a new shape leaves the add to the generic
|
||||
* store; one that replaces through an existing shape still runs. */
|
||||
memset(&store_ic, 0, sizeof store_ic);
|
||||
ScrStr *added = scr_str_new("added", 5);
|
||||
ScrStr *added = key_of(bytes, key_len, budget + 1000);
|
||||
ScrDyn *value = scr_dyn_new_num(8);
|
||||
assert(!scr_js_store_probe(object, added, value, &store_ic));
|
||||
assert(object->v.obj.len == 1);
|
||||
@@ -94,11 +95,11 @@ static void immortal_objects_stay_unshaped(void) {
|
||||
ScrDyn *object = object_with(key, 3);
|
||||
object->rc = SIZE_MAX;
|
||||
assert(!probe_reads(object, key, 3));
|
||||
assert(object->v.obj.shape == NULL);
|
||||
assert(object->v.obj.len == 1);
|
||||
memset(&store_ic, 0, sizeof store_ic);
|
||||
ScrDyn *value = scr_dyn_new_num(4);
|
||||
assert(!scr_js_store_probe(object, key, value, &store_ic));
|
||||
assert(object->v.obj.shape == NULL);
|
||||
assert(object->v.obj.len == 1);
|
||||
scr_dyn_release(value);
|
||||
scr_str_release(key);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { expect, test } from "vitest";
|
||||
|
||||
const exec = promisify(execFile);
|
||||
|
||||
test("dynamic-tier shapes stay within the shape and key-byte budgets, and absent reads are cached", async () => {
|
||||
test("layouts stay within the layout and key-byte budgets, and dynamic-tier absent reads are cached", async () => {
|
||||
const scratch = await mkdtemp(join(tmpdir(), "scriptc-js-object-"));
|
||||
const src = import.meta.dirname;
|
||||
const binary = join(scratch, "js-object");
|
||||
|
||||
+578
-190
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@
|
||||
#ifdef SCR_RC_AUDIT
|
||||
long scr_str_live_count(void);
|
||||
long scr_dyn_key_live_count(void);
|
||||
void scr_layout_reset_idle(void);
|
||||
#endif
|
||||
|
||||
static void put(ScrDyn *map, ScrDyn *key, ScrDyn *value) {
|
||||
@@ -167,7 +168,7 @@ static void checked_storage(void) {
|
||||
char key[16];
|
||||
int length = snprintf(key, sizeof key, "key-%d", i);
|
||||
scr_dyn_obj_set(object, key, (size_t)length, scr_dyn_new_num(i));
|
||||
assert((object->v.obj.index != NULL) == (i >= 31));
|
||||
assert((object->v.obj.index != NULL) == (i >= (int)SCR_LAYOUT_MAX_KEYS));
|
||||
}
|
||||
for (int i = 0; i < 129; i++) {
|
||||
char key[16];
|
||||
@@ -229,6 +230,8 @@ static void checked_storage(void) {
|
||||
|
||||
static void shared_property_keys(void) {
|
||||
#ifdef SCR_RC_AUDIT
|
||||
/* Layouts hold key references until the thread's state is torn down. */
|
||||
scr_layout_reset_idle();
|
||||
long before = scr_dyn_key_live_count();
|
||||
#endif
|
||||
/* Distinct objects share bytes, not values or property attributes. Key
|
||||
@@ -238,7 +241,8 @@ static void shared_property_keys(void) {
|
||||
char key[] = "shared\0name";
|
||||
scr_dyn_obj_set(first, key, sizeof key - 1, scr_dyn_new_num(1));
|
||||
scr_dyn_obj_set(second, key, sizeof key - 1, scr_dyn_new_num(2));
|
||||
assert(first->v.obj.entries[0].key == second->v.obj.entries[0].key);
|
||||
size_t first_len, second_len;
|
||||
assert(scr_dyn_obj_key_at(first, 0, &first_len) == scr_dyn_obj_key_at(second, 0, &second_len));
|
||||
key[0] = 'x';
|
||||
ScrStr *name = scr_str_new("shared\0name", sizeof key - 1);
|
||||
scr_dyn_key_delete(first, name, true);
|
||||
@@ -269,6 +273,7 @@ static void shared_property_keys(void) {
|
||||
}
|
||||
}
|
||||
#ifdef SCR_RC_AUDIT
|
||||
scr_layout_reset_idle();
|
||||
assert(scr_dyn_key_live_count() == before);
|
||||
#endif
|
||||
}
|
||||
@@ -651,8 +656,10 @@ static void json_schema_parse(void) {
|
||||
static const char *const order[] = {"tags", "ok", "extra", "qty", "sku"};
|
||||
assert(view->kind == SCR_DYN_OBJ && view->v.obj.len == 5);
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
assert(view->v.obj.entries[i].key_len == strlen(order[i]));
|
||||
assert(memcmp(view->v.obj.entries[i].key, order[i], strlen(order[i])) == 0);
|
||||
size_t key_len;
|
||||
const char *key = scr_dyn_obj_key_at(view, i, &key_len);
|
||||
assert(key_len == strlen(order[i]));
|
||||
assert(memcmp(key, order[i], strlen(order[i])) == 0);
|
||||
}
|
||||
assert(scr_dyn_obj_get(view, "extra", 5)->kind == SCR_DYN_OBJ);
|
||||
test_item_release(second);
|
||||
|
||||
@@ -716,8 +716,9 @@ ScrDyn *scr_process_versions(void) {
|
||||
scr_dyn_obj_set(scr_versions_object, "openssl", 7, scr_dyn_new_str(openssl));
|
||||
scr_str_release(node);
|
||||
scr_str_release(openssl);
|
||||
for (size_t i = 0; i < scr_versions_object->v.obj.len; i++) {
|
||||
scr_versions_object->v.obj.entries[i].writable = false;
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(scr_versions_object); i++) {
|
||||
ScrDynProp prop = scr_dyn_obj_prop_at(scr_versions_object, i);
|
||||
scr_dyn_obj_set_attrs_at(scr_versions_object, i, false, prop.enumerable, prop.configurable);
|
||||
}
|
||||
scr_atexit(&scr_versions_object_cleanup);
|
||||
}
|
||||
@@ -914,8 +915,8 @@ static void scr_env_store(ScrDyn *out, const char *text, size_t ks, size_t ke,
|
||||
}
|
||||
|
||||
static int scr_env_entry_order(const void *left, const void *right) {
|
||||
const ScrDynEntry *a = *(const ScrDynEntry *const *)left;
|
||||
const ScrDynEntry *b = *(const ScrDynEntry *const *)right;
|
||||
const ScrDynProp *a = left;
|
||||
const ScrDynProp *b = right;
|
||||
size_t n = a->key_len < b->key_len ? a->key_len : b->key_len;
|
||||
int cmp = memcmp(a->key, b->key, n);
|
||||
return cmp ? cmp : (a->key_len > b->key_len) - (a->key_len < b->key_len);
|
||||
@@ -991,14 +992,14 @@ static ScrDyn *scr_env_parse_content(const ScrStr *input, bool ordinary) {
|
||||
/* Node's std::map orders UTF-8 keys before installing object properties.
|
||||
* Ordinary objects ignore string-valued __proto__ assignments; loading
|
||||
* the native environment retains that name as an ordinary variable. */
|
||||
size_t count = entries->v.obj.len;
|
||||
const ScrDynEntry **sorted = count ? malloc(count * sizeof(*sorted)) : NULL;
|
||||
size_t count = scr_dyn_obj_count(entries);
|
||||
ScrDynProp *sorted = count ? malloc(count * sizeof(*sorted)) : NULL;
|
||||
if (count && !sorted) scr_trap("scriptc: out of memory\n");
|
||||
for (size_t i = 0; i < count; i++) sorted[i] = &entries->v.obj.entries[i];
|
||||
for (size_t i = 0; i < count; i++) sorted[i] = scr_dyn_obj_prop_at(entries, i);
|
||||
if (count > 1) qsort(sorted, count, sizeof(*sorted), scr_env_entry_order);
|
||||
ScrDyn *result = ordinary ? scr_dyn_new_obj() : scr_dyn_new_obj_null_proto();
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
const ScrDynEntry *entry = sorted[i];
|
||||
const ScrDynProp *entry = &sorted[i];
|
||||
if (ordinary && entry->key_len == 9 && memcmp(entry->key, "__proto__", 9) == 0) continue;
|
||||
if (!ordinary) {
|
||||
scr_dyn_obj_set(result, entry->key, entry->key_len, scr_dyn_retain(entry->value));
|
||||
@@ -1103,15 +1104,15 @@ void scr_process_load_env_file(const ScrDyn *value) {
|
||||
if (!content) return;
|
||||
ScrDyn *entries = scr_env_parse_content(content, false);
|
||||
scr_str_release(content);
|
||||
for (size_t i = 0; i < entries->v.obj.len; i++) {
|
||||
const ScrDynEntry *entry = &entries->v.obj.entries[i];
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(entries); i++) {
|
||||
ScrDynProp entry = scr_dyn_obj_prop_at(entries, i);
|
||||
/* Node normalizes each raw sorted entry immediately before checking
|
||||
* the environment. Distinct invalid byte keys can normalize alike;
|
||||
* the first absent name wins, rather than the last parsed entry. */
|
||||
ScrStr *key = scr_str_from_utf8_lossy((const uint8_t *)entry->key, entry->key_len);
|
||||
ScrStr *key = scr_str_from_utf8_lossy((const uint8_t *)entry.key, entry.key_len);
|
||||
ScrStr *existing = scr_env_get(key);
|
||||
if (!existing) {
|
||||
const ScrStr *raw = entry->value->v.str;
|
||||
const ScrStr *raw = entry.value->v.str;
|
||||
ScrStr *value = scr_str_from_utf8_lossy((const uint8_t *)raw->data, raw->len);
|
||||
scr_env_set(key, value);
|
||||
scr_str_release(value);
|
||||
@@ -2810,10 +2811,9 @@ ScrDyn *scr_process_release(void) {
|
||||
#endif
|
||||
#undef SCR_RELEASE_FILE
|
||||
#undef SCR_RELEASE_PREFIX
|
||||
for (size_t i = 0; i < scr_release_object->v.obj.len; i++) {
|
||||
scr_release_object->v.obj.entries[i].writable = false;
|
||||
scr_release_object->v.obj.entries[i].configurable = false;
|
||||
}
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(scr_release_object); i++)
|
||||
scr_dyn_obj_set_attrs_at(scr_release_object, i, false,
|
||||
scr_dyn_obj_prop_at(scr_release_object, i).enumerable, false);
|
||||
scr_atexit(&scr_release_object_cleanup);
|
||||
}
|
||||
return scr_dyn_retain(scr_release_object);
|
||||
|
||||
@@ -636,9 +636,9 @@ static bool scr_publish_container_ok(const ScrDyn *d) {
|
||||
if (!scr_dyn_is_frozen(d)) return false;
|
||||
if (d->kind == SCR_DYN_ARR) return d->v.arr.properties == NULL;
|
||||
if (d->null_proto || d->v.obj.source_identity || d->v.obj.source_access) return false;
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) {
|
||||
const ScrDynEntry *e = &d->v.obj.entries[i];
|
||||
if (e->accessor || !e->enumerable) return false;
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
|
||||
ScrDynProp e = scr_dyn_obj_prop_at(d, i);
|
||||
if (e.accessor || !e.enumerable) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -666,8 +666,8 @@ static bool scr_publish_collect(ScrPublish *p, ScrDyn *root) {
|
||||
break;
|
||||
case SCR_DYN_OBJ:
|
||||
if (!scr_publish_container_ok(d)) return false;
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) {
|
||||
ScrDyn *child = d->v.obj.entries[i].value;
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
|
||||
ScrDyn *child = scr_dyn_obj_value_at(d, i);
|
||||
if (child && !scr_publish_seen(p, child)) scr_publish_push(p, child);
|
||||
}
|
||||
break;
|
||||
@@ -702,9 +702,10 @@ static ScrMessage *scr_message_try_publish(const ScrDyn *value) {
|
||||
} else if (d->rc != SIZE_MAX) {
|
||||
scr_cyc_on_dead(d); /* leave the candidate and tenured buffers */
|
||||
d->rc = SIZE_MAX;
|
||||
/* A shape names a node of this thread's transition tree (scr_js.h),
|
||||
* which other threads cannot use and which ends with this thread. */
|
||||
if (d->kind == SCR_DYN_OBJ) d->v.obj.shape = NULL;
|
||||
/* A layout names a node of this thread's transition tree
|
||||
* (scr_dyn.h), which other threads cannot use and which ends with
|
||||
* this thread: the object moves to dictionary mode. */
|
||||
if (d->kind == SCR_DYN_OBJ) scr_dyn_to_dictionary(d);
|
||||
#ifdef SCR_RC_AUDIT
|
||||
scr_dyn_live_forget();
|
||||
#endif
|
||||
@@ -1033,14 +1034,15 @@ ScrDyn *scr_message_decode(const ScrMessage *message) {
|
||||
} else scr_dyn_obj_set(values[i], property->key, property->length, value);
|
||||
}
|
||||
if (node->kind == SCR_MESSAGE_ERROR) {
|
||||
for (size_t j = 0; j < values[i]->v.obj.len; j++) {
|
||||
ScrDynEntry *entry = &values[i]->v.obj.entries[j];
|
||||
entry->enumerable = false;
|
||||
for (size_t j = 0; j < scr_dyn_obj_count(values[i]); j++) {
|
||||
ScrDynProp entry = scr_dyn_obj_prop_at(values[i], j);
|
||||
bool enumerable = false;
|
||||
for (size_t k = 0; k < node->count; k++) {
|
||||
const ScrMessageProperty *property = &node->properties[k];
|
||||
if (!property->hidden && property->length == entry->key_len && !memcmp(property->key, entry->key, entry->key_len))
|
||||
entry->enumerable = true;
|
||||
if (!property->hidden && property->length == entry.key_len && !memcmp(property->key, entry.key, entry.key_len))
|
||||
enumerable = true;
|
||||
}
|
||||
scr_dyn_obj_set_attrs_at(values[i], j, entry.writable, enumerable, entry.configurable);
|
||||
}
|
||||
ScrError *error = scr_errdyn_err_of(values[i]);
|
||||
scr_error_commit_dyn(error, values[i]);
|
||||
|
||||
@@ -225,8 +225,11 @@ static void check_large(void) {
|
||||
ScrDyn *decoded = scr_message_decode(message);
|
||||
assert(decoded && !scr_exc_pending());
|
||||
assert(decoded->v.obj.len == 42);
|
||||
assert(decoded->v.obj.entries[0].key_len == 5 && !memcmp(decoded->v.obj.entries[0].key, "item0", 5));
|
||||
assert(decoded->v.obj.entries[41].key_len == 6 && !memcmp(decoded->v.obj.entries[41].key, "nested", 6));
|
||||
size_t member_len;
|
||||
const char *member = scr_dyn_obj_key_at(decoded, 0, &member_len);
|
||||
assert(member_len == 5 && !memcmp(member, "item0", 5));
|
||||
member = scr_dyn_obj_key_at(decoded, 41, &member_len);
|
||||
assert(member_len == 6 && !memcmp(member, "nested", 6));
|
||||
assert(scr_dyn_obj_get(decoded, "nested", 6)->v.num == 42);
|
||||
ScrDyn *first = scr_dyn_obj_get(decoded, "item0", 5);
|
||||
for (int i = 0; i < 40; i++) {
|
||||
|
||||
@@ -745,9 +745,8 @@ double scr_os_cpus_field(double i, double field) {
|
||||
|
||||
static void scr_os_constant(ScrDyn *o, const char *name, double value) {
|
||||
scr_dyn_obj_set(o, name, strlen(name), scr_dyn_new_num(value));
|
||||
ScrDynEntry *entry = &o->v.obj.entries[o->v.obj.len - 1];
|
||||
entry->writable = false;
|
||||
entry->configurable = false;
|
||||
size_t last = scr_dyn_obj_count(o) - 1;
|
||||
scr_dyn_obj_set_attrs_at(o, last, false, scr_dyn_obj_prop_at(o, last).enumerable, false);
|
||||
}
|
||||
#define SCR_OS_CONSTANT(o, name) scr_os_constant((o), #name, (double)(name))
|
||||
|
||||
@@ -1514,10 +1513,10 @@ ScrDyn *scr_node_constants(const ScrDyn *crypto_table) {
|
||||
scr_os_signal_constants(o);
|
||||
scr_fs_constants(o);
|
||||
if (crypto && crypto->kind == SCR_DYN_OBJ) {
|
||||
for (size_t i = 0; i < crypto->v.obj.len; i++) {
|
||||
const ScrDynEntry *entry = &crypto->v.obj.entries[i];
|
||||
if (entry->accessor || !entry->value) continue;
|
||||
scr_dyn_obj_set(o, entry->key, entry->key_len, scr_dyn_retain(entry->value));
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(crypto); i++) {
|
||||
ScrDynProp entry = scr_dyn_obj_prop_at(crypto, i);
|
||||
if (entry.accessor || !entry.value) continue;
|
||||
scr_dyn_obj_set(o, entry.key, entry.key_len, scr_dyn_retain(entry.value));
|
||||
}
|
||||
}
|
||||
scr_dyn_release(crypto);
|
||||
@@ -1718,10 +1717,12 @@ double scr_util_signal_exit_code(const ScrDyn *code) {
|
||||
ScrJsonBuf reason;
|
||||
scr_jb_init(&reason);
|
||||
scr_jb_puts(&reason, "must be one of: ");
|
||||
for (size_t i = 0; i < signals->v.obj.len; i++) {
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(signals); i++) {
|
||||
if (i > 0) scr_jb_puts(&reason, ", ");
|
||||
scr_jb_putc(&reason, '\'');
|
||||
scr_jb_write(&reason, signals->v.obj.entries[i].key, signals->v.obj.entries[i].key_len);
|
||||
size_t key_len;
|
||||
const char *key = scr_dyn_obj_key_at(signals, i, &key_len);
|
||||
scr_jb_write(&reason, key, key_len);
|
||||
scr_jb_putc(&reason, '\'');
|
||||
}
|
||||
ScrStr *text = scr_jb_finish(&reason);
|
||||
|
||||
@@ -278,8 +278,8 @@ static bool scr_pub_dyn_ok(const ScrDyn *d, const char **why) {
|
||||
*why = "Cannot publish an Object object";
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < d->v.obj.len; i++)
|
||||
if (d->v.obj.entries[i].accessor) {
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(d); i++)
|
||||
if (scr_dyn_obj_prop_at(d, i).accessor) {
|
||||
*why = "Cannot publish an accessor property";
|
||||
return false;
|
||||
}
|
||||
@@ -325,9 +325,9 @@ void scr_pub_dyn_fn(void *v, ScrPub *ctx) {
|
||||
case SCR_DYN_ARR:
|
||||
case SCR_DYN_OBJ: {
|
||||
if (!scr_pub_dyn_ok(d, &why)) break;
|
||||
size_t n = d->kind == SCR_DYN_ARR ? d->v.arr.len : d->v.obj.len;
|
||||
size_t n = d->kind == SCR_DYN_ARR ? d->v.arr.len : scr_dyn_obj_count(d);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
ScrDyn *child = d->kind == SCR_DYN_ARR ? d->v.arr.items[i] : d->v.obj.entries[i].value;
|
||||
ScrDyn *child = d->kind == SCR_DYN_ARR ? d->v.arr.items[i] : scr_dyn_obj_value_at(d, i);
|
||||
if (child && child->rc != SIZE_MAX) SCR_PUB_ADD((uintptr_t)child);
|
||||
}
|
||||
break;
|
||||
@@ -481,9 +481,9 @@ void scr_publish(void *root, ScrPubFn fn) {
|
||||
void *obj = scr_pub_log_obj(&ctx.log[i]);
|
||||
if (ctx.log[i].obj & SCR_PUB_DYN) {
|
||||
ScrDyn *d = obj;
|
||||
/* Other threads share the object; its shape belongs to this one
|
||||
* (scr_js.h). */
|
||||
if (d->kind == SCR_DYN_OBJ) d->v.obj.shape = NULL;
|
||||
/* Other threads share the object; layouts belong to this thread
|
||||
* (scr_dyn.h), so it moves to dictionary mode. */
|
||||
if (d->kind == SCR_DYN_OBJ) scr_dyn_to_dictionary(d);
|
||||
scr_dyn_release(scr_dyn_freeze_data(d));
|
||||
}
|
||||
#ifdef SCR_RC_AUDIT
|
||||
|
||||
@@ -858,12 +858,12 @@ static void scr_rgn_walk_dyn(ScrRgnWalk *w, ScrDyn *d) {
|
||||
break;
|
||||
case SCR_DYN_OBJ:
|
||||
/* source_identity is owned through its access callback: not walked. */
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) {
|
||||
const ScrDynEntry *e = &d->v.obj.entries[i];
|
||||
if (e->accessor) {
|
||||
scr_rgn_enter(w, e->getter, SCR_RGN_W_DYN, NULL);
|
||||
scr_rgn_enter(w, e->setter, SCR_RGN_W_DYN, NULL);
|
||||
} else scr_rgn_enter(w, e->value, SCR_RGN_W_DYN, NULL);
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
|
||||
ScrDynProp e = scr_dyn_obj_prop_at(d, i);
|
||||
if (e.accessor) {
|
||||
scr_rgn_enter(w, e.getter, SCR_RGN_W_DYN, NULL);
|
||||
scr_rgn_enter(w, e.setter, SCR_RGN_W_DYN, NULL);
|
||||
} else scr_rgn_enter(w, e.value, SCR_RGN_W_DYN, NULL);
|
||||
}
|
||||
break;
|
||||
case SCR_DYN_FUNC: scr_rgn_enter(w, d->v.fn.clo, SCR_RGN_W_CLOSURE, NULL); break;
|
||||
|
||||
@@ -333,7 +333,7 @@ static void scr_dirent_init(void) {
|
||||
scr_dyn_obj_set(proto, methods[i].name, strlen(methods[i].name),
|
||||
scr_dyn_new_func(scr_closure_new(NULL, 0), methods[i].thunk, 0, "native:fs.Dirent",
|
||||
methods[i].name));
|
||||
proto->v.obj.entries[proto->v.obj.len - 1].enumerable = false;
|
||||
scr_dyn_obj_set_attrs_at(proto, scr_dyn_obj_count(proto) - 1, true, false, true);
|
||||
}
|
||||
ScrStr *desc = scr_str_new("type", 4);
|
||||
ScrSym *sym = scr_sym_new(desc);
|
||||
@@ -350,15 +350,14 @@ void scr_dyn_dirent_finish(ScrDyn *obj) {
|
||||
scr_dyn_region_store(obj, NULL); /* a prototype store (ScrDynRegion) */
|
||||
scr_dyn_release(obj->prototype);
|
||||
obj->prototype = scr_dyn_retain(scr_dirent_prototype);
|
||||
for (size_t i = 0; i < obj->v.obj.len; i++) {
|
||||
ScrDynEntry *ent = &obj->v.obj.entries[i];
|
||||
if (ent->key_len != 6 || memcmp(ent->key, "%dtype", 6) != 0) continue;
|
||||
ent->enumerable = false;
|
||||
size_t i = scr_dyn_obj_index_of(obj, "%dtype", 6);
|
||||
if (i != SIZE_MAX) {
|
||||
ScrDynProp ent = scr_dyn_obj_prop_at(obj, i);
|
||||
scr_dyn_obj_set_attrs_at(obj, i, ent.writable, false, ent.configurable);
|
||||
if (!obj->symbol_properties && !obj->symbol_keys)
|
||||
scr_dyn_symbol_init_own(obj, scr_dirent_type_key, scr_dirent_type_storage, ent->value);
|
||||
scr_dyn_symbol_init_own(obj, scr_dirent_type_key, scr_dirent_type_storage, ent.value);
|
||||
else
|
||||
scr_dyn_symbol_key_set(obj, scr_dirent_type_key, ent->value);
|
||||
break;
|
||||
scr_dyn_symbol_key_set(obj, scr_dirent_type_key, ent.value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2597,21 +2597,21 @@ static bool scr_tls_srv_opts_walk_ex(const ScrDyn *opts, const char *api, ScrByt
|
||||
}
|
||||
if (!scr_tls_opts_validate(opts)) return false; /* Node's typed errors first */
|
||||
bool ok = true;
|
||||
for (size_t i = 0; ok && i < opts->v.obj.len; i++) {
|
||||
const ScrDynEntry *e = &opts->v.obj.entries[i];
|
||||
const ScrDyn *v = e->value;
|
||||
for (size_t i = 0; ok && i < scr_dyn_obj_count(opts); i++) {
|
||||
ScrDynProp e = scr_dyn_obj_prop_at(opts, i);
|
||||
const ScrDyn *v = e.value;
|
||||
if (v->kind == SCR_DYN_UNDEF) continue; /* an undefined member reads as absent, like Node */
|
||||
if (strcmp(e->key, "cert") == 0 || strcmp(e->key, "key") == 0) {
|
||||
if (strcmp(e.key, "cert") == 0 || strcmp(e.key, "key") == 0) {
|
||||
char what[96];
|
||||
snprintf(what, sizeof what, "a %s '%s' option", api, e->key);
|
||||
snprintf(what, sizeof what, "a %s '%s' option", api, e.key);
|
||||
ScrBytes *b = scr_tls_pem_dyn(v, what, false);
|
||||
if (b == NULL) { ok = false; break; }
|
||||
ScrBytes **slot = e->key[0] == 'c' ? cert_out : key_out;
|
||||
ScrBytes **slot = e.key[0] == 'c' ? cert_out : key_out;
|
||||
scr_bytes_release(*slot);
|
||||
*slot = b;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(e->key, "ca") == 0) {
|
||||
if (strcmp(e.key, "ca") == 0) {
|
||||
/* the server's `ca` verifies client certificates and completes the
|
||||
* server's own chain (scr_tls_srv_client_auth); a walk without
|
||||
* client certificates drops it */
|
||||
@@ -2623,7 +2623,7 @@ static bool scr_tls_srv_opts_walk_ex(const ScrDyn *opts, const char *api, ScrByt
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (strcmp(e->key, "requestCert") == 0) {
|
||||
if (strcmp(e.key, "requestCert") == 0) {
|
||||
if (auth != NULL) {
|
||||
auth->request = scr_tls_dyn_truthy(v);
|
||||
continue;
|
||||
@@ -2635,22 +2635,22 @@ static bool scr_tls_srv_opts_walk_ex(const ScrDyn *opts, const char *api, ScrByt
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (strcmp(e->key, "rejectUnauthorized") == 0) {
|
||||
if (strcmp(e.key, "rejectUnauthorized") == 0) {
|
||||
if (auth != NULL) auth->reject = scr_tls_dyn_not_false(v);
|
||||
continue; /* without client certificates it has no effect */
|
||||
}
|
||||
if (server_ok && (strcmp(e->key, "ALPNProtocols") == 0 || strcmp(e->key, "minVersion") == 0 ||
|
||||
strcmp(e->key, "maxVersion") == 0))
|
||||
if (server_ok && (strcmp(e.key, "ALPNProtocols") == 0 || strcmp(e.key, "minVersion") == 0 ||
|
||||
strcmp(e.key, "maxVersion") == 0))
|
||||
continue;
|
||||
if (strcmp(e->key, "SNICallback") == 0) {
|
||||
if (strcmp(e.key, "SNICallback") == 0) {
|
||||
scr_tls_opt_fence(api, "SNICallback",
|
||||
"SNICallback lowers only on http2.createSecureServer({ allowHTTP1: true, ... }) with a literal options object");
|
||||
ok = false;
|
||||
continue;
|
||||
}
|
||||
for (size_t k = 0; SCR_TLS_SRV_FENCED_OPTIONS[k] != NULL; k++) {
|
||||
if (strcmp(e->key, SCR_TLS_SRV_FENCED_OPTIONS[k]) == 0) {
|
||||
scr_tls_opt_fence(api, e->key,
|
||||
if (strcmp(e.key, SCR_TLS_SRV_FENCED_OPTIONS[k]) == 0) {
|
||||
scr_tls_opt_fence(api, e.key,
|
||||
"cert and key (PEM strings or Buffers) are the honestly-implemented members of a runtime options record");
|
||||
ok = false;
|
||||
break;
|
||||
@@ -2957,17 +2957,17 @@ static const char *const SCR_TLS_WRAP_FENCED_OPTIONS[] = {
|
||||
* certificates are not modeled), SNICallback and ALPNCallback fence.
|
||||
* False with the exception pending. */
|
||||
static bool scr_tls_wrap_socket_opts_check(const ScrDyn *record, const char *api) {
|
||||
for (size_t i = 0; record != NULL && i < record->v.obj.len; i++) {
|
||||
const ScrDynEntry *e = &record->v.obj.entries[i];
|
||||
if (e->value->kind == SCR_DYN_UNDEF) continue;
|
||||
if (strcmp(e->key, "requestCert") == 0) {
|
||||
if (!scr_tls_dyn_truthy(e->value)) continue;
|
||||
for (size_t i = 0; record != NULL && i < scr_dyn_obj_count(record); i++) {
|
||||
ScrDynProp e = scr_dyn_obj_prop_at(record, i);
|
||||
if (e.value->kind == SCR_DYN_UNDEF) continue;
|
||||
if (strcmp(e.key, "requestCert") == 0) {
|
||||
if (!scr_tls_dyn_truthy(e.value)) continue;
|
||||
scr_tls_opt_fence(api, "requestCert",
|
||||
"requestCert lowers with cert and key options, not beside a secureContext");
|
||||
return false;
|
||||
}
|
||||
if (strcmp(e->key, "SNICallback") == 0 || strcmp(e->key, "ALPNCallback") == 0) {
|
||||
scr_tls_opt_fence(api, e->key,
|
||||
if (strcmp(e.key, "SNICallback") == 0 || strcmp(e.key, "ALPNCallback") == 0) {
|
||||
scr_tls_opt_fence(api, e.key,
|
||||
"isServer, cert, key, secureContext, ALPNProtocols, minVersion and maxVersion are the implemented members");
|
||||
return false;
|
||||
}
|
||||
@@ -2980,21 +2980,21 @@ ScrNetSocket *scr_tls_wrap_socket(ScrNetSocket *raw, const ScrDyn *opts /*borrow
|
||||
static const char api[] = "new tls.TLSSocket";
|
||||
bool is_server = false;
|
||||
const ScrDyn *record = opts != NULL && opts->kind == SCR_DYN_OBJ ? opts : NULL;
|
||||
for (size_t i = 0; record != NULL && i < record->v.obj.len; i++) {
|
||||
const ScrDynEntry *e = &record->v.obj.entries[i];
|
||||
if (e->value->kind == SCR_DYN_UNDEF) continue;
|
||||
if (strcmp(e->key, "isServer") == 0) {
|
||||
is_server = scr_tls_dyn_truthy(e->value);
|
||||
for (size_t i = 0; record != NULL && i < scr_dyn_obj_count(record); i++) {
|
||||
ScrDynProp e = scr_dyn_obj_prop_at(record, i);
|
||||
if (e.value->kind == SCR_DYN_UNDEF) continue;
|
||||
if (strcmp(e.key, "isServer") == 0) {
|
||||
is_server = scr_tls_dyn_truthy(e.value);
|
||||
continue;
|
||||
}
|
||||
if (strcmp(e->key, "secureContext") == 0) {
|
||||
if (strcmp(e.key, "secureContext") == 0) {
|
||||
scr_tls_opt_fence(api, "secureContext",
|
||||
"secureContext lowers as a member of an options object literal");
|
||||
return NULL;
|
||||
}
|
||||
for (size_t k = 0; SCR_TLS_WRAP_FENCED_OPTIONS[k] != NULL; k++) {
|
||||
if (strcmp(e->key, SCR_TLS_WRAP_FENCED_OPTIONS[k]) == 0) {
|
||||
scr_tls_opt_fence(api, e->key,
|
||||
if (strcmp(e.key, SCR_TLS_WRAP_FENCED_OPTIONS[k]) == 0) {
|
||||
scr_tls_opt_fence(api, e.key,
|
||||
"isServer, cert, key, secureContext, ALPNProtocols, minVersion and maxVersion are the implemented members");
|
||||
return NULL;
|
||||
}
|
||||
@@ -3267,10 +3267,10 @@ static ScrNetSocket *scr_tls_connect_plain(double port, ScrStr *host /*borrowed,
|
||||
/* createSecureContext's version checks come before its other options
|
||||
* (Node's SecureContext constructor). */
|
||||
if (ok && !scr_tls_versions_dyn(opts, &min_version, &max_version)) ok = false;
|
||||
for (size_t i = 0; ok && i < opts->v.obj.len; i++) {
|
||||
const ScrDynEntry *e = &opts->v.obj.entries[i];
|
||||
const ScrDyn *v = e->value;
|
||||
if (strcmp(e->key, "checkServerIdentity") == 0) {
|
||||
for (size_t i = 0; ok && i < scr_dyn_obj_count(opts); i++) {
|
||||
ScrDynProp e = scr_dyn_obj_prop_at(opts, i);
|
||||
const ScrDyn *v = e.value;
|
||||
if (strcmp(e.key, "checkServerIdentity") == 0) {
|
||||
/* Node spreads user options over the defaults, so a PRESENT key
|
||||
* replaces the builtin verifier even when its value is undefined
|
||||
* — validateFunction then throws for anything non-callable. A
|
||||
@@ -3287,7 +3287,7 @@ static ScrNetSocket *scr_tls_connect_plain(double port, ScrStr *host /*borrowed,
|
||||
continue;
|
||||
}
|
||||
if (v->kind == SCR_DYN_UNDEF) continue;
|
||||
if (strcmp(e->key, "port") == 0) {
|
||||
if (strcmp(e.key, "port") == 0) {
|
||||
if (port >= 0) continue; /* the argument form wins, like Node */
|
||||
if (v->kind == SCR_DYN_NUM) port = v->v.num;
|
||||
else if (v->kind == SCR_DYN_STR) port = strtod(v->v.str->data, NULL); /* Node coerces numeric strings */
|
||||
@@ -3297,7 +3297,7 @@ static ScrNetSocket *scr_tls_connect_plain(double port, ScrStr *host /*borrowed,
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (strcmp(e->key, "host") == 0) {
|
||||
if (strcmp(e.key, "host") == 0) {
|
||||
if (v->kind == SCR_DYN_STR) opt_host = v->v.str;
|
||||
else {
|
||||
scr_dyn_arg_type_fail("options.host", "of type string", v);
|
||||
@@ -3305,34 +3305,34 @@ static ScrNetSocket *scr_tls_connect_plain(double port, ScrStr *host /*borrowed,
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (strcmp(e->key, "rejectUnauthorized") == 0) {
|
||||
if (strcmp(e.key, "rejectUnauthorized") == 0) {
|
||||
reject = scr_tls_dyn_not_false(v);
|
||||
continue;
|
||||
}
|
||||
if (strcmp(e->key, "ca") == 0) {
|
||||
if (strcmp(e.key, "ca") == 0) {
|
||||
scr_bytes_release(ca);
|
||||
if (!scr_tls_ca_dyn(v, "a tls.connect 'ca' option", &ca)) ok = false;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(e->key, "cert") == 0 || strcmp(e->key, "key") == 0) {
|
||||
ScrBytes **slot = e->key[0] == 'c' ? &own_cert : &own_key;
|
||||
if (strcmp(e.key, "cert") == 0 || strcmp(e.key, "key") == 0) {
|
||||
ScrBytes **slot = e.key[0] == 'c' ? &own_cert : &own_key;
|
||||
scr_bytes_release(*slot);
|
||||
*slot = scr_tls_pem_dyn(v, e->key[0] == 'c' ? "a tls.connect 'cert' option" : "a tls.connect 'key' option",
|
||||
*slot = scr_tls_pem_dyn(v, e.key[0] == 'c' ? "a tls.connect 'cert' option" : "a tls.connect 'key' option",
|
||||
false);
|
||||
if (*slot == NULL) ok = false;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(e->key, "ALPNProtocols") == 0) {
|
||||
if (strcmp(e.key, "ALPNProtocols") == 0) {
|
||||
alpn_value = v; /* converted after the context options, as TLSSocket does */
|
||||
continue;
|
||||
}
|
||||
if (strcmp(e->key, "minVersion") == 0 || strcmp(e->key, "maxVersion") == 0) continue;
|
||||
if (strcmp(e->key, "timeout") == 0) {
|
||||
if (strcmp(e.key, "minVersion") == 0 || strcmp(e.key, "maxVersion") == 0) continue;
|
||||
if (strcmp(e.key, "timeout") == 0) {
|
||||
/* `if (options.timeout) tlssock.setTimeout(options.timeout)` */
|
||||
timeout_value = scr_tls_dyn_truthy(v) ? v : NULL;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(e->key, "socket") == 0) {
|
||||
if (strcmp(e.key, "socket") == 0) {
|
||||
if (v->kind == SCR_DYN_HANDLE && v->v.handle.tag == SCR_DYNH_NET_SOCKET) {
|
||||
wrap = (ScrNetSocket *)v->v.handle.ptr;
|
||||
} else {
|
||||
@@ -3341,7 +3341,7 @@ static ScrNetSocket *scr_tls_connect_plain(double port, ScrStr *host /*borrowed,
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (strcmp(e->key, "servername") == 0) {
|
||||
if (strcmp(e.key, "servername") == 0) {
|
||||
if (v->kind == SCR_DYN_STR) servername = v->v.str;
|
||||
else {
|
||||
scr_dyn_arg_type_fail("options.servername", "of type string", v);
|
||||
@@ -3350,8 +3350,8 @@ static ScrNetSocket *scr_tls_connect_plain(double port, ScrStr *host /*borrowed,
|
||||
continue;
|
||||
}
|
||||
for (size_t k = 0; SCR_TLS_CONNECT_FENCED_OPTIONS[k] != NULL; k++) {
|
||||
if (strcmp(e->key, SCR_TLS_CONNECT_FENCED_OPTIONS[k]) == 0) {
|
||||
scr_tls_opt_fence("tls.connect", e->key,
|
||||
if (strcmp(e.key, SCR_TLS_CONNECT_FENCED_OPTIONS[k]) == 0) {
|
||||
scr_tls_opt_fence("tls.connect", e.key,
|
||||
"port/host/socket/rejectUnauthorized/ca/servername/ALPNProtocols/minVersion/maxVersion/timeout are the implemented members of a runtime options record");
|
||||
ok = false;
|
||||
break;
|
||||
|
||||
@@ -26,15 +26,15 @@ static bool util_deep_equal(const ScrDyn *, const ScrDyn *, bool, const ScrDeepF
|
||||
|
||||
static size_t util_deep_key_count(const ScrDyn *table) {
|
||||
size_t count = 0;
|
||||
if (table) for (size_t i = 0; i < table->v.obj.len; i++) count += table->v.obj.entries[i].enumerable;
|
||||
if (table) for (size_t i = 0; i < scr_dyn_obj_count(table); i++) count += scr_dyn_obj_prop_at(table, i).enumerable;
|
||||
return count;
|
||||
}
|
||||
|
||||
static bool util_deep_same_keys(const ScrDyn *a, const ScrDyn *b) {
|
||||
if (util_deep_key_count(a) != util_deep_key_count(b)) return false;
|
||||
if (a) for (size_t i = 0; i < a->v.obj.len; i++) {
|
||||
const ScrDynEntry *e = &a->v.obj.entries[i];
|
||||
if (e->enumerable && (!b || !scr_dyn_obj_enumerable(b, e->key, e->key_len))) return false;
|
||||
if (a) for (size_t i = 0; i < scr_dyn_obj_count(a); i++) {
|
||||
ScrDynProp e = scr_dyn_obj_prop_at(a, i);
|
||||
if (e.enumerable && (!b || !scr_dyn_obj_enumerable(b, e.key, e.key_len))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -42,14 +42,14 @@ static bool util_deep_same_keys(const ScrDyn *a, const ScrDyn *b) {
|
||||
static ScrDyn *util_deep_constructor(const ScrDyn *value) {
|
||||
for (const ScrDyn *p = value; p; p = p->prototype) {
|
||||
const ScrDyn *table = p->kind == SCR_DYN_OBJ ? p : p->kind == SCR_DYN_ARR ? p->v.arr.properties : NULL;
|
||||
if (table) for (size_t i = 0; i < table->v.obj.len; i++) {
|
||||
const ScrDynEntry *e = &table->v.obj.entries[i];
|
||||
if (e->key_len == 11 && !memcmp(e->key, "constructor", 11)) {
|
||||
if (e->accessor) { util_deep_refused("native accessor properties"); return NULL; }
|
||||
if (p == value && e->value->kind == SCR_DYN_FUNC) {
|
||||
if (table) for (size_t i = 0; i < scr_dyn_obj_count(table); i++) {
|
||||
ScrDynProp e = scr_dyn_obj_prop_at(table, i);
|
||||
if (e.key_len == 11 && !memcmp(e.key, "constructor", 11)) {
|
||||
if (e.accessor) { util_deep_refused("native accessor properties"); return NULL; }
|
||||
if (p == value && e.value->kind == SCR_DYN_FUNC) {
|
||||
util_deep_refused("native own constructor functions"); return NULL;
|
||||
}
|
||||
return scr_dyn_retain(e->value);
|
||||
return scr_dyn_retain(e.value);
|
||||
}
|
||||
}
|
||||
if (!p->prototype) {
|
||||
@@ -77,8 +77,8 @@ static ScrDyn *util_deep_tag(const ScrDyn *value) {
|
||||
if (!scr_dyn_same_value(keys->v.arr.items[i], key)) continue;
|
||||
// General native getters retain an explicit boundary, including
|
||||
// non-enumerable Symbol.toStringTag descriptors read by Node.
|
||||
for (size_t j = 0; j < p->symbol_properties->v.obj.len; j++) {
|
||||
if (p->symbol_properties->v.obj.entries[j].accessor) {
|
||||
for (size_t j = 0; j < scr_dyn_obj_count(p->symbol_properties); j++) {
|
||||
if (scr_dyn_obj_prop_at(p->symbol_properties, j).accessor) {
|
||||
scr_dyn_release(key); util_deep_refused("native accessor properties"); return NULL;
|
||||
}
|
||||
}
|
||||
@@ -95,20 +95,21 @@ static bool util_deep_properties(const ScrDyn *a, const ScrDyn *b, bool skip,
|
||||
const ScrDeepFrame *frame) {
|
||||
if (util_deep_key_count(a) != util_deep_key_count(b)) return false;
|
||||
if (!a) return true;
|
||||
for (size_t i = 0; i < a->v.obj.len; i++) {
|
||||
const ScrDynEntry *x = &a->v.obj.entries[i];
|
||||
if (!x->enumerable) continue;
|
||||
const ScrDynEntry *y = NULL;
|
||||
if (b) for (size_t j = 0; j < b->v.obj.len; j++) {
|
||||
const ScrDynEntry *candidate = &b->v.obj.entries[j];
|
||||
if (candidate->enumerable && candidate->key_len == x->key_len &&
|
||||
!memcmp(candidate->key, x->key, x->key_len)) { y = candidate; break; }
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(a); i++) {
|
||||
ScrDynProp x = scr_dyn_obj_prop_at(a, i);
|
||||
if (!x.enumerable) continue;
|
||||
ScrDynProp yp;
|
||||
const ScrDynProp *y = NULL;
|
||||
if (b) for (size_t j = 0; j < scr_dyn_obj_count(b); j++) {
|
||||
ScrDynProp candidate = scr_dyn_obj_prop_at(b, j);
|
||||
if (candidate.enumerable && candidate.key_len == x.key_len &&
|
||||
!memcmp(candidate.key, x.key, x.key_len)) { yp = candidate; y = &yp; break; }
|
||||
}
|
||||
if (!y) return false;
|
||||
if (x->accessor || y->accessor) return util_deep_refused("native accessor properties");
|
||||
if (x.accessor || y->accessor) return util_deep_refused("native accessor properties");
|
||||
// Materializing a cyclic typed child can refresh its ancestor table.
|
||||
// Retain the values before that refresh replaces descriptor storage.
|
||||
ScrDyn *xv = scr_dyn_retain(x->value), *yv = scr_dyn_retain(y->value);
|
||||
ScrDyn *xv = scr_dyn_retain(x.value), *yv = scr_dyn_retain(y->value);
|
||||
bool same = util_deep_equal(xv, yv, skip, frame);
|
||||
scr_dyn_release(xv); scr_dyn_release(yv);
|
||||
if (!same) return false;
|
||||
|
||||
@@ -1385,17 +1385,17 @@ static ScrDyn *scr_worker_new_options(double root, ScrStr *filename, ScrDyn *opt
|
||||
scr_dyn_arg_type_fail("options", "object", options);
|
||||
return NULL;
|
||||
}
|
||||
for (size_t i = 0; i < options->v.obj.len; i++) {
|
||||
const ScrDynEntry *entry = &options->v.obj.entries[i];
|
||||
if (entry->accessor) return scr_worker_refusal("Worker option accessors are not supported yet");
|
||||
if (entry->key_len == 10 && !memcmp(entry->key, "workerData", 10)) data = entry->value;
|
||||
else if (entry->key_len == 4 && !memcmp(entry->key, "argv", 4)) argv = entry->value;
|
||||
else if (entry->key_len == 12 && !memcmp(entry->key, "transferList", 12)) transfer_list = entry->value;
|
||||
else if (entry->key_len == 14 && !memcmp(entry->key, "resourceLimits", 14)) limits = entry->value;
|
||||
else if (entry->key_len == 4 && !memcmp(entry->key, "name", 4)) name = entry->value;
|
||||
else if (entry->key_len == 6 && !memcmp(entry->key, "stdout", 6)) capture |= scr_dyn_truthy(entry->value) ? 1 : 0;
|
||||
else if (entry->key_len == 6 && !memcmp(entry->key, "stderr", 6)) capture |= scr_dyn_truthy(entry->value) ? 2 : 0;
|
||||
else if (entry->key_len == 5 && !memcmp(entry->key, "stdin", 5)) capture |= scr_dyn_truthy(entry->value) ? 4 : 0;
|
||||
for (size_t i = 0; i < scr_dyn_obj_count(options); i++) {
|
||||
ScrDynProp entry = scr_dyn_obj_prop_at(options, i);
|
||||
if (entry.accessor) return scr_worker_refusal("Worker option accessors are not supported yet");
|
||||
if (entry.key_len == 10 && !memcmp(entry.key, "workerData", 10)) data = entry.value;
|
||||
else if (entry.key_len == 4 && !memcmp(entry.key, "argv", 4)) argv = entry.value;
|
||||
else if (entry.key_len == 12 && !memcmp(entry.key, "transferList", 12)) transfer_list = entry.value;
|
||||
else if (entry.key_len == 14 && !memcmp(entry.key, "resourceLimits", 14)) limits = entry.value;
|
||||
else if (entry.key_len == 4 && !memcmp(entry.key, "name", 4)) name = entry.value;
|
||||
else if (entry.key_len == 6 && !memcmp(entry.key, "stdout", 6)) capture |= scr_dyn_truthy(entry.value) ? 1 : 0;
|
||||
else if (entry.key_len == 6 && !memcmp(entry.key, "stderr", 6)) capture |= scr_dyn_truthy(entry.value) ? 2 : 0;
|
||||
else if (entry.key_len == 5 && !memcmp(entry.key, "stdin", 5)) capture |= scr_dyn_truthy(entry.value) ? 4 : 0;
|
||||
else return scr_worker_refusal("This Worker constructor option is not supported yet");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
const src = join(import.meta.dirname, "..", "src");
|
||||
|
||||
/* Ordinary objects keep their properties behind the object core: units
|
||||
* outside it read and adjust them through the scr_dyn.h own-property
|
||||
* accessors (scr_dyn_obj_count, scr_dyn_obj_prop_at, ...), so the core can
|
||||
* change how it stores them. */
|
||||
const CORE = new Set(["scr_json.c", "scr_js_object.c", "scr_dyn.h"]);
|
||||
|
||||
test("only the object core reads ordinary objects' storage", () => {
|
||||
const offenders: string[] = [];
|
||||
for (const name of readdirSync(src)) {
|
||||
if (!/\.(c|h)$/.test(name) || name.endsWith(".test.c") || CORE.has(name)) continue;
|
||||
const lines = readFileSync(join(src, name), "utf8").split("\n");
|
||||
lines.forEach((line, i) => {
|
||||
if (/\bv\.obj\.(entries|slots|layout|len|cap|index)\b/.test(line))
|
||||
offenders.push(`${name}:${i + 1}: ${line.trim()}`);
|
||||
});
|
||||
}
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
@@ -571,10 +571,10 @@ static ScrDyn *define_from(ScrDyn *target, const char *key, ScrDyn *value, bool
|
||||
}
|
||||
|
||||
static const ScrDynEntry *entry_of(const ScrDyn *obj, const char *key) {
|
||||
const ScrDynEntry *entries = scr_dyn_obj_dict_entries(obj);
|
||||
for (size_t i = 0; i < obj->v.obj.len; i++)
|
||||
if (obj->v.obj.entries[i].key_len == strlen(key) &&
|
||||
memcmp(obj->v.obj.entries[i].key, key, strlen(key)) == 0)
|
||||
return &obj->v.obj.entries[i];
|
||||
if (entries[i].key_len == strlen(key) && memcmp(entries[i].key, key, strlen(key)) == 0)
|
||||
return &entries[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -618,7 +618,7 @@ static void test_copy_property_descriptors(void) {
|
||||
scr_dyn_obj_set(existing, "label", 5, scr_dyn_new_num(9));
|
||||
scr_dyn_release(scr_dyn_copy_property_descriptors(existing, bag));
|
||||
const ScrDynEntry *label = entry_of(existing, "label");
|
||||
check(!scr_exc_pending() && existing->v.obj.len == 5 && existing->v.obj.entries[0].key_len == 5 &&
|
||||
check(!scr_exc_pending() && existing->v.obj.len == 5 && scr_dyn_obj_dict_entries(existing)[0].key_len == 5 &&
|
||||
label->value->v.num == 1,
|
||||
"copy redefines an existing property in place");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user