- EventEmitter.defaultMaxListeners on the untyped constructor is Node's
accessor over the default the typed EventEmitter reads: assignments
validate as validateNumber(arg, 'defaultMaxListeners', 0) does, and
getMaxListeners() of emitters without their own limit answers the new
value.
- dyn.builtinInstanceOf and dyn.functionInstanceOf link the
constructor-function unit and the units it calls (dyn-invoke, inspect,
symbol, emitter), so a program whose only use is `this instanceof F`
links.
- The legacy pipe's cleanup runs once: when the destination emits
'close' from end(), the cleanup still queued on the source's 'end'
finds the listeners removed and emit('end') answers true.
pipe() called from untyped JavaScript on a native stream went through
scr_stream_pipe directly, so a helper that piped into the converted
process.stdout or process.stderr ended it at the source's end, and later
writes through the program's NodeJS.WritableStream slots failed. The
stream view's pipe now calls scr_stream_pipe_general, which keeps end off
for those Writables, as Node's pipe() does for process.stdout and
process.stderr.
Adds corpus 6469.
A method call declared to return `this`, such as setEncoding, resolves to
its receiver's access path, so a 'data' listener on the stream that
`const out = child.stdout.setEncoding("utf8")` returns receives strings.
A setEncoding call that a preceding statement always evaluates (its
expression, a variable initializer, or the receiver of a method chain in
one of those) counts as preceding the listener. Only `new` and the
child_process factories (spawn, fork, exec, execFile) count as creating a
separate stream; any other call result may be a stream another binding
already holds.
Externrefs a guest holds are borrowed pointers. An import's result was
kept only until the outermost call returned, which was before the export
call converted its results to JavaScript, so a result created by an
import was freed before conversion. A table or global slot released the
reference it replaced at once, although the guest may still hold it in a
local after reading the slot. The kept references are now released after
the outermost caller has converted its results (scr_wasm_leave), and a
table or global store during a call keeps the reference it replaces until
then.
Checked memories (explicit bounds checks: sanitized builds, Windows)
grew with realloc, which can move the storage that memory.buffer and its
views alias. They now reserve the address space for their largest size
(mmap on POSIX hosts, VirtualAlloc on Windows) and commit pages as they
grow, so the base never moves. Hosts without address-space reservation
grow in place only until a buffer has been handed out; after that,
growth that would move the storage fails.
Tests: webassembly-externref-lifetimes.mjs (import results through an
export, table and global references replaced by the guest and by
JavaScript), webassembly-memory-growth.test.ts (views written after
growth from JavaScript and from the guest), and translate.test.ts cases
for memargs naming memory 0 explicitly and for local.tee operand types,
which b3bcbaaf7 made consistent with validation. Under SCRIPTC_SAN=1 the
two runtime tests report heap-use-after-free without these changes.
Merges main; internal/compatibility/generated/node-v24-backlog.json is
regenerated with pnpm node-compat.
`totals[side] += n`, where `side` is a union of string literals naming
declared fields of one type, now dispatches over those fields: the
receiver and key evaluate once, the old value is read from the field the
key names, then the right side, then the write to that same field. Every
compound operator, prefix and postfix ++/--, and ??=/||=/&&= share the
path. A single literal key (spelled at the site or carried by a const)
reads and writes the field directly. A key smuggled outside its literal
type throws the fixed-shape TypeError of a keyed write miss.
A string key into a pure index-signature record (`counts[name] += 1`,
`counts[name]++`) reads the entry, NaN for a missing one under numeric
operators, and inserts or replaces it.
Keys naming fields of different types, class instances and JavaScript
sources keep their refusals.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- http.request, https.request and get validate each name and value of a
flat [name, value, ...] headers array, as Node's _storeHeader does: a
name that is not an HTTP token throws ERR_INVALID_HTTP_TOKEN, a value
with an invalid character throws ERR_INVALID_CHAR, and an odd length
throws ERR_INVALID_ARG_VALUE with the inspected array. The
[[name, value], ...] entry form throws the options fence.
- A closing socket drops its 'lookup' listeners with its other listeners,
so a listener that holds the socket no longer keeps it alive.
- The options-record diagnostics snapshot no longer expects the
socketPath refusal.
The export-alias rewrite skipped a whole function when a let or const
anywhere inside it declared the alias's name, so the function's other
references to the alias kept the alias name after its declaration was
removed. A function now shadows the alias only through its parameters,
its own name, its vars and the declarations directly in its body; a
block, case block, catch clause or for loop shadows it through its own
declarations, and the walk skips just that scope.
- A checked-dynamic entry of a tuple Promise.all goes through
PromiseResolve (`dyn.promiseResolve`): a native promise is itself, a
thenable adopts its settlement through its resolve job, and any other
value fulfills. Before, a thenable fulfilled the slot with the object.
- Tuple fields read their entry by position. Shape fields are sorted by
name, so tuples of more than ten entries paired fields with the wrong
entries.
The refusal of callback returns that the inferred result type would cut
down found the return by walking up through parentheses and await only,
so a return inside a conditional's branch or a `satisfies` expression
converted silently and lost the extended members. It now walks through
every parent whose value can be the expression's own: parentheses,
await, satisfies, non-null assertions, conditional branches, the
operands of ??, || and &&, and the right operand of a comma.
Under the general stream representation, every conversion of
process.stdout or process.stderr into a Writable slot made a new stream,
so two NodeJS.WritableStream slots holding process.stdout compared
unequal and kept separate listeners and writable state. A pipe into such
a slot also ended the converted Writable at the source's end, so later
writes through the slot failed; Node never ends process.stdout or
process.stderr from a pipe.
scr_stream_stdio_writable now keeps one Writable per descriptor and
thread, released at exit. Under the general representation, a pipe into
a Writable slot lowers to readable.pipeGeneral (scr_stream_pipe_general,
last in the stream unit), which keeps end off when the destination is
one of those Writables; programs without the general representation
lower pipes as before.
The fallback stream.Writable's on() and once() overloads for drain,
finish, close and error return the stream again, so listener chains such
as out.on("finish", listener).end(data) type-check.
Adds corpus 6468.
An untyped child-stream 'data' listener takes strings when a setEncoding
call on the same stream precedes it. A const alias of an access path
(`const out = child.stdout`) now resolves to that path, so a setEncoding
through one alias and a listener through another, or through the stream
itself, agree. A setEncoding on another receiver that can hold the same
stream (a let copy, a parameter, a call result) leaves the state unclear
and reports the existing SC1090. Receivers that name different members
of one root, or two const roots each initialized by a call or new, are
treated as different streams.
Construction from checked-dynamic code (`new F(options)` with F an
untyped value holding a ctor-less subclass of a stream class or
EventEmitter) dispatched to the class's constructor and completed the
arguments against the inherited signature, which takes none here, so the
options were dropped. The checked construction dispatch now reports
those arguments at run time with the message typed construction reports
at compile time, located at the class: a fixed argument count refuses at
the call, and a spread call refuses when its argument list is not empty.
- Promise.race admits a Promise<never> entry (the deadline idiom) under
any result type: it can only reject, so its fulfillment adapter traps.
A Promise<never> executor's one-parameter resolve validates against the
void inner.
- Array(n).fill(v) and new Array(n).fill(v), which the checker types as
any[], take v's type.
- Object.entries/values over an index-signature record cast to a narrower
value type check each value into the element type instead of refusing.
- dns.LookupAllOptions and the lookup-function option union that holds it
(LookupOneOptions | LookupAllOptions | number | undefined) ride
checked-dynamic storage, and dns.lookup passes a cast of a
checked-dynamic options value through unchanged.
- npm packages: a CommonJS module that names its export object through a
variable (`var ip = exports; ip.isPrivate = ...`, the ip package) is
respelled through `exports`, and tsc output that adds a default-export
object (`exports.default = { metrics: metrics_api_1.metrics }`, the
@opentelemetry/api index) keeps its named exports linked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Node answers a Unix-socket server's address() with its path. Programs
that call listen() with a string argument now get a Unix-aware
address(): typed `string | AddressInfo | null` (the @types/node
declaration), it answers the path; typed as the AddressInfo record alone,
which cannot hold the path, it throws for such a server, as does the
composed address().port read. Every other program keeps the record-only
lowering, so their output is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@types/node types the transform/flush completion callback's data as any
and a child stream 'data' chunk as any, and ChildProcess accepts
(code: number, signal: string) exit and close listeners through its
catch-all overload. None of these compiled: the any-typed completion
callback crashed the LLVM emitter, and the child listeners were refused.
The completion callback's data parameter is typed Buffer | string |
undefined while the option callback lowers. Child stream 'data' listeners
with an unknown or any chunk box it as a checked-dynamic value, taking
strings when a setEncoding statement on the same stream precedes the
listener and Buffers when the function never calls setEncoding on it;
other placements are refused. Exit and close listener parameters declared
number or string bind as checked-dynamic values, so the body sees the
null Node passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>