mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-05 08:13:06 +08:00
Support named properties and Symbol.species on typed arrays held as untyped values (#2068)
This commit is contained in:
@@ -72,7 +72,7 @@ describe.runIf(supported)("macOS arm64 native link info", () => {
|
||||
minimum_os: "14.0",
|
||||
},
|
||||
program: { object, entry_symbol: "main" },
|
||||
runtime_abi: { version: 43, marker: "scr_runtime_abi_v43" },
|
||||
runtime_abi: { version: 44, marker: "scr_runtime_abi_v44" },
|
||||
runtime_pack: {
|
||||
kind: "precompiled",
|
||||
package: "@scriptc/runtime-darwin-arm64",
|
||||
@@ -165,6 +165,6 @@ describe.runIf(supported)("macOS arm64 native link info", () => {
|
||||
() => null,
|
||||
(failure: { stderr?: string }) => failure,
|
||||
);
|
||||
expect(error?.stderr).toContain("scr_runtime_abi_v43");
|
||||
expect(error?.stderr).toContain("scr_runtime_abi_v44");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* of the link contract: a program object keeps this symbol undefined and the
|
||||
* matching runtime defines it, so incompatible manual links fail before the
|
||||
* program can start. */
|
||||
export const RUNTIME_ABI_VERSION = 43 as const;
|
||||
export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v43" as const;
|
||||
export const RUNTIME_ABI_VERSION = 44 as const;
|
||||
export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v44" as const;
|
||||
|
||||
/** Object consumption is useful today, but the complete scr_* surface may
|
||||
* still change before 1.0. The versioned marker prevents accidental mixing;
|
||||
|
||||
@@ -52,7 +52,7 @@ async function fixture(target: NativeTargetSpec, workers = false) {
|
||||
object_format: target.objectFormat,
|
||||
minimum_os: target.minimumOs,
|
||||
},
|
||||
runtime_abi: { version: 43, marker: "scr_runtime_abi_v43" },
|
||||
runtime_abi: { version: 44, marker: "scr_runtime_abi_v44" },
|
||||
compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple },
|
||||
macros: { executable: [], excluded: ["SCR_LIB"], sanitizer: "external-toolchain-required" },
|
||||
flavors: {
|
||||
|
||||
@@ -129,7 +129,7 @@ async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) {
|
||||
object_format: target.objectFormat,
|
||||
minimum_os: target.minimumOs,
|
||||
},
|
||||
runtime_abi: { version: 43, marker: "scr_runtime_abi_v43" },
|
||||
runtime_abi: { version: 44, marker: "scr_runtime_abi_v44" },
|
||||
compiler: {
|
||||
command: "clang",
|
||||
identity: "fixture clang",
|
||||
|
||||
@@ -1117,7 +1117,10 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
|
||||
if (!argNode || (ts.isIdentifier(argNode) && argNode.text === "undefined")) {
|
||||
return { kind: "libCall", fn: "sym.newAnon", args: [], type: SYMBOL_T, loc };
|
||||
}
|
||||
const desc = lowerer.lowerExpr(argNode);
|
||||
let desc = lowerer.lowerExpr(argNode);
|
||||
// A number or boolean description is its ToString.
|
||||
if (desc.type.kind === "f64" || desc.type.kind === "bool")
|
||||
desc = lowerer.ensureString(desc, argNode);
|
||||
if (desc.type.kind !== "string") {
|
||||
lowerer.noLowering(
|
||||
`Symbol with a '${lowerer.fmt(desc.type)}' description`,
|
||||
|
||||
@@ -355,12 +355,13 @@ describe("LLVM backend declares match scr_runtime.h prototypes", () => {
|
||||
#include "scr_runtime.h"
|
||||
|
||||
/* LLVM emits: %ScrBytes = type { i64, i64, i32, ptr, ptr, i8, i8, i8, i8, ptr }.
|
||||
* Keep every ABI fact used by its field-index GEPs explicit here. */
|
||||
* Keep every ABI fact used by its field-index GEPs explicit here. The C
|
||||
* struct continues with fields only the runtime reads. */
|
||||
_Static_assert(sizeof(size_t) == 8, "LLVM ScrBytes expects 64-bit size_t");
|
||||
_Static_assert(sizeof(void *) == 8, "LLVM ScrBytes expects 64-bit pointers");
|
||||
_Static_assert(sizeof(ScrBytesElem) == 4, "LLVM ScrBytes elem field is i32");
|
||||
_Static_assert(_Alignof(ScrBytes) == 8, "LLVM ScrBytes alignment changed");
|
||||
_Static_assert(sizeof(ScrBytes) == 56, "LLVM ScrBytes size changed");
|
||||
_Static_assert(sizeof(ScrBytes) == 64, "ScrBytes size changed");
|
||||
_Static_assert(offsetof(ScrBytes, rc) == 0, "LLVM ScrBytes.rc offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, len) == 8, "LLVM ScrBytes.len offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, elem) == 16, "LLVM ScrBytes.elem offset changed");
|
||||
@@ -370,7 +371,9 @@ _Static_assert(offsetof(ScrBytes, is_buffer) == 40, "LLVM ScrBytes.is_buffer off
|
||||
_Static_assert(offsetof(ScrBytes, is_data_view) == 41, "LLVM ScrBytes.is_data_view offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, external) == 42, "LLVM ScrBytes.external offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, untransferable) == 43, "LLVM ScrBytes.untransferable offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, properties_traced) == 44, "ScrBytes.properties_traced must stay in padding");
|
||||
_Static_assert(offsetof(ScrBytes, shared) == 48, "LLVM ScrBytes.shared offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, properties) == 56, "ScrBytes.properties must follow the LLVM prefix");
|
||||
`,
|
||||
);
|
||||
const driver = resolveCc();
|
||||
|
||||
@@ -378,7 +378,7 @@ done:
|
||||
expect(await symbols(helperObject, ["-u"])).toEqual(await symbols(clangObject, ["-u"]));
|
||||
expect(await symbols(helperObject, ["-gU"])).toEqual(await symbols(clangObject, ["-gU"]));
|
||||
expect(await symbols(helperObject, ["-gU"])).toEqual(["0000000000000000 T _main"]);
|
||||
expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v43");
|
||||
expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v44");
|
||||
|
||||
const clangExe = join(dir, "clang-program");
|
||||
const linkDriver = join(dir, "clang-driver.c");
|
||||
@@ -712,7 +712,7 @@ done:
|
||||
.split("\n");
|
||||
expect(undefinedSymbols).toContain("_sf_scale");
|
||||
expect(undefinedSymbols).toContain("_sf_callback_mix");
|
||||
expect(undefinedSymbols).toContain("_scr_runtime_abi_v43");
|
||||
expect(undefinedSymbols).toContain("_scr_runtime_abi_v44");
|
||||
});
|
||||
|
||||
test("helper and clang object paths retain the same outbound FFI C ABI", async () => {
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7753-typed-array-named-properties.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8321-typed-array-constructor-kind.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -366,7 +366,7 @@ async function build() {
|
||||
package: packageManifest.name,
|
||||
version: packageManifest.version,
|
||||
target: RUNTIME_PACK_MATRIX.target,
|
||||
runtime_abi: { version: 43, marker: "scr_runtime_abi_v43" },
|
||||
runtime_abi: { version: 44, marker: "scr_runtime_abi_v44" },
|
||||
compiler: {
|
||||
command: compiler,
|
||||
identity: compilerVersion,
|
||||
|
||||
@@ -469,7 +469,7 @@ async function build() {
|
||||
package: packageManifest.name,
|
||||
version: packageManifest.version,
|
||||
target: matrix.target,
|
||||
runtime_abi: { version: 43, marker: "scr_runtime_abi_v43" },
|
||||
runtime_abi: { version: 44, marker: "scr_runtime_abi_v44" },
|
||||
compiler: {
|
||||
command: compiler,
|
||||
identity: compilerVersion,
|
||||
|
||||
@@ -190,6 +190,8 @@ ScrBytes *scr_bytes_take_data(uint8_t *data, size_t len) {
|
||||
b->external = false;
|
||||
b->untransferable = false;
|
||||
b->shared = NULL;
|
||||
b->properties = NULL;
|
||||
b->properties_traced = false;
|
||||
#ifdef SCR_RC_AUDIT
|
||||
scr_live_bytes++;
|
||||
#endif
|
||||
@@ -304,6 +306,7 @@ void scr_bytes_release(ScrBytes *b) {
|
||||
if (!b || b->rc == SIZE_MAX) return; /* NULL: an uninitialized `let` local */
|
||||
if (--b->rc == 0) {
|
||||
scr_weak_dispose_inline(b);
|
||||
if (b->properties) scr_dyn_release(b->properties);
|
||||
if (b->backing) {
|
||||
scr_bytes_release(b->backing); /* a view: data points into the owner */
|
||||
} else if (b->shared) {
|
||||
@@ -740,6 +743,8 @@ ScrBytes *scr_bytes_subarray(ScrBytes *b, double start, double end) {
|
||||
v->external = false;
|
||||
v->untransferable = false;
|
||||
v->shared = owner->shared;
|
||||
v->properties = NULL;
|
||||
v->properties_traced = false;
|
||||
#ifdef SCR_RC_AUDIT
|
||||
scr_live_bytes++;
|
||||
#endif
|
||||
@@ -883,7 +888,9 @@ ScrBytes *scr_bytes_from_dyn(ScrBytesElem elem, const ScrDyn *value, bool from)
|
||||
if (value->kind == SCR_DYN_BYTES) return scr_bytes_convert(elem, value->v.bytes);
|
||||
if (!from && scr_buffer_storage_is(value)) return scr_array_buffer_view(elem, value, scr_dyn_undefined(), scr_dyn_undefined());
|
||||
if (!from && (value->kind == SCR_DYN_NUM || value->kind == SCR_DYN_BOOL ||
|
||||
value->kind == SCR_DYN_STR || value->kind == SCR_DYN_NULL || value->kind == SCR_DYN_UNDEF)) {
|
||||
value->kind == SCR_DYN_STR || value->kind == SCR_DYN_NULL || value->kind == SCR_DYN_UNDEF ||
|
||||
value->kind == SCR_DYN_SYMBOL || value->kind == SCR_DYN_BIGINT)) {
|
||||
/* ToIndex: a Symbol or BigInt length throws ToNumber's TypeError. */
|
||||
double number;
|
||||
if (!scr_dyn_number_coerce_js(value, &number)) return NULL;
|
||||
return scr_bytes_new(elem, number);
|
||||
@@ -1009,6 +1016,8 @@ ScrBytes *scr_dataview_new(ScrBytes *src, double byte_off, bool has_len, double
|
||||
v->external = false;
|
||||
v->untransferable = false;
|
||||
v->shared = owner->shared;
|
||||
v->properties = NULL;
|
||||
v->properties_traced = false;
|
||||
#ifdef SCR_RC_AUDIT
|
||||
scr_live_bytes++;
|
||||
#endif
|
||||
@@ -2948,6 +2957,8 @@ static ScrBytes *scr_pool_take(size_t len) {
|
||||
v->external = false;
|
||||
v->untransferable = false;
|
||||
v->shared = NULL;
|
||||
v->properties = NULL;
|
||||
v->properties_traced = false;
|
||||
#ifdef SCR_RC_AUDIT
|
||||
scr_live_bytes++;
|
||||
#endif
|
||||
|
||||
@@ -65,7 +65,15 @@ typedef struct ScrBytes {
|
||||
bool is_data_view; /* DataView is distinct from a numeric typed array. */
|
||||
bool external; /* Root aliases foreign memory; releasing it never frees data. */
|
||||
bool untransferable; /* A Buffer pool slab: its ArrayBuffer cannot be transferred. */
|
||||
/* The cycle collector's last trace of `properties` from the one checked
|
||||
* value that held this view (see scr_dyn_trace_v). In padding, so the
|
||||
* offsets compiled code uses hold. */
|
||||
bool properties_traced;
|
||||
ScrSharedBytes *shared; /* Neutral backing, borrowed by views from their local owner. */
|
||||
/* Named properties a program adds to the view (`ta.foo = 1`,
|
||||
* Object.defineProperty): a checked object, NULL until the first one.
|
||||
* Runtime-only and last, so compiled code's field indices hold. */
|
||||
struct ScrDyn *properties;
|
||||
} ScrBytes;
|
||||
|
||||
/* Copies out of and into shared storage, made of relaxed atomic accesses
|
||||
|
||||
@@ -148,7 +148,7 @@ static void scr_enable_utf8_console_output(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
void scr_runtime_abi_v43(void) {}
|
||||
void scr_runtime_abi_v44(void) {}
|
||||
|
||||
void scr_init(void) {
|
||||
#ifdef SCR_WORKERS
|
||||
|
||||
@@ -81,7 +81,7 @@ void scr_init(void);
|
||||
/* Program objects emitted by the bundled LLVM helper reference this symbol.
|
||||
* Its versioned spelling makes a mismatched manual runtime link fail before
|
||||
* the program can start. */
|
||||
void scr_runtime_abi_v43(void);
|
||||
void scr_runtime_abi_v44(void);
|
||||
void scr_runtime_workers_v8(void);
|
||||
|
||||
/* ── the trap funnel (scr_console.c; scr_library.c under -DSCR_LIB) ──────
|
||||
|
||||
@@ -964,6 +964,9 @@ void scr_dyn_install_iterator_symbol(ScrDyn *key);
|
||||
void scr_dyn_install_has_instance_symbol(ScrDyn *key);
|
||||
void scr_dyn_install_async_iterator_symbol(ScrDyn *key);
|
||||
void scr_dyn_install_async_dispose_symbol(ScrDyn *key);
|
||||
void scr_dyn_install_species_symbol(ScrDyn *key);
|
||||
/* Records each well-known symbol the symbol unit creates. */
|
||||
void scr_dyn_install_well_known_symbol(ScrDyn *key);
|
||||
void scr_dyn_install_to_string_tag_symbol(ScrDyn *key);
|
||||
/* True when key is Symbol.asyncDispose (registered by the symbol unit when
|
||||
* the program names it); handle symbol_get ops outside that unit use it. */
|
||||
@@ -1051,6 +1054,9 @@ ScrDyn *scr_bytes_construct(const ScrDyn *callee, const ScrDyn *args, const ScrS
|
||||
extern SCR_TL bool (*scr_dyn_global_construct_fn)(const ScrDyn *callee, const ScrDyn *args,
|
||||
ScrDyn **result);
|
||||
ScrDyn *scr_dyn_bytes_key_get(const ScrDyn *value, const ScrStr *key);
|
||||
/* A typed array's, Buffer's or DataView's own named property (+1), or NULL
|
||||
* without an exception when it has none. */
|
||||
ScrDyn *scr_bytes_named_get(const ScrDyn *view, const char *key, size_t key_len);
|
||||
/* The Buffer-flavored twin (stream chunks): string coercion/toString
|
||||
* decode utf8 instead of joining elements. */
|
||||
ScrDyn *scr_dyn_new_buffer(const ScrBytes *b);
|
||||
|
||||
@@ -1712,6 +1712,20 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
|
||||
if (recv->kind == SCR_DYN_BYTES) {
|
||||
ScrBytes *bytes = recv->v.bytes;
|
||||
/* An own property of the view shadows the prototype's method. */
|
||||
ScrDyn *member = bytes->properties ? scr_bytes_named_get(recv, method, strlen(method)) : NULL;
|
||||
if (!member && scr_exc_pending()) return NULL;
|
||||
if (member) {
|
||||
ScrDyn *result = NULL;
|
||||
if (!scr_dyn_is_callable(member)) dyn_throw_not_fn(what);
|
||||
else {
|
||||
scr_dyn_this_push_dyn(recv);
|
||||
result = scr_dyn_call(member, args, argc, what);
|
||||
scr_dyn_this_pop();
|
||||
}
|
||||
scr_dyn_release(member);
|
||||
return result;
|
||||
}
|
||||
if (bytes->is_data_view) {
|
||||
static const char *const kinds[] = { "Uint8", "Int8", "Uint16", "Int16", "Uint32", "Int32", "Float32", "Float64", "BigUint64", "BigInt64", "Float16" };
|
||||
const bool get = !strncmp(method, "get", 3);
|
||||
|
||||
@@ -794,6 +794,52 @@ ScrStr *scr_insp_regex(ScrRegex *re) {
|
||||
return ib_take(&out);
|
||||
}
|
||||
|
||||
/* Named properties of a typed array, Buffer or DataView (its
|
||||
* ScrBytes.properties table): slot k is the k-th string-keyed entry, then
|
||||
* 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);
|
||||
}
|
||||
|
||||
static ScrStr *insp_accessor_tag(const ScrDynEntry *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;
|
||||
ScrStr *key = NULL;
|
||||
if (k < props->v.obj.len) {
|
||||
ent = &props->v.obj.entries[k];
|
||||
} else {
|
||||
ScrDyn *symbol = props->symbol_keys->v.arr.items[k - props->v.obj.len];
|
||||
ent = scr_dyn_own_symbol_entry(props, symbol);
|
||||
if (ent && ent->enumerable) key = scr_insp_dyn(symbol, recurse, depth);
|
||||
}
|
||||
if (!ent || !ent->enumerable) return NULL;
|
||||
ScrStr *val = ent->accessor ? insp_accessor_tag(ent) : scr_insp_dyn(ent->value, recurse, depth);
|
||||
InspBuf eb = {0};
|
||||
if (key) ib_bytes(&eb, key->data, key->len);
|
||||
else insp_key_into(&eb, ent->key, ent->key_len);
|
||||
ib_cstr(&eb, ": ");
|
||||
ib_bytes(&eb, val->data, val->len);
|
||||
scr_str_release(key);
|
||||
scr_str_release(val);
|
||||
return ib_take(&eb);
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Buffer: the custom-inspect hex form `<Buffer aa bb ...>`, capped at
|
||||
* INSPECT_MAX_BYTES (50); renders fully at any depth (custom inspection
|
||||
* runs before the depth check in Node). u8 element kind only (the
|
||||
@@ -816,6 +862,17 @@ ScrStr *scr_insp_buffer(ScrBytes *b) {
|
||||
remaining > 1 ? "s" : "");
|
||||
ib_bytes(&out, more, (size_t)n);
|
||||
}
|
||||
/* Named properties follow as `, key: value`, rendered as the members of
|
||||
* an object at depth 0 (Buffer's custom inspect). */
|
||||
bool first = true;
|
||||
for (size_t k = 0; k < insp_view_property_slots(b->properties); k++) {
|
||||
ScrStr *entry = insp_view_property(b->properties, k, 1, 2);
|
||||
if (!entry) continue;
|
||||
if (!first || b->len) ib_cstr(&out, ", ");
|
||||
first = false;
|
||||
ib_bytes(&out, entry->data, entry->len);
|
||||
scr_str_release(entry);
|
||||
}
|
||||
ib_char(&out, '>');
|
||||
return ib_take(&out);
|
||||
}
|
||||
@@ -899,6 +956,12 @@ static ScrStr *scr_insp_data_view(const ScrBytes *b, double recurse, double dept
|
||||
entry = ib_take(&eb);
|
||||
scr_insp_entry(entry, false);
|
||||
scr_str_release(entry);
|
||||
for (size_t k = 0; k < insp_view_property_slots(b->properties); k++) {
|
||||
entry = insp_view_property(b->properties, k, recurse + 1, depth);
|
||||
if (!entry) continue;
|
||||
scr_insp_entry(entry, false);
|
||||
scr_str_release(entry);
|
||||
}
|
||||
ScrStr *base = scr_str_new("", 0);
|
||||
ScrStr *b0 = scr_str_new("DataView {", 10);
|
||||
ScrStr *b1 = scr_str_new("}", 1);
|
||||
@@ -1537,7 +1600,10 @@ static ScrStr *scr_insp_obj(ScrDyn *d, double recurse, double depth, const char
|
||||
ScrStr *scr_insp_typed_array(ScrBytes *b, double recurse, double depth) {
|
||||
char prefix[48];
|
||||
int pn = snprintf(prefix, sizeof prefix, "%s(%zu) [", scr_bytes_elem_name(b->elem), b->len);
|
||||
if (b->len == 0) {
|
||||
/* Named properties follow the elements as `key: value` entries. */
|
||||
ScrDyn *props = b->properties;
|
||||
size_t nprops = insp_view_property_count(props);
|
||||
if (b->len == 0 && nprops == 0) {
|
||||
InspBuf out = {0};
|
||||
ib_bytes(&out, prefix, (size_t)pn);
|
||||
ib_char(&out, ']');
|
||||
@@ -1561,10 +1627,19 @@ ScrStr *scr_insp_typed_array(ScrBytes *b, double recurse, double depth) {
|
||||
scr_insp_entry(m, true);
|
||||
scr_str_release(m);
|
||||
}
|
||||
size_t entries = shown + (more ? 1 : 0);
|
||||
for (size_t k = 0; nprops && k < insp_view_property_slots(props); k++) {
|
||||
ScrStr *entry = insp_view_property(props, k, recurse + 1, depth);
|
||||
if (!entry) continue;
|
||||
/* Node's grouping pads by the element at the entry's output position. */
|
||||
scr_insp_entry(entry, entries < b->len);
|
||||
scr_str_release(entry);
|
||||
entries++;
|
||||
}
|
||||
ScrStr *base = scr_str_new("", 0);
|
||||
ScrStr *b0 = scr_str_new(prefix, (size_t)pn);
|
||||
ScrStr *b1 = scr_str_new("]", 1);
|
||||
ScrStr *out = scr_insp_end(base, b0, b1, recurse + 1, true, more);
|
||||
ScrStr *out = scr_insp_end(base, b0, b1, recurse + 1, true, nprops ? entries > 100 : more);
|
||||
scr_str_release(base);
|
||||
scr_str_release(b0);
|
||||
scr_str_release(b1);
|
||||
|
||||
+553
-65
@@ -210,6 +210,33 @@ void scr_dyn_install_has_instance_symbol(ScrDyn *key) {
|
||||
scr_atexit(scr_has_instance_symbol_cleanup);
|
||||
}
|
||||
|
||||
/* Symbol.species, installed when the program first evaluates it: no
|
||||
* constructor names a species before then. */
|
||||
static SCR_TL ScrDyn *scr_species_symbol;
|
||||
static void scr_species_symbol_cleanup(void) {
|
||||
scr_dyn_release(scr_species_symbol);
|
||||
scr_species_symbol = NULL;
|
||||
}
|
||||
void scr_dyn_install_species_symbol(ScrDyn *key) {
|
||||
if (scr_species_symbol) return;
|
||||
scr_species_symbol = scr_dyn_retain(key);
|
||||
scr_atexit(scr_species_symbol_cleanup);
|
||||
}
|
||||
|
||||
/* The well-known symbols the program has created. They live until exit,
|
||||
* so their identities are compared without a reference. */
|
||||
static SCR_TL const ScrSym *scr_well_known_symbols[24];
|
||||
static SCR_TL size_t scr_well_known_count;
|
||||
void scr_dyn_install_well_known_symbol(ScrDyn *key) {
|
||||
if (scr_well_known_count < sizeof scr_well_known_symbols / sizeof scr_well_known_symbols[0])
|
||||
scr_well_known_symbols[scr_well_known_count++] = key->v.symbol.value;
|
||||
}
|
||||
static bool scr_dyn_well_known_symbol(const ScrDyn *key) {
|
||||
for (size_t i = 0; i < scr_well_known_count; i++)
|
||||
if (scr_well_known_symbols[i] == key->v.symbol.value) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Symbol.toStringTag, installed when the program first evaluates it: no
|
||||
* object holds the key before then. */
|
||||
static SCR_TL ScrDyn *scr_to_string_tag_symbol;
|
||||
@@ -908,6 +935,17 @@ void scr_dyn_trace_v(void *ptr, ScrTraceVisit visit, void *ctx) {
|
||||
}
|
||||
break;
|
||||
case SCR_DYN_FUNC: visit(d->v.fn.clo, ctx); break;
|
||||
case SCR_DYN_BYTES: {
|
||||
/* A view's named properties belong to its ScrBytes. Only a view this
|
||||
* value alone holds owns that edge; a shared view's table stays out of
|
||||
* trial deletion. Teardown reads the snapshot, not the count. */
|
||||
ScrBytes *bytes = d->v.bytes;
|
||||
if (bytes->properties) {
|
||||
bytes->properties_traced = bytes->rc == 1;
|
||||
if (bytes->properties_traced) visit(bytes->properties, ctx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SCR_DYN_PROXY:
|
||||
visit(d->v.proxy.target, ctx);
|
||||
visit(d->v.proxy.handler, ctx);
|
||||
@@ -1174,6 +1212,8 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) {
|
||||
scr_str_release(d->v.str);
|
||||
break;
|
||||
case SCR_DYN_BYTES:
|
||||
/* The collector frees a traced table itself. */
|
||||
if (collected && d->v.bytes->properties && d->v.bytes->properties_traced) d->v.bytes->properties = NULL;
|
||||
scr_bytes_release(d->v.bytes);
|
||||
break;
|
||||
case SCR_DYN_ARR:
|
||||
@@ -2603,10 +2643,27 @@ static bool scr_dyn_typed_ref_view_answer(ScrDyn *object, bool (*query)(const Sc
|
||||
return true;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_bytes_properties(const ScrDyn *v);
|
||||
static bool scr_dyn_is_extensible_view(const ScrDyn *view) {
|
||||
if (view->kind == SCR_DYN_BYTES) return !view->v.bytes->properties || !view->v.bytes->properties->non_extensible;
|
||||
return (view->kind == SCR_DYN_OBJ || view->kind == SCR_DYN_ARR) && !view->non_extensible;
|
||||
}
|
||||
|
||||
/* A view's integrity state lives on its named-property table. A typed array
|
||||
* with elements cannot be frozen, and cannot be sealed while extensible: as
|
||||
* in V8, the named properties are sealed or frozen and the view made
|
||||
* non-extensible before the TypeError. */
|
||||
static bool scr_bytes_integrity_fence(const ScrDyn *view, const char *verb) {
|
||||
if (view->v.bytes->is_data_view || view->v.bytes->len == 0) return false;
|
||||
ScrJsonBuf message;
|
||||
scr_jb_init(&message);
|
||||
scr_jb_puts(&message, "Cannot ");
|
||||
scr_jb_puts(&message, verb);
|
||||
scr_jb_puts(&message, " array buffer views with elements");
|
||||
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&message));
|
||||
return true;
|
||||
}
|
||||
|
||||
/* A plain function's own properties (name, length, prototype, and the ones
|
||||
* a program adds) live in its property table, which carries the function's
|
||||
* integrity state too: freezing or sealing the function freezes or seals
|
||||
@@ -2655,6 +2712,7 @@ ScrDyn *scr_dyn_prevent_extensions(ScrDyn *object) {
|
||||
if (object->kind == SCR_DYN_ARR && object->v.arr.properties)
|
||||
object->v.arr.properties->non_extensible = true;
|
||||
}
|
||||
if (object->kind == SCR_DYN_BYTES) scr_bytes_properties(object)->non_extensible = true;
|
||||
return scr_dyn_retain(object);
|
||||
}
|
||||
|
||||
@@ -2685,6 +2743,12 @@ ScrDyn *scr_dyn_seal(ScrDyn *object) {
|
||||
if (scr_dyn_class_integrity(object, scr_dyn_seal))
|
||||
return scr_exc_pending() ? NULL : scr_dyn_retain(object);
|
||||
if (scr_dyn_class_integrity_fence(object, "Object.seal")) return NULL;
|
||||
if (object->kind == SCR_DYN_BYTES) {
|
||||
bool extensible = scr_dyn_is_extensible_view(object);
|
||||
scr_dyn_release(scr_dyn_seal(scr_bytes_properties(object)));
|
||||
if (extensible && scr_bytes_integrity_fence(object, "seal")) return NULL;
|
||||
return scr_dyn_retain(object);
|
||||
}
|
||||
if (object->symbol_properties) scr_dyn_release(scr_dyn_seal(object->symbol_properties));
|
||||
ScrDyn *table = scr_dyn_fn_integrity_table(object);
|
||||
if (table) {
|
||||
@@ -2720,6 +2784,8 @@ bool scr_dyn_is_sealed(const ScrDyn *object) {
|
||||
if (object->kind == SCR_DYN_ARR)
|
||||
return object->non_extensible && (object->v.arr.len == 0 || object->v.arr.sealed) &&
|
||||
(!object->v.arr.properties || scr_dyn_is_sealed(object->v.arr.properties));
|
||||
/* V8 answers from a typed array's named properties alone. */
|
||||
if (object->kind == SCR_DYN_BYTES) return object->v.bytes->properties && scr_dyn_is_sealed(object->v.bytes->properties);
|
||||
if (object->kind != SCR_DYN_OBJ) return true;
|
||||
if (!object->non_extensible) return false;
|
||||
for (size_t i = 0; i < object->v.obj.len; i++) if (object->v.obj.entries[i].configurable) return false;
|
||||
@@ -2926,6 +2992,65 @@ static bool scr_dyn_data_view_proto_name(const ScrStr *key) {
|
||||
return scr_dyn_name_in(key, names, sizeof names / sizeof names[0]);
|
||||
}
|
||||
|
||||
/* A canonical numeric string ("1", "-0", "1.5", "Infinity"): typed arrays
|
||||
* answer those keys as element indices, never as named properties. */
|
||||
static bool scr_bytes_numeric_key(const ScrStr *key, double *index) {
|
||||
if (key->len == 2 && memcmp(key->data, "-0", 2) == 0) { *index = -0.0; return true; }
|
||||
*index = scr_string_to_number(key);
|
||||
char spelling[32];
|
||||
size_t length = scr_f64_to_str(*index, spelling);
|
||||
return length == key->len && memcmp(spelling, key->data, length) == 0;
|
||||
}
|
||||
|
||||
/* Whether the view has an own named property `key`. */
|
||||
static bool scr_bytes_has_named(const ScrDyn *v, const ScrStr *key) {
|
||||
const ScrDyn *properties = v->v.bytes->properties;
|
||||
return properties && scr_dyn_obj_get(properties, key->data, key->len);
|
||||
}
|
||||
|
||||
/* The view's own named property `key` read with the view as a getter's
|
||||
* `this` (+1), or NULL without an exception when the view has none. Only
|
||||
* reads the table, so callers do not link the typed array methods. */
|
||||
ScrDyn *scr_bytes_named_get(const ScrDyn *view, const char *key, size_t key_len) {
|
||||
const ScrDyn *properties = view->v.bytes->properties;
|
||||
if (!properties || !scr_dyn_obj_get(properties, key, key_len)) return NULL;
|
||||
return scr_dyn_obj_read_receiver(properties, key, key_len, view);
|
||||
}
|
||||
|
||||
/* The view's named-property table, created on first use. Borrowed. The
|
||||
* table belongs to the ScrBytes, which several checked values can wrap. */
|
||||
static ScrDyn *scr_bytes_properties(const ScrDyn *v) {
|
||||
ScrBytes *bytes = v->v.bytes;
|
||||
if (!bytes->properties) bytes->properties = scr_dyn_new_obj_null_proto();
|
||||
return bytes->properties;
|
||||
}
|
||||
|
||||
/* Accessors without a setter on the view's prototype chain: writing one
|
||||
* that the view does not own throws V8's strict-mode TypeError. */
|
||||
static bool scr_bytes_getter_name(const ScrDyn *v, const ScrStr *key) {
|
||||
static const char *const names[] = { "byteLength", "byteOffset", "buffer", "length", "parent", "offset" };
|
||||
size_t count = v->v.bytes->is_data_view ? 3 : v->buffer ? 6 : 4;
|
||||
return scr_dyn_name_in(key, names, count);
|
||||
}
|
||||
|
||||
/* Keys that the runtime's conversions, iteration and printing of a view
|
||||
* read from its prototype without consulting its own properties. */
|
||||
static bool scr_bytes_protocol_name(const ScrStr *key) {
|
||||
static const char *const names[] = { "toString", "valueOf", "toLocaleString", "toJSON", "join" };
|
||||
return scr_dyn_name_in(key, names, sizeof names / sizeof names[0]);
|
||||
}
|
||||
|
||||
static void scr_bytes_protocol_fence(const char *key, size_t key_len) {
|
||||
ScrJsonBuf message;
|
||||
scr_jb_init(&message);
|
||||
scr_jb_puts(&message, "defining ");
|
||||
scr_jb_write(&message, key, key_len);
|
||||
scr_jb_puts(&message, " on a typed array, Buffer or DataView is not supported yet");
|
||||
ScrStr *text = scr_jb_finish(&message);
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, text->data, text->len, "SC2020");
|
||||
scr_str_release(text);
|
||||
}
|
||||
|
||||
/* `in` on a typed array, Buffer or DataView. On a typed array, a canonical
|
||||
* numeric key (one that ToString(ToNumber(key)) reproduces, or "-0")
|
||||
* answers by bounds alone: an integral index, not -0, below the length.
|
||||
@@ -2933,6 +3058,7 @@ static bool scr_dyn_data_view_proto_name(const ScrStr *key) {
|
||||
* Buffer, or Object.prototype. A DataView has no indexed elements: its keys
|
||||
* name members of DataView.prototype or Object.prototype. */
|
||||
static bool scr_dyn_bytes_in(const ScrDyn *v, const ScrStr *key) {
|
||||
if (scr_bytes_has_named(v, key)) return true;
|
||||
if (v->v.bytes->is_data_view) return scr_dyn_data_view_proto_name(key) || scr_dyn_object_proto_name(key);
|
||||
if (key->len == 2 && memcmp(key->data, "-0", 2) == 0) return false;
|
||||
double index = scr_string_to_number(key);
|
||||
@@ -3048,6 +3174,19 @@ ScrDyn *scr_dyn_own_descriptor(const ScrDyn *value, const ScrStr *key) {
|
||||
scr_dyn_obj_set(out, "configurable", 12, scr_dyn_new_bool(false));
|
||||
return out;
|
||||
}
|
||||
if (value->kind == SCR_DYN_BYTES && scr_bytes_has_named(value, key))
|
||||
return scr_dyn_own_descriptor(value->v.bytes->properties, key);
|
||||
if (value->kind == SCR_DYN_BYTES && !value->v.bytes->is_data_view) {
|
||||
if (!scr_dyn_canonical_own_index(key, value->v.bytes->len)) return scr_dyn_retain(scr_dyn_undefined());
|
||||
size_t index = 0;
|
||||
for (size_t i = 0; i < key->len; i++) index = index * 10 + (size_t)(key->data[i] - '0');
|
||||
ScrDyn *out = scr_dyn_new_obj();
|
||||
scr_dyn_obj_set(out, "value", 5, scr_dyn_new_num(scr_bytes_get(value->v.bytes, (double)index)));
|
||||
scr_dyn_obj_set(out, "writable", 8, scr_dyn_new_bool(true));
|
||||
scr_dyn_obj_set(out, "enumerable", 10, scr_dyn_new_bool(true));
|
||||
scr_dyn_obj_set(out, "configurable", 12, scr_dyn_new_bool(true));
|
||||
return out;
|
||||
}
|
||||
if (value->kind == SCR_DYN_ARR) {
|
||||
if (value->v.arr.properties &&
|
||||
scr_dyn_obj_get(value->v.arr.properties, key->data, key->len))
|
||||
@@ -5173,6 +5312,88 @@ static double *dyn_bytes_values(const ScrBytes *b, size_t n) {
|
||||
return values;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_dyn_symbol_receiver(const ScrDyn *value);
|
||||
static ScrDynEntry *scr_dyn_symbol_entry(const ScrDyn *receiver, const ScrDyn *key, bool own);
|
||||
|
||||
/* Whether a typed-array constructor has a Symbol.species of its own,
|
||||
* shadowing the one it inherits from %TypedArray%. */
|
||||
static bool scr_bytes_ctor_own_species(const ScrDyn *constructor) {
|
||||
ScrDyn *table = scr_dyn_symbol_receiver(constructor);
|
||||
bool own = table && scr_dyn_symbol_entry(table, scr_species_symbol, true);
|
||||
scr_dyn_release(table);
|
||||
return own;
|
||||
}
|
||||
|
||||
/* A relative index (ToIntegerOrInfinity, negatives from the end), clamped
|
||||
* to [0, len], as scr_bytes_slice computes it. */
|
||||
static size_t scr_bytes_rel_index_of(double i, size_t len) {
|
||||
if (i != i) return 0;
|
||||
double t = trunc(i);
|
||||
if (t < 0) t += (double)len;
|
||||
if (t < 0) return 0;
|
||||
if (t > (double)len) return len;
|
||||
return (size_t)t;
|
||||
}
|
||||
|
||||
/* SpeciesConstructor of a typed array: NULL (and no exception) for the
|
||||
* default constructor of its kind, else the species constructor (+1).
|
||||
* Only a `constructor` the program set on the view can differ. */
|
||||
static ScrDyn *scr_bytes_species(ScrDyn *recv, bool *failed) {
|
||||
*failed = false;
|
||||
ScrStr *name = scr_str_new("constructor", 11);
|
||||
bool own = scr_bytes_has_named(recv, name);
|
||||
ScrDyn *constructor = own ? scr_dyn_obj_read_receiver(recv->v.bytes->properties, name->data, name->len, recv) : NULL;
|
||||
scr_str_release(name);
|
||||
if (!own) return NULL;
|
||||
if (!constructor) { *failed = true; return NULL; }
|
||||
if (constructor->kind == SCR_DYN_UNDEF) { scr_dyn_release(constructor); return NULL; }
|
||||
if (constructor->kind != SCR_DYN_OBJ && constructor->kind != SCR_DYN_FUNC) {
|
||||
scr_dyn_release(constructor);
|
||||
static const char message[] = "object.constructor is not an object";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
*failed = true;
|
||||
return NULL;
|
||||
}
|
||||
/* Before the program creates the species symbol, no constructor can have
|
||||
* a Symbol.species property of its own. */
|
||||
ScrDyn *species = scr_species_symbol ? scr_dyn_symbol_key_get(constructor, scr_species_symbol, true)
|
||||
: scr_dyn_retain(scr_dyn_undefined());
|
||||
if (species && species->kind == SCR_DYN_UNDEF && scr_bytes_ctor_elem(constructor) >= 0 &&
|
||||
(!scr_species_symbol || !scr_bytes_ctor_own_species(constructor))) {
|
||||
/* A built-in typed-array constructor inherits %TypedArray%'s species
|
||||
* getter, which returns the constructor itself. */
|
||||
scr_dyn_release(species);
|
||||
species = scr_dyn_retain(constructor);
|
||||
}
|
||||
scr_dyn_release(constructor);
|
||||
if (scr_exc_pending()) { scr_dyn_release(species); *failed = true; return NULL; }
|
||||
if (!species || species->kind == SCR_DYN_UNDEF || species->kind == SCR_DYN_NULL ||
|
||||
scr_bytes_ctor_elem(species) == (int)recv->v.bytes->elem) {
|
||||
scr_dyn_release(species);
|
||||
return NULL;
|
||||
}
|
||||
if (!scr_dyn_is_callable(species)) {
|
||||
scr_dyn_release(species);
|
||||
static const char message[] = "object.constructor[Symbol.species] is not a constructor";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
*failed = true;
|
||||
return NULL;
|
||||
}
|
||||
return species;
|
||||
}
|
||||
|
||||
/* TypedArraySpeciesCreate(recv, [n]): a new array of the receiver's kind
|
||||
* (and Buffer flavor), or the species constructor's checked result. */
|
||||
static ScrDyn *scr_bytes_species_create(ScrDyn *recv, size_t n) {
|
||||
bool failed;
|
||||
ScrDyn *species = scr_bytes_species(recv, &failed);
|
||||
if (failed) return NULL;
|
||||
if (!species) return dyn_bytes_wrap(recv, scr_bytes_new(recv->v.bytes->elem, (double)n));
|
||||
ScrDyn *result = scr_bytes_static_create(species, n);
|
||||
scr_dyn_release(species);
|
||||
return result;
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *args, size_t argc, bool *handled) {
|
||||
ScrBytes *bytes = recv->v.bytes;
|
||||
size_t len = bytes->len;
|
||||
@@ -5192,11 +5413,48 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar
|
||||
if (scr_exc_pending()) return NULL;
|
||||
double endD = scr_dyn_bytes_index_arg(args, argc, 1, (double)blen);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
ScrBytes *out = recv->buffer || scr_dyn_bytes_named(method, "subarray")
|
||||
? scr_bytes_subarray(bytes, startD, endD) : scr_bytes_slice(bytes, startD, endD);
|
||||
ScrDyn *d = recv->buffer ? scr_dyn_new_buffer(out) : scr_dyn_new_bytes(out);
|
||||
const bool subarray = recv->buffer || scr_dyn_bytes_named(method, "subarray");
|
||||
bool failed;
|
||||
ScrDyn *species = recv->buffer ? NULL : scr_bytes_species(recv, &failed);
|
||||
if (!recv->buffer && failed) return NULL;
|
||||
if (species && !subarray) {
|
||||
/* TypedArraySpeciesCreate runs first: the constructor can change the
|
||||
* source, and the copy reads its elements as they are afterwards,
|
||||
* up to its current length. */
|
||||
size_t from = scr_bytes_rel_index_of(startD, blen), to = scr_bytes_rel_index_of(endD, blen);
|
||||
size_t count = to > from ? to - from : 0;
|
||||
ScrDyn *result = scr_bytes_static_create(species, count);
|
||||
scr_dyn_release(species);
|
||||
if (!result) return NULL;
|
||||
size_t end = to < bytes->len ? to : bytes->len;
|
||||
for (size_t k = from, n = 0; k < end; k++, n++)
|
||||
scr_bytes_set(result->v.bytes, (double)n, scr_bytes_get(bytes, (double)k));
|
||||
return result;
|
||||
}
|
||||
ScrBytes *out = subarray ? scr_bytes_subarray(bytes, startD, endD) : scr_bytes_slice(bytes, startD, endD);
|
||||
if (!species) {
|
||||
ScrDyn *d = recv->buffer ? scr_dyn_new_buffer(out) : scr_dyn_new_bytes(out);
|
||||
scr_bytes_release(out);
|
||||
return d;
|
||||
}
|
||||
/* A subarray's species constructor views the same buffer. */
|
||||
ScrDyn *ctor_args = scr_dyn_new_arr();
|
||||
scr_dyn_arr_push(ctor_args, scr_array_buffer_from_bytes(bytes));
|
||||
scr_dyn_arr_push(ctor_args, scr_dyn_new_num(scr_bytes_byte_offset(out)));
|
||||
scr_dyn_arr_push(ctor_args, scr_dyn_new_num((double)out->len));
|
||||
ScrStr *what = scr_str_new("TypedArray species", 18);
|
||||
ScrDyn *result = scr_dyn_construct(species, ctor_args, what);
|
||||
scr_str_release(what);
|
||||
scr_dyn_release(ctor_args);
|
||||
if (result && (result->kind != SCR_DYN_BYTES || result->v.bytes->is_data_view)) {
|
||||
scr_dyn_release(result);
|
||||
result = NULL;
|
||||
static const char message[] = "this is not a typed array.";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
}
|
||||
scr_dyn_release(species);
|
||||
scr_bytes_release(out);
|
||||
return d;
|
||||
return result;
|
||||
}
|
||||
if (scr_dyn_bytes_named(method, "set")) {
|
||||
double offset = scr_dyn_bytes_index_arg(args, argc, 1, 0);
|
||||
@@ -5234,6 +5492,22 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar
|
||||
scr_str_release(joined);
|
||||
return out;
|
||||
}
|
||||
if (scr_dyn_bytes_named(method, "toLocaleString")) {
|
||||
/* Each element's Number.prototype.toLocaleString, joined by ",". */
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (i > 0) scr_jb_putc(&b, ',');
|
||||
if (i >= bytes->len) continue;
|
||||
ScrStr *text = scr_intl_num_format_en_us(scr_bytes_get(bytes, (double)i));
|
||||
scr_jb_write(&b, text->data, text->len);
|
||||
scr_str_release(text);
|
||||
}
|
||||
ScrStr *joined = scr_jb_finish(&b);
|
||||
ScrDyn *out = scr_dyn_new_str(joined);
|
||||
scr_str_release(joined);
|
||||
return out;
|
||||
}
|
||||
if (scr_dyn_bytes_named(method, "keys") || scr_dyn_bytes_named(method, "values") || scr_dyn_bytes_named(method, "entries"))
|
||||
return scr_dyn_native_handle_iterator(recv, scr_dyn_bytes_named(method, "keys") ? 1 : scr_dyn_bytes_named(method, "entries") ? 2 : 0);
|
||||
if (scr_dyn_bytes_named(method, "indexOf") || scr_dyn_bytes_named(method, "lastIndexOf") || scr_dyn_bytes_named(method, "includes")) {
|
||||
@@ -5272,7 +5546,8 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar
|
||||
if (!scr_dyn_bytes_cb_check(args, argc)) return NULL;
|
||||
ScrDyn *cb = args[0], *this_arg = argc > 1 ? args[1] : scr_dyn_undefined();
|
||||
const bool backward = scr_dyn_bytes_named(method, "findLast") || scr_dyn_bytes_named(method, "findLastIndex");
|
||||
ScrBytes *mapped = scr_dyn_bytes_named(method, "map") ? scr_bytes_new(bytes->elem, (double)len) : NULL;
|
||||
ScrDyn *mapped = NULL;
|
||||
if (scr_dyn_bytes_named(method, "map") && !(mapped = scr_bytes_species_create(recv, len))) return NULL;
|
||||
double *kept = scr_dyn_bytes_named(method, "filter") ? malloc((len ? len : 1) * sizeof *kept) : NULL;
|
||||
size_t nkept = 0;
|
||||
for (size_t step = 0; step < len; step++) {
|
||||
@@ -5283,7 +5558,7 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar
|
||||
scr_dyn_release(index);
|
||||
if (!r) {
|
||||
scr_dyn_release(item);
|
||||
if (mapped) scr_bytes_release(mapped);
|
||||
scr_dyn_release(mapped);
|
||||
free(kept);
|
||||
return NULL;
|
||||
}
|
||||
@@ -5292,8 +5567,8 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar
|
||||
bool converted = scr_dyn_number_coerce_js(r, &v);
|
||||
scr_dyn_release(r);
|
||||
scr_dyn_release(item);
|
||||
if (!converted) { scr_bytes_release(mapped); return NULL; }
|
||||
if (k < mapped->len) scr_bytes_set(mapped, (double)k, v);
|
||||
if (!converted) { scr_dyn_release(mapped); return NULL; }
|
||||
if (k < mapped->v.bytes->len) scr_bytes_set(mapped->v.bytes, (double)k, v);
|
||||
continue;
|
||||
}
|
||||
bool truthy = scr_dyn_truthy(r);
|
||||
@@ -5314,12 +5589,12 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar
|
||||
}
|
||||
scr_dyn_release(item);
|
||||
}
|
||||
if (mapped) return dyn_bytes_wrap(recv, mapped);
|
||||
if (mapped) return mapped;
|
||||
if (kept) {
|
||||
ScrBytes *out = scr_bytes_new(bytes->elem, (double)nkept);
|
||||
for (size_t i = 0; i < nkept; i++) scr_bytes_set(out, (double)i, kept[i]);
|
||||
ScrDyn *out = scr_bytes_species_create(recv, nkept);
|
||||
for (size_t i = 0; out && i < nkept; i++) scr_bytes_set(out->v.bytes, (double)i, kept[i]);
|
||||
free(kept);
|
||||
return dyn_bytes_wrap(recv, out);
|
||||
return out;
|
||||
}
|
||||
if (scr_dyn_bytes_named(method, "some")) return scr_dyn_new_bool(false);
|
||||
if (scr_dyn_bytes_named(method, "every")) return scr_dyn_new_bool(true);
|
||||
@@ -5446,6 +5721,8 @@ static ScrDyn *scr_dyn_bytes_method_value(const ScrStr *name, size_t arity) {
|
||||
/* Shared view metadata and indexed reads for both compiler backends. */
|
||||
ScrDyn *scr_dyn_bytes_key_get(const ScrDyn *value, const ScrStr *key) {
|
||||
ScrBytes *bytes = value->v.bytes;
|
||||
/* Own named properties first; a getter sees the view as `this`. */
|
||||
if (scr_bytes_has_named(value, key)) return scr_bytes_named_get(value, key->data, key->len);
|
||||
if (key->len == 10 && memcmp(key->data, "byteLength", 10) == 0) return scr_dyn_new_num(scr_bytes_byte_len(bytes));
|
||||
if (key->len == 10 && memcmp(key->data, "byteOffset", 10) == 0) return scr_dyn_new_num(scr_bytes_byte_offset(bytes));
|
||||
if (key->len == 6 && memcmp(key->data, "buffer", 6) == 0) return scr_array_buffer_from_bytes(bytes);
|
||||
@@ -6447,6 +6724,45 @@ bool scr_dyn_same_value(const ScrDyn *left, const ScrDyn *right) {
|
||||
static void scr_dyn_descriptor_fields_drop(ScrDyn **fields) {
|
||||
for (size_t i = 0; i < 6; i++) scr_dyn_release(fields[i]);
|
||||
}
|
||||
|
||||
/* ToPropertyDescriptor: the present fields of `descriptor` (enumerable,
|
||||
* configurable, value, writable, get, set; +1 each, NULL when absent),
|
||||
* read in that order through any getters. False with a TypeError pending
|
||||
* for a primitive descriptor, a getter's exception, or an invalid
|
||||
* combination; every field is then released. */
|
||||
static bool scr_dyn_descriptor_fields_read(const ScrDyn *descriptor, ScrDyn **fields) {
|
||||
for (size_t i = 0; i < 6; i++) fields[i] = NULL;
|
||||
if (descriptor->kind != SCR_DYN_OBJ) {
|
||||
static const char msg[] = "Property description must be an object";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
return false;
|
||||
}
|
||||
static const char *const names[] = {"enumerable", "configurable", "value", "writable", "get", "set"};
|
||||
static const size_t lengths[] = {10, 12, 5, 8, 3, 3};
|
||||
for (size_t i = 0; i < 6; i++) {
|
||||
if (!scr_dyn_obj_get(descriptor, names[i], lengths[i])) continue;
|
||||
fields[i] = scr_dyn_obj_read(descriptor, names[i], lengths[i]);
|
||||
if (!fields[i]) {
|
||||
scr_dyn_descriptor_fields_drop(fields);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ScrDyn *get = fields[4], *set = fields[5];
|
||||
if ((get || set) && (fields[2] || fields[3])) {
|
||||
scr_dyn_descriptor_fields_drop(fields);
|
||||
static const char msg[] = "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
return false;
|
||||
}
|
||||
if ((get && get->kind != SCR_DYN_UNDEF && get->kind != SCR_DYN_FUNC) ||
|
||||
(set && set->kind != SCR_DYN_UNDEF && set->kind != SCR_DYN_FUNC)) {
|
||||
scr_dyn_descriptor_fields_drop(fields);
|
||||
static const char msg[] = "Getter and setter must be functions";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static void scr_error_sync_cause(ScrDyn *view, const ScrStr *key);
|
||||
static void scr_error_sync_slots(const ScrDyn *view, ScrDyn *obj, const ScrStr *key);
|
||||
|
||||
@@ -6770,6 +7086,10 @@ static ScrDyn *scr_dyn_symbol_receiver(const ScrDyn *value) {
|
||||
if (value->kind == SCR_DYN_FUNC) return scr_dyn_fn_properties(value);
|
||||
if (value->kind == SCR_DYN_OBJ || value->kind == SCR_DYN_ARR || value->kind == SCR_DYN_HANDLE)
|
||||
return scr_dyn_retain((ScrDyn *)value);
|
||||
/* A view keeps its symbol properties on its named-property table, which
|
||||
* writes create first (scr_bytes_symbol_write). */
|
||||
if (value->kind == SCR_DYN_BYTES)
|
||||
return value->v.bytes->properties ? scr_dyn_retain(value->v.bytes->properties) : NULL;
|
||||
if (value->kind == SCR_DYN_PROXY) {
|
||||
static const char message[] = "Symbol properties on this native value have no lowering";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
|
||||
@@ -8183,8 +8503,46 @@ void scr_dyn_symbol_key_placeholder(ScrDyn *value, ScrDyn *key) {
|
||||
entry->value = scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
|
||||
/* Symbol keys that the runtime reads from a view's prototype without
|
||||
* consulting its own properties: the well-known symbols and
|
||||
* util.inspect.custom. */
|
||||
static bool scr_bytes_protocol_symbol(const ScrDyn *key) {
|
||||
if (scr_dyn_well_known_symbol(key)) return true;
|
||||
ScrStr *description = key->v.symbol.description(key->v.symbol.value);
|
||||
bool custom = description && description->len == 26 && !memcmp(description->data, "nodejs.util.inspect.custom", 26);
|
||||
scr_str_release(description);
|
||||
return custom;
|
||||
}
|
||||
|
||||
/* Before a symbol-keyed write or definition on a view: false with an
|
||||
* exception pending for a key the view cannot own, else true with the
|
||||
* view's table created. Symbol.toStringTag is an accessor without a setter
|
||||
* on %TypedArray%.prototype, so assigning it throws V8's TypeError. */
|
||||
static bool scr_bytes_symbol_write(ScrDyn *value, const ScrDyn *key, bool assign) {
|
||||
ScrDyn *receiver = scr_dyn_symbol_receiver(value);
|
||||
bool own = receiver && scr_dyn_symbol_entry(receiver, key, true);
|
||||
scr_dyn_release(receiver);
|
||||
if (own) return true;
|
||||
ScrStr *description = key->v.symbol.description(key->v.symbol.value);
|
||||
bool tag = assign && !value->v.bytes->is_data_view && scr_dyn_well_known_symbol(key) && description &&
|
||||
description->len == 18 && !memcmp(description->data, "Symbol.toStringTag", 18);
|
||||
if (tag) {
|
||||
static const char message[] = "Cannot set property Symbol(Symbol.toStringTag) of [object Object] which has only a getter";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
} else if (scr_bytes_protocol_symbol(key)) {
|
||||
ScrStr *name = key->v.symbol.render(key->v.symbol.value);
|
||||
scr_bytes_protocol_fence(name->data, name->len);
|
||||
scr_str_release(name);
|
||||
}
|
||||
scr_str_release(description);
|
||||
if (scr_exc_pending()) return false;
|
||||
scr_bytes_properties(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
void scr_dyn_symbol_key_set(ScrDyn *value, ScrDyn *key, ScrDyn *stored) {
|
||||
if (scr_dyn_error_prototype_fence(value)) return;
|
||||
if (value->kind == SCR_DYN_BYTES && !scr_bytes_symbol_write(value, key, true)) return;
|
||||
ScrDyn *receiver = scr_dyn_symbol_receiver(value);
|
||||
if (!receiver) {
|
||||
if (!scr_exc_pending()) {
|
||||
@@ -8207,6 +8565,18 @@ void scr_dyn_symbol_key_set(ScrDyn *value, ScrDyn *key, ScrDyn *stored) {
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
} else {
|
||||
bool present = scr_dyn_symbol_entry(receiver, key, true) != NULL;
|
||||
if (!present && receiver->non_extensible) {
|
||||
ScrStr *rendered = key->v.symbol.render(key->v.symbol.value);
|
||||
ScrJsonBuf message;
|
||||
scr_jb_init(&message);
|
||||
scr_jb_puts(&message, "Cannot add property ");
|
||||
scr_jb_write(&message, rendered->data, rendered->len);
|
||||
scr_jb_puts(&message, ", object is not extensible");
|
||||
scr_str_release(rendered);
|
||||
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&message));
|
||||
scr_dyn_release(receiver);
|
||||
return;
|
||||
}
|
||||
scr_dyn_symbol_table(receiver);
|
||||
ScrStr *name = scr_dyn_symbol_storage_key(key);
|
||||
scr_dyn_key_set(receiver->symbol_properties, name, stored);
|
||||
@@ -8248,6 +8618,7 @@ void scr_dyn_symbol_field_set(ScrDyn *obj, ScrDyn *key, ScrDyn *value) {
|
||||
}
|
||||
|
||||
static ScrDyn *scr_dyn_define_symbol(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor) {
|
||||
if (target->kind == SCR_DYN_BYTES && !scr_bytes_symbol_write(target, key, false)) return NULL;
|
||||
ScrDyn *receiver = scr_dyn_symbol_receiver(target);
|
||||
if (!receiver) {
|
||||
if (!scr_exc_pending()) {
|
||||
@@ -8497,6 +8868,63 @@ ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor)
|
||||
scr_dyn_release(defined);
|
||||
return scr_dyn_retain(target);
|
||||
}
|
||||
if (target->kind == SCR_DYN_BYTES) {
|
||||
ScrStr *name = scr_dyn_property_key(key);
|
||||
if (!name) return NULL;
|
||||
double index;
|
||||
bool numeric = !target->v.bytes->is_data_view && scr_bytes_numeric_key(name, &index);
|
||||
if (numeric) {
|
||||
/* [[DefineOwnProperty]] of an element: a valid index and a data
|
||||
* descriptor that keeps it writable, enumerable and configurable. */
|
||||
/* The descriptor is read first (ToPropertyDescriptor), through its
|
||||
* getters, and must be an object. */
|
||||
ScrDyn *fields[6];
|
||||
if (!scr_dyn_descriptor_fields_read(descriptor, fields)) {
|
||||
scr_str_release(name);
|
||||
return NULL;
|
||||
}
|
||||
const ScrDyn *enumerable = fields[0], *configurable = fields[1], *value = fields[2];
|
||||
const ScrDyn *writable = fields[3];
|
||||
bool accessor = fields[4] || fields[5];
|
||||
bool valid = !signbit(index) && trunc(index) == index && index < (double)target->v.bytes->len;
|
||||
if (!valid) {
|
||||
scr_dyn_descriptor_fields_drop(fields);
|
||||
scr_str_release(name);
|
||||
static const char message[] = "Invalid typed array index";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
if (accessor || (configurable && !scr_dyn_truthy(configurable)) ||
|
||||
(enumerable && !scr_dyn_truthy(enumerable)) || (writable && !scr_dyn_truthy(writable))) {
|
||||
scr_dyn_descriptor_fields_drop(fields);
|
||||
ScrJsonBuf message;
|
||||
scr_jb_init(&message);
|
||||
scr_jb_puts(&message, "Cannot redefine property: ");
|
||||
scr_jb_write(&message, name->data, name->len);
|
||||
scr_str_release(name);
|
||||
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&message));
|
||||
return NULL;
|
||||
}
|
||||
scr_str_release(name);
|
||||
double number;
|
||||
bool stored = true;
|
||||
if (value) {
|
||||
stored = scr_dyn_number_coerce_js(value, &number);
|
||||
/* The conversion can run user code; the index is checked again. */
|
||||
if (stored && index < (double)target->v.bytes->len) scr_bytes_set(target->v.bytes, index, number);
|
||||
}
|
||||
scr_dyn_descriptor_fields_drop(fields);
|
||||
return stored ? scr_dyn_retain(target) : NULL;
|
||||
}
|
||||
bool fenced = scr_bytes_protocol_name(name) && !scr_bytes_has_named(target, name);
|
||||
if (fenced) scr_bytes_protocol_fence(name->data, name->len);
|
||||
scr_str_release(name);
|
||||
if (fenced) return NULL;
|
||||
ScrDyn *defined = scr_dyn_define_property(scr_bytes_properties(target), key, descriptor);
|
||||
if (!defined) return NULL;
|
||||
scr_dyn_release(defined);
|
||||
return scr_dyn_retain(target);
|
||||
}
|
||||
if (target->kind != SCR_DYN_OBJ) {
|
||||
static const char msg[] = "Object.defineProperty called on non-object";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
@@ -8504,44 +8932,16 @@ ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor)
|
||||
}
|
||||
ScrStr *name = scr_dyn_property_key(key);
|
||||
if (!name) return NULL;
|
||||
if (descriptor->kind != SCR_DYN_OBJ) {
|
||||
ScrDyn *fields[6];
|
||||
if (!scr_dyn_descriptor_fields_read(descriptor, fields)) {
|
||||
scr_str_release(name);
|
||||
static const char msg[] = "Property description must be an object";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
return NULL;
|
||||
}
|
||||
static const char *const names[] = {"enumerable", "configurable", "value", "writable", "get", "set"};
|
||||
static const size_t lengths[] = {10, 12, 5, 8, 3, 3};
|
||||
ScrDyn *fields[6] = {NULL};
|
||||
for (size_t i = 0; i < 6; i++) {
|
||||
if (!scr_dyn_obj_get(descriptor, names[i], lengths[i])) continue;
|
||||
fields[i] = scr_dyn_obj_read(descriptor, names[i], lengths[i]);
|
||||
if (!fields[i]) {
|
||||
scr_dyn_descriptor_fields_drop(fields);
|
||||
scr_str_release(name);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
ScrDyn *e = fields[0], *c = fields[1], *value = fields[2];
|
||||
ScrDyn *w = fields[3], *get = fields[4], *set = fields[5];
|
||||
ScrDynEntry *prior = scr_dyn_entry(target, name);
|
||||
bool accessor_descriptor = get || set;
|
||||
bool data_descriptor = value || w;
|
||||
if (accessor_descriptor && data_descriptor) {
|
||||
scr_dyn_descriptor_fields_drop(fields);
|
||||
scr_str_release(name);
|
||||
static const char msg[] = "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
return NULL;
|
||||
}
|
||||
if ((get && get->kind != SCR_DYN_UNDEF && get->kind != SCR_DYN_FUNC) ||
|
||||
(set && set->kind != SCR_DYN_UNDEF && set->kind != SCR_DYN_FUNC)) {
|
||||
scr_dyn_descriptor_fields_drop(fields);
|
||||
scr_str_release(name);
|
||||
static const char msg[] = "Getter and setter must be functions";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
return NULL;
|
||||
}
|
||||
bool accessor = accessor_descriptor || (!data_descriptor && prior && prior->accessor);
|
||||
bool writable = w ? scr_dyn_truthy(w) : prior && !prior->accessor && !accessor ? prior->writable : false;
|
||||
bool enumerable = e ? scr_dyn_truthy(e) : prior ? prior->enumerable : false;
|
||||
@@ -8651,7 +9051,7 @@ ScrDyn *scr_dyn_get_own_property_descriptor(ScrDyn *target, ScrDyn *key) {
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
return NULL;
|
||||
}
|
||||
if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_PROXY && target->kind != SCR_DYN_FUNC && target->kind != SCR_DYN_ARR && target->kind != SCR_DYN_STR && target->kind != SCR_DYN_BOOL && target->kind != SCR_DYN_NUM && target->kind != SCR_DYN_TYPED_REF) {
|
||||
if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_PROXY && target->kind != SCR_DYN_FUNC && target->kind != SCR_DYN_ARR && target->kind != SCR_DYN_STR && target->kind != SCR_DYN_BOOL && target->kind != SCR_DYN_NUM && target->kind != SCR_DYN_TYPED_REF && target->kind != SCR_DYN_BYTES) {
|
||||
static const char msg[] = "Object.getOwnPropertyDescriptor on this value is not supported yet";
|
||||
scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
|
||||
return NULL;
|
||||
@@ -9010,7 +9410,8 @@ static bool scr_dyn_json_write(ScrJsonBuf *b, const ScrDyn *d) {
|
||||
const ScrBytes *bytes = d->v.bytes;
|
||||
if (d->buffer) scr_jb_puts(b, "{\"type\":\"Buffer\",\"data\":[");
|
||||
else scr_jb_putc(b, '{');
|
||||
for (size_t i = 0; i < bytes->len; i++) {
|
||||
size_t count = bytes->is_data_view ? 0 : bytes->len;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (i > 0) scr_jb_putc(b, ',');
|
||||
if (!d->buffer) {
|
||||
char idx[32];
|
||||
@@ -9019,6 +9420,32 @@ static bool scr_dyn_json_write(ScrJsonBuf *b, const ScrDyn *d) {
|
||||
}
|
||||
scr_jb_put_f64(b, scr_bytes_get(bytes, (double)i));
|
||||
}
|
||||
/* Named properties follow the elements; a Buffer's toJSON leaves them
|
||||
* out. */
|
||||
ScrDyn *named = d->buffer || !bytes->properties ? NULL : scr_dyn_obj_keys(bytes->properties);
|
||||
bool first = count == 0;
|
||||
for (size_t i = 0; named && i < named->v.arr.len; i++) {
|
||||
const ScrStr *key = named->v.arr.items[i]->v.str;
|
||||
ScrDyn *value = scr_dyn_obj_read_receiver(bytes->properties, key->data, key->len, d);
|
||||
if (!value) { scr_dyn_release(named); return false; }
|
||||
ScrJsonBuf probe;
|
||||
scr_jb_init(&probe);
|
||||
bool present = scr_dyn_json_write(&probe, value);
|
||||
scr_dyn_release(value);
|
||||
if (!present) {
|
||||
scr_jb_dispose(&probe);
|
||||
if (scr_exc_pending()) { scr_dyn_release(named); return false; }
|
||||
continue;
|
||||
}
|
||||
if (!first) scr_jb_putc(b, ',');
|
||||
first = false;
|
||||
scr_jb_put_json_str(b, key);
|
||||
scr_jb_putc(b, ':');
|
||||
ScrStr *body = scr_jb_finish(&probe);
|
||||
scr_jb_write(b, body->data, body->len);
|
||||
scr_str_release(body);
|
||||
}
|
||||
scr_dyn_release(named);
|
||||
scr_jb_puts(b, d->buffer ? "]}" : "}");
|
||||
return true;
|
||||
}
|
||||
@@ -10792,6 +11219,7 @@ bool scr_dyn_has_key(const ScrDyn *v, const ScrStr *key) {
|
||||
}
|
||||
if (v->kind == SCR_DYN_STR)
|
||||
return scr_dyn_canonical_own_index(key, (size_t)scr_str_utf16_len(v->v.str));
|
||||
if (v->kind == SCR_DYN_BYTES) return scr_dyn_bytes_in(v, key);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -10854,7 +11282,13 @@ static void scr_dyn_throw_property(const ScrDyn *object, const char *key, size_t
|
||||
scr_jb_puts(&buffer, deleting ? "Cannot delete property '" : "Cannot assign to read only property '");
|
||||
scr_jb_write(&buffer, key, key_len);
|
||||
scr_jb_puts(&buffer, deleting ? "' of " : "' of object '");
|
||||
if (object->null_proto) scr_jb_puts(&buffer, "[object Object]");
|
||||
if (object->kind == SCR_DYN_BYTES) {
|
||||
/* V8 names a typed array by its tag and a DataView by its class. */
|
||||
const char *kind = object->v.bytes->is_data_view ? "DataView" : scr_bytes_elem_name(object->v.bytes->elem);
|
||||
scr_jb_puts(&buffer, object->v.bytes->is_data_view ? "#<" : "[object ");
|
||||
scr_jb_puts(&buffer, kind);
|
||||
scr_jb_puts(&buffer, object->v.bytes->is_data_view ? ">" : "]");
|
||||
} else if (object->null_proto) scr_jb_puts(&buffer, "[object Object]");
|
||||
else {
|
||||
scr_jb_puts(&buffer, "#<");
|
||||
scr_jb_puts(&buffer, name ? name : "Object");
|
||||
@@ -11072,19 +11506,36 @@ void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) {
|
||||
}
|
||||
if (recv->kind == SCR_DYN_BYTES) {
|
||||
/* Integer-indexed exotic writes coerce even for an invalid canonical
|
||||
* numeric index, then ignore an out-of-range index without an error. */
|
||||
double index = scr_string_to_number(key);
|
||||
char spelling[32];
|
||||
size_t length = scr_f64_to_str(index, spelling);
|
||||
bool negative_zero = key->len == 2 && memcmp(key->data, "-0", 2) == 0;
|
||||
if (negative_zero || (length == key->len && memcmp(spelling, key->data, length) == 0)) {
|
||||
* numeric index, then ignore an out-of-range index without an error.
|
||||
* A DataView has no elements: its numeric keys are named properties. */
|
||||
double index;
|
||||
if (!recv->v.bytes->is_data_view && scr_bytes_numeric_key(key, &index)) {
|
||||
double number;
|
||||
if (!scr_dyn_number_coerce_js(value, &number)) return;
|
||||
if (!negative_zero && index >= 0 && index < (double)recv->v.bytes->len && trunc(index) == index) {
|
||||
if (!signbit(index) && index < (double)recv->v.bytes->len && trunc(index) == index) {
|
||||
scr_bytes_set(recv->v.bytes, index, number);
|
||||
}
|
||||
return;
|
||||
}
|
||||
/* Any other key is a named property of the view. */
|
||||
if (!scr_bytes_has_named(recv, key)) {
|
||||
if (scr_bytes_getter_name(recv, key)) {
|
||||
ScrJsonBuf message;
|
||||
scr_jb_init(&message);
|
||||
scr_jb_puts(&message, "Cannot set property ");
|
||||
scr_jb_write(&message, key->data, key->len);
|
||||
scr_jb_puts(&message, recv->v.bytes->is_data_view ? " of #<DataView> which has only a getter"
|
||||
: " of [object Object] which has only a getter");
|
||||
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&message));
|
||||
return;
|
||||
}
|
||||
if (scr_bytes_protocol_name(key)) {
|
||||
scr_bytes_protocol_fence(key->data, key->len);
|
||||
return;
|
||||
}
|
||||
}
|
||||
scr_dyn_object_key_set(scr_bytes_properties(recv), key, value, recv);
|
||||
return;
|
||||
}
|
||||
|
||||
ScrJsonBuf b;
|
||||
@@ -13016,6 +13467,11 @@ static ScrDyn *scr_json_member(const ScrDyn *holder, const ScrStr *key) {
|
||||
scr_dyn_release(view);
|
||||
return member;
|
||||
}
|
||||
/* A view's named properties, getters included, read through the view. */
|
||||
if (holder->kind == SCR_DYN_BYTES && (holder->v.bytes->is_data_view || scr_bytes_has_named(holder, key))) {
|
||||
ScrDyn *named = scr_bytes_named_get(holder, key->data, key->len);
|
||||
return named || scr_exc_pending() ? named : scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
if (holder->kind == SCR_DYN_ARR || holder->kind == SCR_DYN_BYTES) {
|
||||
size_t index = 0;
|
||||
for (size_t i = 0; i < key->len; i++) index = index * 10 + (size_t)(key->data[i] - '0');
|
||||
@@ -13167,6 +13623,23 @@ void scr_dyn_key_delete(ScrDyn *recv, const ScrStr *key, bool strict) {
|
||||
scr_dyn_key_delete(recv->v.arr.properties, key, strict);
|
||||
return;
|
||||
}
|
||||
if (recv->kind == SCR_DYN_BYTES) {
|
||||
/* A typed array's elements cannot be deleted; other numeric keys name
|
||||
* no property. Named properties follow their configurability. */
|
||||
double index;
|
||||
if (!recv->v.bytes->is_data_view && scr_bytes_numeric_key(key, &index)) {
|
||||
if (strict && !signbit(index) && trunc(index) == index && index < (double)recv->v.bytes->len)
|
||||
scr_dyn_throw_property(recv, key->data, key->len, true);
|
||||
return;
|
||||
}
|
||||
ScrDynEntry *entry = recv->v.bytes->properties ? scr_dyn_entry(recv->v.bytes->properties, key) : NULL;
|
||||
if (entry && !entry->configurable) {
|
||||
if (strict) scr_dyn_throw_property(recv, key->data, key->len, true);
|
||||
return;
|
||||
}
|
||||
if (entry) scr_json_delete_member(recv->v.bytes->properties, key);
|
||||
return;
|
||||
}
|
||||
if (recv->kind == SCR_DYN_UNDEF || recv->kind == SCR_DYN_NULL) {
|
||||
const char *message = "Cannot convert undefined or null to object";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, strlen(message));
|
||||
@@ -14848,7 +15321,7 @@ ScrDyn *scr_dyn_get_own_property_names(const ScrDyn *value) {
|
||||
if (value->kind == SCR_DYN_ARR || value->kind == SCR_DYN_STR || value->kind == SCR_DYN_BYTES) {
|
||||
ScrDyn *keys = scr_dyn_new_arr();
|
||||
size_t length = value->kind == SCR_DYN_ARR ? value->v.arr.len :
|
||||
value->kind == SCR_DYN_BYTES ? value->v.bytes->len :
|
||||
value->kind == SCR_DYN_BYTES ? (value->v.bytes->is_data_view ? 0 : value->v.bytes->len) :
|
||||
(size_t)scr_str_utf16_len(value->v.str);
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
if (value->kind == SCR_DYN_ARR && !scr_dyn_arr_has_index(value, i)) continue;
|
||||
@@ -14860,8 +15333,10 @@ ScrDyn *scr_dyn_get_own_property_names(const ScrDyn *value) {
|
||||
scr_dyn_arr_push(keys, scr_dyn_objwalk_key("length", 6));
|
||||
bool array_prototype = value == scr_builtin_array_prototype;
|
||||
if (array_prototype) scr_dyn_arr_push(keys, scr_dyn_objwalk_key("constructor", 11));
|
||||
if (value->kind == SCR_DYN_ARR && value->v.arr.properties) {
|
||||
ScrDyn *properties = scr_dyn_obj_own_keys(value->v.arr.properties);
|
||||
ScrDyn *named = value->kind == SCR_DYN_ARR ? value->v.arr.properties
|
||||
: value->kind == SCR_DYN_BYTES ? value->v.bytes->properties : NULL;
|
||||
if (named) {
|
||||
ScrDyn *properties = scr_dyn_obj_own_keys(named);
|
||||
for (size_t i = 0; i < properties->v.arr.len; i++) {
|
||||
ScrDyn *name = properties->v.arr.items[i];
|
||||
/* A constructor stored on Array.prototype keeps the position above. */
|
||||
@@ -15132,7 +15607,7 @@ static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) {
|
||||
return out;
|
||||
}
|
||||
if (v->kind == SCR_DYN_ARR || v->kind == SCR_DYN_BYTES) {
|
||||
size_t n = v->kind == SCR_DYN_ARR ? v->v.arr.len : v->v.bytes->len;
|
||||
size_t n = v->kind == SCR_DYN_ARR ? v->v.arr.len : v->v.bytes->is_data_view ? 0 : v->v.bytes->len;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if (v->kind == SCR_DYN_ARR && !scr_dyn_arr_has_index(v, i)) continue;
|
||||
char key[24];
|
||||
@@ -15153,10 +15628,11 @@ static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) {
|
||||
scr_dyn_arr_push(out, pair);
|
||||
}
|
||||
}
|
||||
if (v->kind == SCR_DYN_ARR && v->v.arr.properties) {
|
||||
ScrDyn *names = scr_dyn_obj_own_keys(v->v.arr.properties);
|
||||
ScrDyn *named = v->kind == SCR_DYN_ARR ? v->v.arr.properties : v->v.bytes->properties;
|
||||
if (named) {
|
||||
ScrDyn *names = scr_dyn_obj_own_keys(named);
|
||||
for (size_t i = 0; i < names->v.arr.len; i++) {
|
||||
if (!scr_dyn_objwalk_entry(out, v->v.arr.properties, mode, names->v.arr.items[i]->v.str, v)) {
|
||||
if (!scr_dyn_objwalk_entry(out, named, mode, names->v.arr.items[i]->v.str, v)) {
|
||||
scr_dyn_release(names);
|
||||
scr_dyn_release(out);
|
||||
return NULL;
|
||||
@@ -15260,6 +15736,7 @@ ScrDyn *scr_dyn_for_in_keys(const ScrDyn *v) {
|
||||
return keys;
|
||||
}
|
||||
case SCR_DYN_ARR:
|
||||
case SCR_DYN_BYTES:
|
||||
return scr_dyn_obj_keys(v);
|
||||
case SCR_DYN_STR: {
|
||||
ScrDyn *keys = scr_dyn_new_arr();
|
||||
@@ -15513,7 +15990,7 @@ static void scr_dyn_assign_strings_from(ScrDyn *target, const ScrDyn *src, bool
|
||||
scr_dyn_proxy_unsupported("Object.assign");
|
||||
return;
|
||||
}
|
||||
if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_FUNC) return;
|
||||
if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_FUNC && target->kind != SCR_DYN_BYTES) return;
|
||||
if (src->kind == SCR_DYN_TYPED_REF) {
|
||||
ScrDyn *materialized = scr_dyn_typed_ref_materialize(src);
|
||||
if (materialized) scr_dyn_assign_strings_from(target, materialized, define);
|
||||
@@ -15576,13 +16053,15 @@ static void scr_dyn_assign_from(ScrDyn *target, const ScrDyn *src, bool define)
|
||||
return;
|
||||
}
|
||||
ScrDyn *view = src->kind == SCR_DYN_FUNC ? scr_dyn_fn_properties(src) : scr_dyn_retain((ScrDyn *)src);
|
||||
/* A view's symbol properties live on its named-property table. */
|
||||
const ScrDyn *holder = src->kind == SCR_DYN_BYTES ? src->v.bytes->properties : view;
|
||||
ScrDyn *symbols = scr_dyn_new_arr();
|
||||
if (view->symbol_keys) for (size_t i = 0; i < view->symbol_keys->v.arr.len; i++)
|
||||
scr_dyn_arr_push(symbols, scr_dyn_retain(view->symbol_keys->v.arr.items[i]));
|
||||
if (holder && holder->symbol_keys) for (size_t i = 0; i < holder->symbol_keys->v.arr.len; i++)
|
||||
scr_dyn_arr_push(symbols, scr_dyn_retain(holder->symbol_keys->v.arr.items[i]));
|
||||
scr_dyn_assign_strings_from(target, view, define);
|
||||
for (size_t i = 0; i < symbols->v.arr.len && !scr_exc_pending(); i++) {
|
||||
ScrDyn *key = symbols->v.arr.items[i];
|
||||
ScrDynEntry *entry = scr_dyn_symbol_entry(view, key, true);
|
||||
ScrDynEntry *entry = scr_dyn_symbol_entry(holder, key, true);
|
||||
if (!entry || !entry->enumerable) continue;
|
||||
ScrDyn *value = scr_dyn_symbol_key_get(src, key, false);
|
||||
if (!value) break;
|
||||
@@ -15973,7 +16452,7 @@ bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key) {
|
||||
return scr_dyn_canonical_own_index(key, (size_t)scr_str_utf16_len(v->v.str));
|
||||
}
|
||||
if (v->kind == SCR_DYN_BYTES) {
|
||||
return scr_dyn_canonical_own_index(key, v->v.bytes->len);
|
||||
return (!v->v.bytes->is_data_view && scr_dyn_canonical_own_index(key, v->v.bytes->len)) || scr_bytes_has_named(v, key);
|
||||
}
|
||||
if (v->kind == SCR_DYN_FUNC) {
|
||||
ScrDyn *desc = scr_dyn_own_descriptor(v, key);
|
||||
@@ -16010,7 +16489,8 @@ bool scr_dyn_property_is_enumerable(const ScrDyn *value, const ScrStr *key) {
|
||||
return (scr_dyn_canonical_own_index(key, value->v.arr.len) && scr_dyn_has_own(value, key)) ||
|
||||
(value->v.arr.properties && scr_dyn_obj_enumerable(value->v.arr.properties, key->data, key->len));
|
||||
if (value->kind == SCR_DYN_BYTES)
|
||||
return scr_dyn_canonical_own_index(key, value->v.bytes->len);
|
||||
return (!value->v.bytes->is_data_view && scr_dyn_canonical_own_index(key, value->v.bytes->len)) ||
|
||||
(value->v.bytes->properties && scr_dyn_obj_enumerable(value->v.bytes->properties, key->data, key->len));
|
||||
return false;
|
||||
}
|
||||
ScrDyn *scr_dyn_obj_values(const ScrDyn *v) { return scr_dyn_objwalk(v, SCR_OBJWALK_VALUES); }
|
||||
@@ -17858,6 +18338,11 @@ ScrDyn *scr_dyn_freeze_data(ScrDyn *value) {
|
||||
ScrDyn *native = scr_dyn_freeze_native(value);
|
||||
if (native) return native;
|
||||
if (scr_dyn_class_integrity_fence(value, "Object.freeze")) return NULL;
|
||||
if (value->kind == SCR_DYN_BYTES) {
|
||||
scr_dyn_release(scr_dyn_freeze_data(scr_bytes_properties(value)));
|
||||
if (scr_bytes_integrity_fence(value, "freeze")) return NULL;
|
||||
return scr_dyn_retain(value);
|
||||
}
|
||||
if (value->symbol_properties) scr_dyn_release(scr_dyn_freeze_data(value->symbol_properties));
|
||||
if (value->kind == SCR_DYN_ARR) {
|
||||
value->non_extensible = true;
|
||||
@@ -17899,6 +18384,9 @@ bool scr_dyn_is_frozen(const ScrDyn *value) {
|
||||
if (value->kind == SCR_DYN_ARR)
|
||||
return scr_dyn_is_sealed(value) && (value->v.arr.len == 0 || value->v.arr.frozen) &&
|
||||
(!value->v.arr.properties || scr_dyn_is_frozen(value->v.arr.properties));
|
||||
if (value->kind == SCR_DYN_BYTES)
|
||||
return (value->v.bytes->is_data_view || value->v.bytes->len == 0) && value->v.bytes->properties &&
|
||||
scr_dyn_is_frozen(value->v.bytes->properties);
|
||||
if (value->kind == SCR_DYN_OBJ && !value->copied_from_native) {
|
||||
if (!value->non_extensible) return false;
|
||||
for (size_t i = 0; i < value->v.obj.len; i++) {
|
||||
|
||||
@@ -85,6 +85,11 @@ ScrSym *scr_sym_well_known(ScrStr *name) {
|
||||
scr_str_release(description);
|
||||
symbol->reg_next = g_sym_well_known;
|
||||
g_sym_well_known = scr_sym_retain(symbol);
|
||||
{
|
||||
ScrDyn *key = scr_dyn_new_symbol(symbol);
|
||||
scr_dyn_install_well_known_symbol(key);
|
||||
scr_dyn_release(key);
|
||||
}
|
||||
if (name->len == 8 && !memcmp(name->data, "iterator", 8)) {
|
||||
ScrDyn *key = scr_dyn_new_symbol(symbol);
|
||||
scr_dyn_install_iterator_symbol(key);
|
||||
@@ -95,6 +100,11 @@ ScrSym *scr_sym_well_known(ScrStr *name) {
|
||||
scr_dyn_install_has_instance_symbol(key);
|
||||
scr_dyn_release(key);
|
||||
}
|
||||
if (name->len == 7 && !memcmp(name->data, "species", 7)) {
|
||||
ScrDyn *key = scr_dyn_new_symbol(symbol);
|
||||
scr_dyn_install_species_symbol(key);
|
||||
scr_dyn_release(key);
|
||||
}
|
||||
if (name->len == 11 && !memcmp(name->data, "toStringTag", 11)) {
|
||||
ScrDyn *key = scr_dyn_new_symbol(symbol);
|
||||
scr_dyn_install_to_string_tag_symbol(key);
|
||||
|
||||
@@ -123,3 +123,15 @@ function plainIterator(TA) {
|
||||
console.log(new TA(source), TA.from(source, (v) => v * 2), gets, nexts);
|
||||
}
|
||||
plainIterator(Int16Array);
|
||||
// ToIndex of a Symbol or BigInt length, toLocaleString, and Symbol descriptions
|
||||
// that are not strings.
|
||||
function small(TA) {
|
||||
for (const bad of [Symbol("s"), 1n]) {
|
||||
try { new TA(bad); console.log("no throw"); } catch (e) { console.log(e.constructor.name); }
|
||||
}
|
||||
console.log(new TA([1, 1234.5, -0, 0]).toLocaleString(), new TA(0).toLocaleString(), typeof new TA(1).toLocaleString);
|
||||
}
|
||||
small(Float64Array);
|
||||
small(Uint16Array);
|
||||
const s = Symbol(1), t = Symbol(true), u = Symbol(2.5);
|
||||
console.log(s.description, t.description, u.toString(), typeof s.description);
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
// Named properties on typed arrays, Buffers and DataViews (assignment,
|
||||
// Object.defineProperty, reflection, deletion, integrity levels, JSON and
|
||||
// inspection), and the Symbol.species constructor of map, filter, slice and
|
||||
// subarray.
|
||||
function box(value) {
|
||||
const holder = {};
|
||||
holder.value = value;
|
||||
return holder.value;
|
||||
}
|
||||
|
||||
function expandos(TA) {
|
||||
const a = new TA([1, 2]);
|
||||
a.foo = 1;
|
||||
a.bar = "x";
|
||||
a[5] = 9;
|
||||
a["1.5"] = 7;
|
||||
console.log(a, a.foo, "foo" in a, a.hasOwnProperty("bar"), Object.keys(a), Object.entries(a).length);
|
||||
console.log(Object.getOwnPropertyNames(a), JSON.stringify(Object.getOwnPropertyDescriptor(a, "foo")), JSON.stringify(Object.getOwnPropertyDescriptor(a, "0")));
|
||||
Object.defineProperty(a, "hidden", { value: 3, enumerable: false });
|
||||
Object.defineProperty(a, "0", { value: 8 });
|
||||
console.log(a, a.hidden, Object.keys(a));
|
||||
try { Object.defineProperty(a, "1", { value: 1, configurable: false }); } catch (e) { console.log(e.constructor.name, e.message); }
|
||||
try { Object.defineProperty(a, "7", { value: 1 }); } catch (e) { console.log(e.constructor.name, e.message); }
|
||||
try { Object.defineProperty(a, "-0", { value: 1 }); } catch (e) { console.log(e.constructor.name, e.message); }
|
||||
try { Object.defineProperty(a, "0", { get() { return 1; } }); } catch (e) { console.log(e.constructor.name, e.message); }
|
||||
// An element's descriptor is read through its getters, and must be an object.
|
||||
const got = [];
|
||||
Object.defineProperty(a, "1", { get value() { got.push("value"); return 6; }, get enumerable() { got.push("enumerable"); return true; } });
|
||||
console.log(a[1], got);
|
||||
// Each field is read once, through its getter; the stored value is the
|
||||
// getter's result, and a getter-produced `writable: false` is refused.
|
||||
let valueReads = 0;
|
||||
Object.defineProperty(a, "1", { get value() { valueReads++; return 40 + valueReads; } });
|
||||
console.log(a[1], valueReads);
|
||||
const order = [];
|
||||
try {
|
||||
Object.defineProperty(a, "1", {
|
||||
get writable() { order.push("writable"); return false; },
|
||||
get value() { order.push("value"); return 2; },
|
||||
get configurable() { order.push("configurable"); return true; },
|
||||
});
|
||||
} catch (e) { console.log(e.constructor.name, e.message); }
|
||||
console.log(a[1], order);
|
||||
for (const bad of [null, true, 1n, Symbol("d")]) {
|
||||
try { Object.defineProperty(a, "1", bad); } catch (e) { console.log(e.constructor.name); }
|
||||
}
|
||||
for (const bad of [5, "x", undefined]) {
|
||||
try { Object.defineProperty(a, "0", bad); } catch (e) { console.log(e.constructor.name); }
|
||||
}
|
||||
try { Object.defineProperty(a, "9", 5); } catch (e) { console.log(e.constructor.name); }
|
||||
try { Object.defineProperty(a, "0", { get configurable() { throw new RangeError("read"); } }); } catch (e) { console.log(e.constructor.name, e.message); }
|
||||
try { Object.defineProperty(a, "0", { value: 1, get: undefined }); } catch (e) { console.log(e.constructor.name); }
|
||||
console.log(a[0], a[1]);
|
||||
let reads = 0;
|
||||
Object.defineProperty(a, "g", { get() { reads++; return this.length; }, enumerable: true });
|
||||
console.log(a.g, reads, new TA(0));
|
||||
const e = new TA(0);
|
||||
e.k = { deep: [1] };
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
function species(TA, Other) {
|
||||
const s = new TA([1, 2, 3, 4]);
|
||||
const calls = [];
|
||||
s.constructor = {};
|
||||
s.constructor[Symbol.species] = function (n) { calls.push(n); return new Other(n); };
|
||||
const m = s.map((v) => v * 1.5), f = s.filter((v) => v > 1), sl = s.slice(1, 3);
|
||||
console.log(m, f, sl, calls);
|
||||
s.constructor[Symbol.species] = function (buffer, offset, length) { calls.push([offset, length]); return new TA(buffer, offset, length); };
|
||||
const sub = s.subarray(1, 3);
|
||||
sub[0] = 50;
|
||||
console.log(sub, s[1], calls.length);
|
||||
s.constructor[Symbol.species] = function () { return {}; };
|
||||
try { s.map((v) => v); } catch (err) { console.log(err.constructor.name); }
|
||||
s.constructor[Symbol.species] = function () { return new TA(1); };
|
||||
try { s.slice(); } catch (err) { console.log(err.constructor.name); }
|
||||
s.constructor = 5;
|
||||
try { s.filter(() => true); } catch (err) { console.log(err.constructor.name); }
|
||||
s.constructor = undefined;
|
||||
console.log(s.map((v) => v + 1));
|
||||
const t = new TA([3, 1]);
|
||||
Object.defineProperty(t, "constructor", { get() { calls.push("get"); return undefined; } });
|
||||
console.log(t.slice(), calls[calls.length - 1]);
|
||||
const u = new TA([1, 2, 3]);
|
||||
u.constructor = { [Symbol.species]: Uint16Array };
|
||||
console.log(u.filter(() => true), u.subarray(1), u.subarray(1).constructor === Uint16Array);
|
||||
// A built-in constructor set as `constructor` is its own species.
|
||||
const w = new TA([1, 2, 3]);
|
||||
w.constructor = Float64Array;
|
||||
const wm = w.map((v) => v / 2), ws = w.slice(1);
|
||||
console.log(wm, wm.constructor === Float64Array, ws, ws.constructor === Float64Array, w.filter((v) => v > 1));
|
||||
w.constructor = TA;
|
||||
console.log(w.map((v) => v / 2).constructor === TA);
|
||||
// slice copies the source after the species constructor ran.
|
||||
const x = new TA([1, 2, 3, 4]);
|
||||
x.constructor = {};
|
||||
x.constructor[Symbol.species] = function (n) { x[1] = 40; x[2] = 30; return new Other(n); };
|
||||
console.log(x.slice(1, 3), x.slice(-1), x.slice(2, 1));
|
||||
}
|
||||
|
||||
function reflection(TA) {
|
||||
const a = new TA([1, 2]);
|
||||
a.foo = 1;
|
||||
a[Symbol.for("s")] = 2;
|
||||
const keys = [];
|
||||
for (const k in a) keys.push(k);
|
||||
console.log(keys, { ...a }, JSON.stringify(a), Object.values(a));
|
||||
console.log(a.propertyIsEnumerable("foo"), Object.hasOwn(a, "foo"), Object.getOwnPropertySymbols(a), a[Symbol.for("s")]);
|
||||
console.log(Object.assign(new TA(2), { x: 1, 0: 9 }), Object.assign({}, a));
|
||||
delete a.foo;
|
||||
delete a[5];
|
||||
delete a.length;
|
||||
console.log(a, "foo" in a, a.length);
|
||||
try { delete a[0]; } catch (err) { console.log(err.message); }
|
||||
Object.defineProperty(a, "fixed", { value: 1 });
|
||||
try { delete a.fixed; } catch (err) { console.log(err.message); }
|
||||
try { a.fixed = 2; } catch (err) { console.log(err.message); }
|
||||
console.log(a.subarray(0, 1).fixed, a.slice().fixed);
|
||||
let seen;
|
||||
Object.defineProperty(a, "s", { set(v) { seen = [v, this === a]; } });
|
||||
a.s = 7;
|
||||
console.log(seen);
|
||||
a.map = 5;
|
||||
try { a.map((v) => v); } catch (err) { console.log(err.message); }
|
||||
for (const key of ["length", "byteLength", "byteOffset", "buffer"]) {
|
||||
try { a[key] = 1; } catch (err) { console.log(err.message); }
|
||||
}
|
||||
try { a[Symbol.toStringTag] = "x"; } catch (err) { console.log(err.message); }
|
||||
// Cycles through a view's own properties are collected.
|
||||
const loop = new TA(1);
|
||||
loop.self = loop;
|
||||
loop.size = () => loop.length;
|
||||
console.log(loop.self === loop, loop.size());
|
||||
}
|
||||
|
||||
function integrity(TA) {
|
||||
const results = [];
|
||||
const attempt = (f) => { try { f(); results.push("ok"); } catch (err) { results.push(err.message); } };
|
||||
const sealed = new TA(1);
|
||||
sealed.x = 1;
|
||||
attempt(() => Object.seal(sealed));
|
||||
console.log(results, Object.isExtensible(sealed), Object.isSealed(sealed), JSON.stringify(Object.getOwnPropertyDescriptor(sealed, "x")));
|
||||
attempt(() => Object.seal(sealed));
|
||||
const frozen = new TA(1);
|
||||
frozen.x = 1;
|
||||
attempt(() => Object.freeze(frozen));
|
||||
attempt(() => Object.freeze(frozen));
|
||||
console.log(results, Object.isFrozen(frozen), JSON.stringify(Object.getOwnPropertyDescriptor(frozen, "x")));
|
||||
const closed = new TA(1);
|
||||
Object.defineProperty(closed, "q", { value: 1 });
|
||||
Object.preventExtensions(closed);
|
||||
try { closed.z = 1; } catch (err) { console.log(err.message); }
|
||||
try { closed[Symbol("n")] = 1; } catch (err) { console.log(err.message); }
|
||||
console.log(Object.isExtensible(closed), Object.isSealed(closed), Object.isFrozen(closed));
|
||||
const empty = new TA(0);
|
||||
empty.k = 1;
|
||||
Object.freeze(empty);
|
||||
try { empty.k = 2; } catch (err) { console.log(err.message); }
|
||||
console.log(Object.isFrozen(empty), Object.isSealed(empty), empty.k);
|
||||
}
|
||||
|
||||
expandos(Uint8Array);
|
||||
expandos(Float64Array);
|
||||
species(Int16Array, Float32Array);
|
||||
reflection(Uint8Array);
|
||||
reflection(Int32Array);
|
||||
integrity(Uint16Array);
|
||||
|
||||
const view = box(new DataView(new ArrayBuffer(2)));
|
||||
view[0] = 5;
|
||||
view.x = 1;
|
||||
console.log(view, Object.keys(view), view[0], JSON.stringify(view), "0" in view);
|
||||
try { view.byteLength = 1; } catch (err) { console.log(err.message); }
|
||||
const buffer = box(Buffer.from([1, 2]));
|
||||
buffer.foo = 1;
|
||||
buffer[Symbol.for("k")] = 2;
|
||||
console.log(buffer, Object.keys(buffer), JSON.stringify(buffer));
|
||||
for (const key of ["parent", "offset", "length"]) {
|
||||
try { buffer[key] = 1; } catch (err) { console.log(err.message); }
|
||||
}
|
||||
const empty = box(Buffer.alloc(0));
|
||||
empty.foo = { a: [1, { b: 2 }] };
|
||||
console.log(empty);
|
||||
@@ -0,0 +1,23 @@
|
||||
// A built-in typed-array constructor set as a view's `constructor` is the
|
||||
// species of map, filter, slice and subarray when the program never names
|
||||
// the species symbol.
|
||||
function kinds(TA, Other) {
|
||||
const a = new TA([1, 2, 3, 4]);
|
||||
a.constructor = Other;
|
||||
const m = a.map((v) => v * 1.5), f = a.filter((v) => v > 1), s = a.slice(1, 3);
|
||||
console.log(m, f, s, m.constructor === Other, f.constructor === Other, s.constructor === Other);
|
||||
try {
|
||||
const sub = a.subarray(0, 2);
|
||||
console.log(sub, sub.constructor === Other);
|
||||
} catch (err) {
|
||||
console.log(err.constructor.name);
|
||||
}
|
||||
a.constructor = TA;
|
||||
console.log(a.map((v) => v / 2).constructor === TA);
|
||||
a.constructor = {};
|
||||
console.log(a.slice().constructor === TA);
|
||||
}
|
||||
kinds(Uint8Array, Float64Array);
|
||||
kinds(Int16Array, Uint8Array);
|
||||
kinds(Float32Array, Int32Array);
|
||||
kinds(Float64Array, Uint8ClampedArray);
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3900, "san": 4400 }
|
||||
@@ -9,6 +9,10 @@ import { nativeCodegenTarget } from "../../packages/compiler/src/backend/targets
|
||||
import type { NativeLinkFeatures } from "../../packages/compiler/src/backend/native-link-info.js";
|
||||
import type { RuntimePackManifest } from "../../packages/compiler/src/backend/runtime-pack-core.js";
|
||||
import type { NativeToolchainManifest } from "../../packages/compiler/src/native/toolchain.js";
|
||||
import {
|
||||
RUNTIME_ABI_MARKER,
|
||||
RUNTIME_ABI_VERSION,
|
||||
} from "../../packages/compiler/src/backend/runtime-abi.js";
|
||||
|
||||
const root = join(import.meta.dirname, "../..");
|
||||
const entry = join(root, "tests/fixtures/self-hosting/native-toolchain.ts");
|
||||
@@ -110,7 +114,7 @@ for (const backend of ["llvm"] as const) {
|
||||
object_format: target.objectFormat,
|
||||
minimum_os: target.minimumOs,
|
||||
},
|
||||
runtime_abi: { version: 43, marker: "scr_runtime_abi_v43" },
|
||||
runtime_abi: { version: RUNTIME_ABI_VERSION, marker: RUNTIME_ABI_MARKER },
|
||||
compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple },
|
||||
macros: { executable: [], excluded: [], sanitizer: "external-toolchain-required" },
|
||||
flavors: {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { mkdtempSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { expect, test } from "vitest";
|
||||
import { compile } from "@scriptc/compiler";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
// The runtime's conversions, iteration and printing of a typed array, Buffer
|
||||
// or DataView read these members from the prototype, so defining one on the
|
||||
// view refuses with a catchable error instead of being ignored.
|
||||
test("typed array members the runtime reads from the prototype refuse as own properties", async () => {
|
||||
const outDir = mkdtempSync(join(tmpdir(), "scriptc-typed-array-refusals-"));
|
||||
const entry = join(outDir, "main.mjs");
|
||||
writeFileSync(
|
||||
entry,
|
||||
`
|
||||
function attempt(define) {
|
||||
try { define(); console.log("defined"); } catch (error) { console.log(error.name, error.message); }
|
||||
}
|
||||
function run(TA, view) {
|
||||
const a = new TA(2);
|
||||
attempt(() => { a.toString = () => "x"; });
|
||||
attempt(() => { a.valueOf = () => 1; });
|
||||
attempt(() => Object.defineProperty(a, "toJSON", { value: () => 1 }));
|
||||
attempt(() => { a.join = () => ""; });
|
||||
attempt(() => { a[Symbol.iterator] = function* () {}; });
|
||||
attempt(() => { a[Symbol.for("nodejs.util.inspect.custom")] = () => "x"; });
|
||||
attempt(() => { view.toLocaleString = () => ""; });
|
||||
attempt(() => { a.label = "kept"; });
|
||||
console.log(a.label, String(a));
|
||||
}
|
||||
run(Uint8Array, new DataView(new ArrayBuffer(1)));
|
||||
`,
|
||||
);
|
||||
const result = await compile(entry, {
|
||||
backend: "llvm",
|
||||
sanitize: process.env["SCRIPTC_SAN"] === "1",
|
||||
outDir,
|
||||
outPath: join(outDir, "program"),
|
||||
});
|
||||
expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true);
|
||||
if (!result.ok) return;
|
||||
const { stdout, stderr } = await execFileAsync(result.binaryPath, [], { encoding: "utf8" });
|
||||
const refused = (key: string): string =>
|
||||
`Error defining ${key} on a typed array, Buffer or DataView is not supported yet`;
|
||||
expect(stdout).toBe(
|
||||
[
|
||||
refused("toString"),
|
||||
refused("valueOf"),
|
||||
refused("toJSON"),
|
||||
refused("join"),
|
||||
refused("Symbol(Symbol.iterator)"),
|
||||
refused("Symbol(nodejs.util.inspect.custom)"),
|
||||
refused("toLocaleString"),
|
||||
"defined",
|
||||
"kept 0,0",
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
Reference in New Issue
Block a user