From cdd78b4502299bbf5b9c3b803ea0a2731f8694d1 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 19 Oct 2026 23:36:14 -0500 Subject: [PATCH] Answer `in` on typed arrays held as any `in` on a typed array, Buffer or DataView held as `any` or `unknown` answered false for every key: indices, `length`, prototype methods and Object.prototype names. On a typed array, a canonical numeric key (one that ToString(ToNumber(key)) reproduces, or "-0") now answers by bounds: an integral index, not -0, below the length, so "1.5", "-0", "NaN" and "Infinity" answer false. Other keys answer true for members of %TypedArray%.prototype, Buffer.prototype on a Buffer, the constructors' BYTES_PER_ELEMENT and Object.prototype. A DataView has no indexed elements and answers DataView.prototype's members and Object.prototype's. --- .../tests/corpus/6407-dyn-bytes-in.ts.json | 6 ++ .../tests/corpus/6408-dyn-bytes-in-js.js.json | 6 ++ packages/runtime/src/scr_json.c | 65 +++++++++++++++++++ tests/corpus/6407-dyn-bytes-in.ts | 28 ++++++++ tests/corpus/6408-dyn-bytes-in-js.js | 35 ++++++++++ .../corpus-costs/6407-dyn-bytes-in.ts.json | 1 + .../corpus-costs/6408-dyn-bytes-in-js.js.json | 1 + 7 files changed, 142 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6407-dyn-bytes-in.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6408-dyn-bytes-in-js.js.json create mode 100644 tests/corpus/6407-dyn-bytes-in.ts create mode 100644 tests/corpus/6408-dyn-bytes-in-js.js create mode 100644 tests/harness/corpus-costs/6407-dyn-bytes-in.ts.json create mode 100644 tests/harness/corpus-costs/6408-dyn-bytes-in-js.js.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6407-dyn-bytes-in.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6407-dyn-bytes-in.ts.json new file mode 100644 index 000000000..e4b1b5d88 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6407-dyn-bytes-in.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6407-dyn-bytes-in.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6408-dyn-bytes-in-js.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6408-dyn-bytes-in-js.js.json new file mode 100644 index 000000000..30bd05d7e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6408-dyn-bytes-in-js.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6408-dyn-bytes-in-js.js" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 83b56bea4..5e812fd9b 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -2122,6 +2122,70 @@ static bool scr_dyn_array_proto_name(const ScrStr *key) { return scr_dyn_name_in(key, names, sizeof names / sizeof names[0]); } +/* Own string keys of %TypedArray%.prototype, plus BYTES_PER_ELEMENT from + * each typed array's own prototype. */ +static bool scr_dyn_typed_array_proto_name(const ScrStr *key) { + static const char *const names[] = { + "constructor", "buffer", "byteLength", "byteOffset", "length", "entries", "keys", "values", + "at", "copyWithin", "every", "fill", "filter", "find", "findIndex", "findLast", + "findLastIndex", "forEach", "includes", "indexOf", "join", "lastIndexOf", "map", "reverse", + "reduce", "reduceRight", "set", "slice", "some", "sort", "subarray", "toReversed", "toSorted", + "with", "toLocaleString", "toString", "BYTES_PER_ELEMENT" + }; + return scr_dyn_name_in(key, names, sizeof names / sizeof names[0]); +} + +/* Own string keys of Buffer.prototype that %TypedArray%.prototype lacks. */ +static bool scr_dyn_buffer_proto_name(const ScrStr *key) { + static const char *const names[] = { + "readBigUInt64LE", "readBigUInt64BE", "readBigUint64LE", "readBigUint64BE", "readBigInt64LE", + "readBigInt64BE", "writeBigUInt64LE", "writeBigUInt64BE", "writeBigUint64LE", + "writeBigUint64BE", "writeBigInt64LE", "writeBigInt64BE", "readUIntLE", "readUInt32LE", + "readUInt16LE", "readUInt8", "readUIntBE", "readUInt32BE", "readUInt16BE", "readUintLE", + "readUint32LE", "readUint16LE", "readUint8", "readUintBE", "readUint32BE", "readUint16BE", + "readIntLE", "readInt32LE", "readInt16LE", "readInt8", "readIntBE", "readInt32BE", + "readInt16BE", "writeUIntLE", "writeUInt32LE", "writeUInt16LE", "writeUInt8", "writeUIntBE", + "writeUInt32BE", "writeUInt16BE", "writeUintLE", "writeUint32LE", "writeUint16LE", + "writeUint8", "writeUintBE", "writeUint32BE", "writeUint16BE", "writeIntLE", "writeInt32LE", + "writeInt16LE", "writeInt8", "writeIntBE", "writeInt32BE", "writeInt16BE", "readFloatLE", + "readFloatBE", "readDoubleLE", "readDoubleBE", "writeFloatLE", "writeFloatBE", "writeDoubleLE", + "writeDoubleBE", "asciiSlice", "base64Slice", "base64urlSlice", "latin1Slice", "hexSlice", + "ucs2Slice", "utf8Slice", "asciiWrite", "base64Write", "base64urlWrite", "latin1Write", + "hexWrite", "ucs2Write", "utf8Write", "parent", "offset", "copy", "equals", "inspect", + "compare", "write", "toJSON", "swap16", "swap32", "swap64" + }; + return scr_dyn_name_in(key, names, sizeof names / sizeof names[0]); +} + +/* Own string keys of DataView.prototype. */ +static bool scr_dyn_data_view_proto_name(const ScrStr *key) { + static const char *const names[] = { + "constructor", "buffer", "byteLength", "byteOffset", "getInt8", "setInt8", "getUint8", + "setUint8", "getInt16", "setInt16", "getUint16", "setUint16", "getInt32", "setInt32", + "getUint32", "setUint32", "getFloat32", "setFloat32", "getFloat64", "setFloat64", + "getBigInt64", "setBigInt64", "getBigUint64", "setBigUint64", "getFloat16", "setFloat16" + }; + return scr_dyn_name_in(key, names, sizeof names / sizeof names[0]); +} + +/* `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. + * Other keys name members of %TypedArray%.prototype, Buffer.prototype for a + * 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 (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); + ScrStr *canonical = scr_f64_to_scrstr(index); + bool numeric = canonical->len == key->len && memcmp(canonical->data, key->data, key->len) == 0; + scr_str_release(canonical); + if (numeric) return index >= 0 && index == floor(index) && index < (double)v->v.bytes->len; + return scr_dyn_typed_array_proto_name(key) || (v->buffer && scr_dyn_buffer_proto_name(key)) || + scr_dyn_object_proto_name(key); +} + static bool scr_dyn_object_proto_key(const ScrDyn *target, const ScrStr *key) { return !target->null_proto && scr_dyn_object_proto_name(key); } @@ -7911,6 +7975,7 @@ bool scr_dyn_in(const ScrDyn *v, const ScrStr *key) { return out; } if (v->kind == SCR_DYN_PROXY) return scr_dyn_proxy_has(v, key); + if (v->kind == SCR_DYN_BYTES) return scr_dyn_bytes_in(v, key); if (scr_dyn_has_key(v, key)) return true; if (scr_exc_pending()) return false; if (v->kind == SCR_DYN_OBJ) return scr_dyn_builtin_inherited(v, key); diff --git a/tests/corpus/6407-dyn-bytes-in.ts b/tests/corpus/6407-dyn-bytes-in.ts new file mode 100644 index 000000000..e08eff8bc --- /dev/null +++ b/tests/corpus/6407-dyn-bytes-in.ts @@ -0,0 +1,28 @@ +// `in` on typed arrays, Buffers and DataViews held as `any` matches Node's. +// On a typed array a canonical numeric key answers by bounds (integral, +// not -0, below the length), and "1.5", "-0", "NaN", "Infinity", "01" and +// "1e0" answer false. Other keys name members of %TypedArray%.prototype, +// Buffer.prototype for a Buffer, each constructor's BYTES_PER_ELEMENT, or +// Object.prototype. A DataView has no indexed elements and answers its own +// prototype's members. + +const bytes: any = new Uint8Array([1, 2, 255]); +const floats: any = new Float64Array(2); +const buf: any = Buffer.from("ab"); +for (const [label, v] of [["u8", bytes], ["f64", floats], ["buffer", buf]] as [string, any][]) { + const keys: any[] = [0, 1, 2, 3, -1, "0", "1.5", "-0", "length", "byteLength", "buffer", "map", "set", "toString", "nope", "write"]; + console.log(label, keys.map((k) => `${String(k)}:${k in v}`).join(" ")); +} +const k: any = JSON.parse("1"); +console.log("computed", k in bytes, "length" in bytes, "map" in bytes); +const view: any = new DataView(new ArrayBuffer(4)); +console.log("dataview", [0, "getInt8", "setUint16", "byteLength", "buffer", "length", "map", "toString"].map((k) => `${String(k)}:${k in view}`).join(" ")); +const clamped: any = new Uint8ClampedArray(3); + +console.log("clamped", 2 in clamped, 3 in clamped, "BYTES_PER_ELEMENT" in clamped); +const sub: any = new Uint8Array([1, 2, 3, 4]).subarray(1, 3); +console.log("subarray", 0 in sub, 1 in sub, 2 in sub); +console.log("hasOwn", Object.hasOwn(bytes, 0), Object.hasOwn(bytes, 3), Object.hasOwn(bytes, "length"), bytes.hasOwnProperty(1)); +console.log("symbol", Symbol.iterator in bytes); +const empty: any = new Uint8Array(0); +console.log("empty", 0 in empty, "Infinity" in bytes, "NaN" in bytes, "1e0" in bytes, "01" in bytes); diff --git a/tests/corpus/6408-dyn-bytes-in-js.js b/tests/corpus/6408-dyn-bytes-in-js.js new file mode 100644 index 000000000..9677c094b --- /dev/null +++ b/tests/corpus/6408-dyn-bytes-in-js.js @@ -0,0 +1,35 @@ +// In JavaScript sources, `in` on typed arrays, Buffers and DataViews +// matches Node's. On a typed array a canonical numeric key answers by +// bounds (integral, not -0, below the length), and "1.5", "-0", "NaN", +// "Infinity", "01" and "1e0" answer false. Other keys name members of +// %TypedArray%.prototype, Buffer.prototype for a Buffer, each +// constructor's BYTES_PER_ELEMENT, or Object.prototype. A DataView has no +// indexed elements and answers its own prototype's members. + +function dyn(value) { + return value; +} + +const bytes = dyn(new Uint8Array([1, 2, 255])); +const floats = dyn(new Float64Array(2)); +const buf = dyn(Buffer.from("ab")); +function row(label, v) { + const keys = [0, 1, 2, 3, -1, "0", "1.5", "-0", "length", "byteLength", "buffer", "map", "set", "toString", "nope", "write"]; + console.log(label, keys.map((k) => `${String(k)}:${k in v}`).join(" ")); +} +row("u8", bytes); +row("f64", floats); +row("buffer", buf); +const k = dyn(JSON.parse("1")); +console.log("computed", k in bytes, "length" in bytes, "map" in bytes); +const view = dyn(new DataView(new ArrayBuffer(4))); +console.log("dataview", [0, "getInt8", "setUint16", "byteLength", "buffer", "length", "map", "toString"].map((k) => `${String(k)}:${k in view}`).join(" ")); +const clamped = dyn(new Uint8ClampedArray(3)); + +console.log("clamped", 2 in clamped, 3 in clamped, "BYTES_PER_ELEMENT" in clamped); +const sub = dyn(new Uint8Array([1, 2, 3, 4]).subarray(1, 3)); +console.log("subarray", 0 in sub, 1 in sub, 2 in sub); +console.log("hasOwn", Object.hasOwn(bytes, 0), Object.hasOwn(bytes, 3), Object.hasOwn(bytes, "length"), bytes.hasOwnProperty(1)); +console.log("symbol", Symbol.iterator in bytes); +const empty = dyn(new Uint8Array(0)); +console.log("empty", 0 in empty, "Infinity" in bytes, "NaN" in bytes, "1e0" in bytes, "01" in bytes); diff --git a/tests/harness/corpus-costs/6407-dyn-bytes-in.ts.json b/tests/harness/corpus-costs/6407-dyn-bytes-in.ts.json new file mode 100644 index 000000000..4b769b3a1 --- /dev/null +++ b/tests/harness/corpus-costs/6407-dyn-bytes-in.ts.json @@ -0,0 +1 @@ +{ "plain": 3590, "san": 4401 } diff --git a/tests/harness/corpus-costs/6408-dyn-bytes-in-js.js.json b/tests/harness/corpus-costs/6408-dyn-bytes-in-js.js.json new file mode 100644 index 000000000..12ffbeef1 --- /dev/null +++ b/tests/harness/corpus-costs/6408-dyn-bytes-in-js.js.json @@ -0,0 +1 @@ +{ "plain": 3076, "san": 4009 }