Skip to content

fix(runtime): validate typed-array constructor length (#3662) - #4093

Merged
proggeramlug merged 1 commit into
mainfrom
fix-3662-remaining
Jun 2, 2026
Merged

proggeramlug merged 1 commit into
mainfrom
fix-3662-remaining

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

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 the spec error on invalid args" cluster.

Prior #3662 sub-clusters already merged:

Before

new Uint8Array(-1)        // -> length-0 Uint8Array (no throw)
new Float64Array(Infinity) // -> length-0 (no throw)

After (byte-identical to node --experimental-strip-types)

RangeError: Invalid typed array length: -1
RangeError: Invalid typed array length: Infinity

What changed

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). Covers 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, 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.ts is byte-identical to node --experimental-strip-types in both the default auto-optimize and PERRY_NO_AUTO_OPTIMIZE=1 builds (covers literal + variable negative lengths, Infinity, 2**53, and the non-throwing NaN/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 — the byteOffset getter is hardcoded to 0), tracked separately.

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
proggeramlug merged commit 4cc05ef into main Jun 2, 2026
11 checks passed
@proggeramlug
proggeramlug deleted the fix-3662-remaining branch June 2, 2026 10:12
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant