Repository navigation
fix(runtime): validate typed-array constructor length (#3662) - #4093
Merged
Merged
Conversation
Typed-array constructors silently clamped a negative or out-of-range length argument to 0 instead of throwing the spec-mandated RangeError. This is the ECMAScript-constructor slice of the #3662 "builtins must throw" cluster (collection brand checks landed in #3739, Function.proto in #4073, node-core fs/zlib/process in #4082). Add a shared ToIndex-style length validator: truncate toward zero (NaN -> 0, 2.5 -> 2, no throw) and throw a plain `RangeError: Invalid typed array length: <n>` (no .code, matching Node) when the resulting integer is negative, Infinity, or >= 2**53. - typedarray.rs: typed_array_length_or_throw, wired into js_typed_array_new_empty (literal-length path) and js_typed_array_new (INT32-tagged and plain-double runtime-dispatch paths) for Int8/16/32, Uint16/32, Float32/64, Uint8ClampedArray. - buffer/from.rs: uint8array_length_or_throw, wired into js_uint8array_alloc (literal path) and js_uint8array_new (runtime path) — Uint8Array is backed by a BufferHeader in Perry. - codegen arrays_finds.rs: stop clamping the literal integer length with .max(0) so a negative literal reaches the runtime validator. New gap test test_gap_3662_typedarray_length.ts is byte-identical to node --experimental-strip-types in both the default auto-optimize and PERRY_NO_AUTO_OPTIMIZE=1 builds.
proggeramlug
pushed a commit
that referenced
this pull request
Jun 2, 2026
…edArray length message (#3146) Spec-mandated throw correctness for #3146. The TypedArray length *validation* itself landed in #4093; this PR adds the two remaining throw gaps plus a message-format fix to #4093: - `undefined.toString()` / `null.toString()` / `null.foo()` → TypeError ("Cannot read properties of <undefined|null> (reading '…')"). New `js_jsvalue_to_string_method` adds the nullish guard that abstract ToString (`String(x)`, templates, `+`) intentionally lacks, so `String(undefined)` still yields "undefined". The `.toString()` codegen member-call routes through it. - `Number.prototype.toString(radix)` / `BigInt.prototype.toString(radix)` with radix ∉ [2, 36] → RangeError. The number `.toString` method-call arm now delegates to the canonical `js_jsvalue_to_string_radix` / `coerce_validate_radix` path (shortest round-trip formatting, #2864) instead of a private formatter, and the shared error text is corrected to match Node verbatim: "toString() radix argument must be between 2 and 36" (#2864 had dropped "argument"). - `new <TypedArray>(badLength)` / `new Uint8Array(badLength)` RangeError now reports the ORIGINAL argument like Node (`new Int32Array(-1.5)` → "Invalid typed array length: -1.5") rather than the truncated integer (-1). Fixes the formatting in #4093's `typed_array_length_or_throw` / `uint8array_length_or_throw`. Regression test test-files/test_gap_3146_spec_throws.ts is byte-identical to `node --experimental-strip-types`; #4093's test_gap_3662_typedarray_length.ts stays green. Remaining #3146 sub-case (non-callable callbacks) tracked in #4091. Also allowlists the pre-existing oversized object_ops.rs (2004 LOC on main, unrelated to this change) so the file-size lint gate passes.
proggeramlug
added a commit
that referenced
this pull request
Jun 2, 2026
…edArray length message (#3146) (#4090) Spec-mandated throw correctness for #3146. The TypedArray length *validation* itself landed in #4093; this PR adds the two remaining throw gaps plus a message-format fix to #4093: - `undefined.toString()` / `null.toString()` / `null.foo()` → TypeError ("Cannot read properties of <undefined|null> (reading '…')"). New `js_jsvalue_to_string_method` adds the nullish guard that abstract ToString (`String(x)`, templates, `+`) intentionally lacks, so `String(undefined)` still yields "undefined". The `.toString()` codegen member-call routes through it. - `Number.prototype.toString(radix)` / `BigInt.prototype.toString(radix)` with radix ∉ [2, 36] → RangeError. The number `.toString` method-call arm now delegates to the canonical `js_jsvalue_to_string_radix` / `coerce_validate_radix` path (shortest round-trip formatting, #2864) instead of a private formatter, and the shared error text is corrected to match Node verbatim: "toString() radix argument must be between 2 and 36" (#2864 had dropped "argument"). - `new <TypedArray>(badLength)` / `new Uint8Array(badLength)` RangeError now reports the ORIGINAL argument like Node (`new Int32Array(-1.5)` → "Invalid typed array length: -1.5") rather than the truncated integer (-1). Fixes the formatting in #4093's `typed_array_length_or_throw` / `uint8array_length_or_throw`. Regression test test-files/test_gap_3146_spec_throws.ts is byte-identical to `node --experimental-strip-types`; #4093's test_gap_3662_typedarray_length.ts stays green. Remaining #3146 sub-case (non-callable callbacks) tracked in #4091. Also allowlists the pre-existing oversized object_ops.rs (2004 LOC on main, unrelated to this change) so the file-size lint gate passes. Co-authored-by: Ralph Küpper <ralph@skelpo.com>
This was referenced Jun 2, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Typed-array constructors silently clamped a negative or out-of-range length argument to
0instead of throwing the spec-mandatedRangeError. This is the ECMAScript-constructor slice of the #3662 "builtins must throw the spec error on invalid args" cluster.Prior #3662 sub-clusters already merged:
Function.prototype.{apply,call,bind}not-callablethis— fix(runtime): brand-check Function.prototype apply/call/bind this (#3662) #4073fs/zlib/processarg validation — fix(node): throw spec arg-validation errors for fs/process/zlib (#3662) #4082Before
After (byte-identical to
node --experimental-strip-types)What changed
A shared
ToIndex-style length validator: truncate toward zero (NaN→ 0,2.5→ 2, no throw) and throw a plainRangeError: Invalid typed array length: <n>(no.code, matching Node) when the resulting integer is negative,Infinity, or>= 2**53.typedarray.rs—typed_array_length_or_throw, wired intojs_typed_array_new_empty(literal-length path) andjs_typed_array_new(INT32-tagged and plain-double runtime-dispatch paths). CoversInt8/16/32,Uint16/32,Float32/64,Uint8ClampedArray.buffer/from.rs—uint8array_length_or_throw, wired intojs_uint8array_alloc(literal path) andjs_uint8array_new(runtime path).Uint8Arrayis backed by aBufferHeaderin Perry, so it has its own path.codegen/expr/arrays_finds.rs— stop clamping the literal integer length with.max(0)so a negative literal reaches the runtime validator.Testing
New gap test
test-files/test_gap_3662_typedarray_length.tsis byte-identical tonode --experimental-strip-typesin both the default auto-optimize andPERRY_NO_AUTO_OPTIMIZE=1builds (covers literal + variable negative lengths,Infinity,2**53, and the non-throwingNaN/fractional truncation cases).Out of scope
The 3-arg buffer-view form
new Uint8Array(buffer, byteOffset, length)offset/length bounds validation is a deeper feature gap (Perry does not yet model real views — thebyteOffsetgetter is hardcoded to 0), tracked separately.