fix(runtime): Node arg-validation errors on fs + assert.throws code/name matching (#2013, #2014)#2035
Merged
Merged
Conversation
…ame matching (#2013, #2014) #2014 — assert.throws object validator: - Error objects now store user-assigned own properties (err.code = "X", err.errno = -2, custom fields) via a per-error side table consulted by the object setter and the .code/.name/... getters. ErrorHeader is a fixed #[repr(C)] struct with no field-storage region, so these assignments were previously dropped and read back as undefined. - object_matcher_matches: drop the over-lenient carve-out that treated a missing actual code/errno as a match, so a wrong code is now rejected. - expected_error_matches: a plain object validator matches only via its own keys, no longer falling through to the instanceof / builtin-constructor checks that could spuriously accept any error. #2013 — fs argument validation: - New fs::validate module. validate_path (path-only functions) throws ERR_INVALID_ARG_TYPE on non path-like input; validate_path_or_fd (readFileSync/writeFileSync) treats a numeric argument as a file descriptor and throws EBADF for an unregistered/closed fd, otherwise ERR_INVALID_ARG_TYPE. fd validity is checked against Perry's synthetic FD_REGISTRY (fds start at 100), not the OS. - Wired into accessSync, statSync, lstatSync, readdirSync, mkdirSync, unlinkSync, rmdirSync, readlinkSync, openSync, readFileSync, writeFileSync. Verified byte-identical to Node v25 across the fs and assert validation cases. No regressions in the fs or assert node-suites (remaining failures are pre-existing and unrelated: assert.rejects is unimplemented, deepStrictEqual Map/Set/cause gaps, CallTracker, cp/symlink deref, rmdirSync recursive-option removal).
This was referenced May 27, 2026
proggeramlug
added a commit
that referenced
this pull request
May 28, 2026
…hmod/copyFile/write/writev (#2013) (#2181) Extends the #2035 validator surface to the rest-of-fs slice the #2013 update flagged as still falling through. Same Node-compatible error shape (TypeError [ERR_INVALID_ARG_TYPE] / RangeError [ERR_OUT_OF_RANGE] with the per-error side table from #2035 carrying `.code`). New validators in `fs::validate`: - `validate_fd(value)` — Node-compatible int32 fd check (`>= 0 && <= 2^31-1`). - `validate_int32(value, name, min, max)` — reused for uid / gid / mode. - `validate_function(name, value)` — TypeError if the value isn't a Perry closure, via `extract_closure_ptr`. Wired in: - `fsyncSync` / `fdatasyncSync` / `writeSync*` / `writevSync` — fd. `writevSync` skips fd validation when the buffers array is empty (matches Node: empty writev returns 0 without touching the fd). - `fchownSync` — fd, uid, gid. - `lchownSync` — path, uid, gid. - `lchmodSync` — path only (mode range deferred; Node opens the path first so `ENOENT` wins on non-existent paths). - `copyFileSync` / async — src, dest, mode (range-checked when supplied). - `exists` callback — `cb` must be a function (path bad-type is silent in Node). - `lchown` / `lchmod` async — same as their sync siblings + callback. To keep the `FileHandle` path safe (it may hold a `-1` fd sentinel from a failed open), extract `fsync_sync_inner` / `fdatasync_sync_inner` / `fchown_sync_inner` / `write_string_sync_inner` / `write_buffer_sync_inner` / `writev_sync_inner` helpers without validation and route `filehandle_*_impl` through those. The C-ABI `js_fs_*_sync` entry points still validate as before. Verification: - New `test-parity/node-suite/fs/sync/arg-validation.ts` is byte-identical to `node --experimental-strip-types` across 27 cases covering fd type+range, path-type, uid/gid type+range, copyFile src/dest/mode, write/writev fd, and the writev empty-array no-throw contract. - Direct re-run of `node-suite/fs/fd/{fdatasync, fstat-ftruncate-fsync, readv-writev, read-write-optional}.ts`, `callbacks/{chown, copyfile-excl, lchmod}.ts`, and `sync/captured-methods.ts` all byte-identical with Node — no regressions in the affected paths. - `cargo fmt --all -- --check` clean. Follow-ups still in #2013: `lchmodSync` mode + `lchownSync` range on existing paths (paired with `ENOENT` propagation), required-callback validation on `fsync`/`fdatasync`/`fchown` async wrappers, and the non-fs APIs (buffer, net, http, zlib, crypto, process, url, timers). Co-authored-by: Ralph Küpper <ralph@skelpo.com>
3 tasks
proggeramlug
added a commit
that referenced
this pull request
May 28, 2026
…chmodSync/chmodSync/renameSync (#2013) (#2328) PR #2035 added the validation pattern for `fs.readFileSync` / `fs.accessSync` / `fs.statSync` / `fs.openSync`; the issue's follow-up call-out lists ~27 fs functions that still hit "Missing expected exception" because they don't yet validate. This gap-fills the fd-only sync surface (`closeSync`, `readSync`, `readvSync`, `writeSync`, `writevSync`, `fchmodSync`, `fsyncSync`, `fdatasyncSync`, `fchownSync`) and the path-mutator sync surface (`chmodSync`, `renameSync`) so each: - throws `TypeError [ERR_INVALID_ARG_TYPE]` on a non-fd / non-path first argument (matches Node); - throws `Error [EBADF]` on a numeric fd that isn't open in perry-runtime's `FD_REGISTRY` (matches Node's syscall surface). The new `validate_fd_open(value, syscall)` helper combines the existing `validate_fd` type+range probe with an `FD_REGISTRY` membership check + EBADF throw. Callers that need the validation order preserved (`fchownSync`: validate uid+gid BEFORE EBADF, per Node's `validateInteger` ordering) keep the bare `validate_fd` and issue the EBADF after the rest of the int32 validations. `validate_fd` itself stays single-purpose so the FileHandle wrappers — which can legitimately hold a `-1` sentinel from a failed open — keep their silent no-op fallback. The parity test (`test-parity/node-suite/fs/sync/arg-validation.ts`) gets 13 new probes covering each new shape against `node --experimental-strip-types`; the existing legacy block stays byte-identical. `cargo test -p perry-runtime --lib` still 746/746. Co-authored-by: Ralph Küpper <ralph@skelpo.com>
proggeramlug
added a commit
that referenced
this pull request
May 29, 2026
…gth (#2013) (#2440) Buffer.alloc/allocUnsafe/allocUnsafeSlow now throw ERR_INVALID_ARG_TYPE on a non-number size and ERR_OUT_OF_RANGE on NaN/Infinity/negatives; Buffer.concat throws ERR_INVALID_ARG_TYPE for a non-Array list / non-Buffer element and validates totalLength; Buffer.byteLength rejects a non string/Buffer/ ArrayBuffer/TypedArray first argument. Previously these coerced silently, so assert.throws-style tests saw 'Missing expected exception'. Reuses the fs::validate primitives (#2035) — the shared validation home the issue calls out — extending describe_received to render strings/bigints. New runtime validators are reached from the Buffer factory codegen (env_clones.rs / array_methods.rs) and the byteLength/concat runtime paths. Verified byte-identical to Node v25 across 120 buffer node-suite cases (incl. 3 new arg-validation tests); no regressions. Co-authored-by: Ralph Küpper <ralph@skelpo.com>
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
Addresses the cross-cutting Node error-validation parity theme surfaced by the #800 sweep after #1924 fixed
assert.throws. Two coupled issues:assert.throws(fn, validator)accepted a wrong-codeerror as a pass.fsAPIs returned sentinels instead of throwing Node'sERR_INVALID_ARG_TYPE/EBADFon bad input.They're handled together because verifying #2013 honestly requires #2014's matcher to actually compare error codes.
#2014 —
assert.throwsobject validatorErrorHeaderis a fixed#[repr(C)]struct with no field-storage region, soerr.code = "X"/err.errno = -2/ custom fields were silently dropped and read back asundefined. A per-error side table (keyed by the error pointer, owned-Stringfor string values / raw bits for immediates — GC-safe) now backs these; the object setter routesGC_TYPE_ERRORsets to it, and the.code/.name/… getters check it first (so a user override also works).object_matcher_matches: dropped the over-lenient carve-out that treated a missing actualcode/errnoas a match — a wrong/absent code is now a mismatch, matching Node.expected_error_matches: a plain object validator ({ code: … }) now matches only via its own keys; it no longer falls through to theinstanceof/ builtin-constructor checks that could spuriously accept any error.#2013 —
fsargument validationNew reusable
fs::validatemodule:validate_path(path-only functions) →ERR_INVALID_ARG_TYPE(TypeError) on any non path-like input (string / Buffer /file:URL are accepted).validate_path_or_fd(readFileSync/writeFileSync) → treats a number as a file descriptor and throwsEBADFfor an unregistered/closed fd, otherwiseERR_INVALID_ARG_TYPE. fd validity is checked against Perry's syntheticFD_REGISTRY(fds start at 100), not the OS — an earlyfcntl-based version wrongly rejected valid Perry fds and broke the fd read/write tests; fixed.Wired into
accessSync,statSync,lstatSync,readdirSync,mkdirSync,unlinkSync,rmdirSync,readlinkSync,openSync,readFileSync,writeFileSync.Verification
ERR_INVALID_ARG_TYPE;readFile/writeFilenumber →EBADF, other bad types →ERR_INVALID_ARG_TYPE) and theassert.throwsrepro/positive/negative cases.fsorassertnode-suites. Remaining failures are pre-existing and unrelated to this change:assert.rejectsis unimplemented;deepStrictEqualMap/Set/cause gaps;CallTracker(removed in Node 20);cpsymlink-deref;rmdirSync({recursive})removal error.cargo fmt --all -- --checkclean.Follow-ups (not in scope here)
validate_pathhelper extends naturally to the remainingfsfunctions and the other APIs in stdlib doesn't throw Node arg-validation errors (ERR_INVALID_ARG_TYPE/EBADF/...) on bad input — ~85 tests across fs/buffer/net/http/crypto/zlib/process #2013 (buffer / net / http / zlib / crypto / process).assert.rejects/assert.doesNotReject(async) is still unimplemented — would flip the 3rejectsnode-suite tests; worth its own issue.