superCallStmt asserted the class base before checking for a callable
base. A class that extends an ordinary function constructor has no base
class info, so the assertion held null. Node ignores the unused value;
the native compiler throws the TypeError of a non-null assertion that
held null, and failed to compile programs with such classes (for example
the Effect stream fixtures). The base is now read after that case
returns.
node:test changes, matching Node 24.22:
- t.assert.snapshot(value[, options]) serializes the value with Node's
default serializer, JSON.stringify(value, null, 2), or with an
options.serializers array literal applied in order, and compares it
with assert.strictEqual against the snapshot stored under
"<test full name> <n>" in "<entry file>.snapshot".
- t.assert.fileSnapshot(value, path[, options]) compares the serialized
value with the contents of path.
- Snapshot files are read in the form Node writes them, exports[`key`] =
`value`; statements, with template escapes and CRLF line endings
cooked as in a template literal.
- Missing snapshots, unreadable snapshot files and throwing serializers
throw Node's ERR_INVALID_STATE errors, with Node's messages.
- Running the binary with Node's --test-update-snapshots flag as its
first argument records the values and writes the snapshot files after
the last test, keys sorted as Node sorts them. The flag is kept out of
process.argv and appears in process.execArgv, which is otherwise [].
- Snapshot assertions count toward the test's plan.
The snapshot fixture compares the reporter output with Node, plain and
with SCRIPTC_SAN=1, against a snapshot file Node wrote. The update-mode
harness test runs Node and the binary with --test-update-snapshots and
compares the files they write. The node:test normalization also drops
the "... N lines matching cause stack trace ..." line Node prints for an
error cause, with the stack frames it summarizes.
markAsUncloneable(value) marks a plain object, a record, an error or a
class instance (scr_json.c); structuredClone and postMessage then throw
DataCloneError "Cannot clone object of unsupported type." for it, also
where it is nested. As in Node, the mark has no effect on primitives,
arrays, typed arrays and ArrayBuffers. Marked values are retained until
the thread exits; with nothing marked, the clone paths add one branch.
worker[Symbol.asyncDispose]() terminates the worker and resolves with
undefined after its 'exit' event, so `await using` disposes a Worker at
the end of its block. The Worker handle's symbol_get op answers the
well-known symbol: the symbol unit registers Symbol.asyncDispose with the
dyn core when the program names it, and scr_dyn_is_async_dispose_symbol
tells the worker unit, so worker programs that never name the symbol do
not link the symbol unit.
static-support.json adds rows for markAsUncloneable, isInternalThread
(already lowered as false and declared) and worker[Symbol.asyncDispose]().
Corpus 5990-5991 match Node v24.22.0; corpus-costs.json records their
costs.
- util.format %d and %i format bigint, symbol, null and undefined arguments
- util.format %j prints [Circular] for circular arguments
- util.types answers the remaining brand checks
- util.inspect prints WeakMap, WeakSet, Date and numeric typed array values as Node does
- util.inspect lowers the breakLength, compact and sorted options
- URLSearchParams iterators compile outside a for-of head
- URLSearchParams can be built from [string, string] tuple pairs
- querystring.stringify writes bigint values
- StringDecoder accepts an encoding known only at run time
- os.setPriority is lowered
- The EventEmitter leak warning reaches process 'warning' listeners on the next tick
- The events listener-count and max-listener helpers work over EventEmitter instances
- timers/promises setTimeout, setImmediate and scheduler.wait accept an AbortSignal
- timers/promises scheduler.wait and scheduler.yield are lowered
net.SocketAddress and net.BlockList were refused at compile time. They
are now native checked-dynamic handles in scr_net.c, so their getters
and methods also work on untyped values, and they match Node 24:
- new SocketAddress(options) checks the options in Node's order
(ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE for the family,
ERR_SOCKET_BAD_PORT, ERR_OUT_OF_RANGE for the flow label) and throws
ERR_INVALID_ADDRESS for an address that does not parse. parse() reads
URL.parse(`http://${input}`)'s hostname and port and answers undefined
on failure; isSocketAddress(), toJSON() and util.inspect match.
- BlockList takes address, range, subnet and CIDR rules (addAddress,
addAddresses, addRange, addSubnet, addCIDR, addCIDRs), removes them
(removeAddress, removeRange, removeSubnet, removeCIDR, clear) and lists
them in Node's order: IPv4 addresses, IPv6 addresses, subnets, then
ranges, each newest first, with a repeated address kept once.
- check() compares across families through the IPv4-mapped IPv6 form,
as Node's SocketAddress::compare and is_in_network do, and answers
false for a string that does not parse in the given family.
- rules, size, toJSON(), fromJSON() (Node's rule patterns), isBlockList(),
PRIVATE_RANGES, instanceof and util.inspect match, with the validation
errors of lib/internal/blocklist.js.
Corpus 5659 and the node-types fixture net-blocklist.ts cover both
classes.
The compatibility registry lists the BlockList methods and
SocketAddress.parse as supported, and both classes as partial: their
structured clone and worker transfer forms are not modeled.
fs.cpSync, fs.cp and fs/promises cp compile with every option:
dereference, errorOnExist, filter, force, mode, preserveTimestamps,
recursive and verbatimSymlinks. They follow Node's two implementations.
cpSync follows cp-sync.js over the fs binding: copy errors are plain
Errors with a code, std::error_code failures throw an ErrnoException,
and links inside a copied directory point at their canonical targets.
fs.cp and fs/promises cp follow cp.js, whose copy errors are
SystemErrors with code, info, errno, syscall and path.
Options and paths validate in Node's order with Node's errors. filter
runs for the top-level paths and each entry in Node's order; an async
filter must return a promise that is already settled, and a pending one
throws SC2020. The callback and promise forms complete the copy before
the callback runs or the promise settles. Windows builds throw SC2020.
The copy lives in the new scr_fs_cp.c. Directory walks keep their frames
on the heap, so deep trees also copy on a fiber's stack. File contents
copy with sendfile on Linux, and COPYFILE_FICLONE uses FICLONE
where it exists. ScrError gains the SystemError context and the
ErrnoException key order, with matching fields in the compiler's Error
layout. The stored fs.cp callback uses the same implementation.
Corpus 5567-5571 cover the sync, callback and promise forms, errors,
symlinks and 1500-level trees.
exec(command, callback) and exec(command, { encoding, maxBuffer },
callback) lower like execFile's callback slice. The command line runs
with /bin/sh -c, as execSync runs it, the call returns the live
ChildProcess, and the error-first callback receives the captured utf8
stdout and stderr. A non-zero exit, including the shell's 127 for a
missing command, reports Node's "Command failed: <command>\n<stderr>":
scr_exec_file takes a shell flag so the message shows the command line.
Under @types/node, child_process.ExecException maps to Error, as
ExecFileException does. Its code member is the child's numeric exit
status, which the runtime error's string code slot does not hold, so
reading it, testing it with `in`, or testing its typeof is a named
refusal.
new MessageChannel() from node:worker_threads makes two entangled
MessagePorts. They share the handle model of parentPort: postMessage
with the existing message clone, 'message' and 'close' events,
on/once, start, close, ref, unref and hasRef. A port's 'close' reaches
the other port after its queued messages. receiveMessageOnPort(port)
answers the next queued message as { message }, or undefined.
A channel is a thread-neutral record (scr_mailbox.c): each end has its
queue and the inbox of the thread that owns its port. A port listed in a
transfer list, given as postMessage's array or { transfer }, or as the
Worker constructor's transferList, travels with the message, and the
decoding thread adopts it. The sending port becomes unusable. A port in a
message without that listing throws DataCloneError, and so does a port
listing itself.
Port events follow Node's loop phases. Each port's queued messages
arrive as one batch, ports in creation order, before setImmediate
callbacks, with nextTicks and microtasks after each message (a new loop
hook keeps the pass going until the batch is delivered). A port's own
'close' runs in the following close phase, the latest close() first. The
other port's 'close' arrives on a later poll, in the order the notices
were sent, and only while the loop keeps running.
Buffer.from of strings, arrays and Uint8Arrays, Buffer.allocUnsafe and
Buffer.concat now return views into a per-thread pool slab of
Buffer.poolSize bytes (64 KiB by default) when the size is below
Buffer.poolSize >>> 1, following Node's allocate, fromStringFast and
fromArrayLike: 8-byte aligned offsets, a new slab when a request does not
fit, and the four-bytes-per-unit reservation for strings. Buffer.poolSize
can be read and set, and Buffer.allocUnsafeSlow returns an unpooled Buffer.
A slab lives while any of its views does. Its ArrayBuffer cannot be
transferred, and structuredClone of a typed array view copies the whole
ArrayBuffer and keeps the view's offset. Buffer.alloc, allocUnsafe and
allocUnsafeSlow validate sizes with Node's ERR_OUT_OF_RANGE error.
The first pooled allocation finds the startup slab, which has the
default 64 KiB size. A request it cannot hold gets a slab of the current
Buffer.poolSize, as in Node, and the room is checked after every new
slab, so a Buffer.poolSize raised before the first pooled allocation
cannot place a reservation past the end of a 64 KiB slab. A reservation
larger than a fresh slab traps instead of writing past it.
Corpus 5531 (typed) and 5532 (untyped) raise Buffer.poolSize before the
first pooled allocation and allocate just under the new threshold
through each pooled path, each case in a fresh child process, and also
shrink Buffer.poolSize and change it between allocations. The
node-types fenced fixture no longer lists Buffer.poolSize, which now
compiles.