Skip to content

fix(node): throw spec arg-validation errors for fs/process/zlib (#3662) - #4082

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

proggeramlug merged 1 commit into
mainfrom
fix-3662-node-argvalidation

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

Node-core argument-validation sub-cluster of #3662: make zlib, process, and fs builtins throw the spec-mandated TypeError [ERR_INVALID_ARG_TYPE] / RangeError [ERR_OUT_OF_RANGE] on invalid arguments instead of silently proceeding.

Scope boundary: the Function.prototype not-callable checks landed in #4073 and the collection (Set/Map/Weak*) brand checks in #3739 — this PR is the remaining node-core piece (fs / process / zlib).

What changed

zlib option validation — new shared js_zlib_validate_options(opts, min_window_bits) in perry-runtime, called from both the in-tree (perry-stdlib) and out-of-tree (perry-ext-zlib, used by the auto-optimize build) codecs: from gzipSync / deflateSync and every create* stream factory. Validates windowBits, level, memLevel, strategy, chunkSize, and flush in Node's exact field order so the first-offender message matches Node when several options are bad. windowBits lower bound is 9 for gzip compression and 8 for every other codec (mirrors Node's Z_MIN_WINDOWBITS). A non-numeric option throws the "options.<name>" property must be of type number TypeError; an out-of-range one throws the matching ERR_OUT_OF_RANGE RangeError.

zlib buffer-arg validation — new js_zlib_validate_buffer_arg rejects a non-string/Buffer/TypedArray/DataView/ArrayBuffer first argument to the gzip/gunzip/deflate/inflate sync codecs in the ext path (the stdlib path already validated this inline via codec_bytes). brotli's ext codec takes a pre-unboxed pointer (calling-convention quirk) and is left as a follow-up — the stdlib path already validates it.

process.emitWarning — validates the warning argument (must be a string or Error) and the type positional (must be a string), in Node's order (type before warning).

fs.mkdirSync — validates the options object's recursive (boolean) and mode (number/string) properties.

Testing

New gap test test-files/test_gap_3662_node_argvalidation.ts is byte-for-byte identical to node --experimental-strip-types in both the default auto-optimize build and PERRY_NO_AUTO_OPTIMIZE=1:

=== NO-OPT diff vs Node ===   ✅ MATCH
=== AUTO-OPT diff vs Node === ✅ MATCH
  • cargo test --release -p perry-runtime → 964 passed, 0 failed
  • cargo fmt --all -- --check → clean
  • Regression-checked valid round trips (gzip/deflate/inflate with options, createGzip, recursive mkdirSync, string emitWarning) stay working.

Follow-ups (not in scope here)

  • brotli buffer-arg validation in the ext (pointer-based) codec path.
  • Scattered fs gaps surfaced while probing (writeFileSync bad-encoding ERR_INVALID_ARG_VALUE, readSync buffer-before-fd order) — separate, riskier touches to hot paths.

Closes part of #3662.

Node-core argument-validation sub-cluster of #3662: make `zlib`, `process`,
and `fs` builtins throw the spec-mandated `TypeError [ERR_INVALID_ARG_TYPE]` /
`RangeError [ERR_OUT_OF_RANGE]` on invalid arguments instead of silently
proceeding. (The `Function.prototype` not-callable checks landed in #4073 and
the collection brand checks in #3739; this change is the node-core piece.)

zlib option validation — new shared `js_zlib_validate_options(opts, min_wb)`
in perry-runtime, called by the in-tree (perry-stdlib) *and* out-of-tree
(perry-ext-zlib, used by the auto-optimize build) codecs from `gzipSync` /
`deflateSync` and every `create*` stream factory. Validates `windowBits`,
`level`, `memLevel`, `strategy`, `chunkSize`, and `flush` in Node's exact
field order so the first-offender message matches; `windowBits` lower bound is
9 for gzip compression and 8 elsewhere. A non-numeric option throws the
`"options.<name>" property must be of type number` TypeError; an out-of-range
one throws the matching RangeError.

zlib buffer-arg validation — new `js_zlib_validate_buffer_arg` rejects a
non-string/Buffer/TypedArray/DataView/ArrayBuffer first argument to the
gzip/gunzip/deflate/inflate sync codecs in the ext path (stdlib already did so
inline). brotli's ext (pointer-based) codec is left as a follow-up.

process.emitWarning — validates the `warning` argument (string or Error) and
the `type` positional (string), matching Node's ordering.

fs.mkdirSync — validates the options object's `recursive` (boolean) and `mode`
(number/string) properties.

New gap test test_gap_3662_node_argvalidation.ts is byte-for-byte identical to
`node --experimental-strip-types` in both the default auto-optimize and
`PERRY_NO_AUTO_OPTIMIZE=1` builds.
@proggeramlug
proggeramlug merged commit 27e03af into main Jun 2, 2026
11 checks passed
@proggeramlug
proggeramlug deleted the fix-3662-node-argvalidation branch June 2, 2026 09:09
proggeramlug added a commit that referenced this pull request Jun 2, 2026
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.

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