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.
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>
http.request, https.request and get now accept options the compiler
cannot see as an object literal: a RequestOptions variable, a literal
that spreads one, or a literal that sets socketPath. The runtime reads
the record in the ClientRequest constructor's order with Node's errors;
documented options it does not implement throw the runtime fence when
they carry a value. https adds rejectUnauthorized, ca and servername.
socketPath dials a Unix domain socket in the net unit; the agent keys
its sockets by the path as Node's getName does, and the Host header
defaults to localhost. server.listen(path[, cb]) binds a Unix socket and
removes the socket file when the server closes; a numeric string is
still a port. Errors name the path ("connect ENOENT /path", "listen
EADDRINUSE: address already in use /path"). Windows reports ENOTSUP.
A TypeScript object literal with no contextual type that spreads a
checked-dynamic value maps to the checked dynamic, so
`const options = { ...location, path, timeout }` compiles.
A socket's 'lookup' event reaches its listeners with (err, address,
family, host) for each address a host name resolves to, or once with
the lookup error, before 'connect'. Only programs that listen for
'lookup' install the lookup records; elsewhere the hook sites are a NULL
check.
server.close((err) => ...) runs the callback with undefined at 'close',
or with ERR_SERVER_NOT_RUNNING when the server was not listening; a
server that never listened now emits 'close'. IncomingMessage 'data'
listeners may take string chunks after setEncoding, and res.end()
accepts a string-or-undefined chunk.
fetch's BodyInit and HeadersInit unions, as RequestInit-derived option
types spell them under @types/node, map to the checked dynamic, and a
URLSearchParams body is sent form-encoded, as by Node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
child.stdout.pipe(), stream events and writes beyond write(string) on a
NodeJS stream interface slot, and pipes into a child's stdin now lower
the program with the general representation instead of fencing. The
fallback ChildStdin is a NodeJS.WritableStream, and the coverage
remainder relowers its unreached code the same way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bundled npm code (glob, yazl, from2, multistream) subclasses Node's
classes with constructor functions: util.inherits(Ctor, EventEmitter) and
EventEmitter.call(this). util.inherits received an identity token and
threw ERR_INVALID_ARG_TYPE, EventEmitter.call(this) and
EventEmitter.prototype were refused, and `this instanceof Ctor` against
a function was refused.
A new runtime unit (scr_dyn_builtin_class.c, linked by the new
dynBuiltinClass feature) provides EventEmitter, the legacy Stream and
the stream classes as untyped constructor functions with Node's
prototype chain. EventEmitter's prototype ports lib/events.js over the
object's own _events, _eventsCount and _maxListeners properties; Stream
adds the legacy pipe. util.inherits, Base.call/apply(this, ...),
Base.prototype, instanceof on untyped values and JavaScript value reads
of these classes use them. Constructing over a stream class throws
SC2020 where it runs. instanceof against a compiled constructor function
compares its prototype with the value's prototype chain.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A typed rest-parameter function passed where a function with fixed parameters is expected (`log(...m: string[])` into `(m: string) => void`) was refused. The function adapter now packs the slot's remaining arguments into the rest array, and the value check admits the function wherever that adapter fits. A nested function declaration with a typed rest parameter becomes a value with its own typed-rest signature, converted at each use.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main split lower-exprs.ts into expression families; the typeof and __dirname changes move to expressions/typeof.ts and expressions/identifiers.ts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A thrown array reached its catch binding as a type-erased native array:
`console.log(e)` printed `{}` where Node prints the array, `String(e)`
gave "[object Object]", `Array.isArray(e)` was false and a typed view
such as `(e as number[]).length` threw a TypeError. Thrown records
already crossed the catch boundary as checked-dynamic values that keep
their identity; arrays, and unions over records and arrays, now take the
same conversion, so the catch binding is the thrown object.
node:test printed a failing test's thrown non-Error value with String():
`throw { code: 42 }` reported `[object Object]` where Node prints the
value as util.inspect renders it. The failing-tests section now inspects
thrown numbers, booleans and checked-dynamic values (records, arrays,
null and undefined); a thrown string is quoted as before. A thrown class
instance that is not an Error still prints `[object Object]` (the
limitations page says so).
Adds corpus 6341, extends the thrown-values node:test fixture, and
records 6341's cost.
Resolve the conflicts with main's process.features value, the WebCrypto dynamic types, the NODE_COMPAT_VERSION import and the symbol.declarations API. Renumber this branch's corpus programs to 6420-6429, write the new limitations partial one sentence per line, and regenerate the compatibility outputs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A .ts or .tsx file with import syntax is an ES module under Node's strip-types rule, which has no __dirname or __filename. TypeScript projects are commonly shipped as CommonJS bundles where both exist, and @types/node declares them for every file. scriptc now compiles them in such files to the module's source path, the same compile-time constants as in CommonJS and the same strings as import.meta.dirname and import.meta.filename. JavaScript ES modules keep the fence. The limitations page documents the difference.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Relational string operators (`<`, `<=`, `>`, `>=`) compared the UTF-8
bytes, which is code-point order. Node compares UTF-16 code units, so the
results differed for characters above U+FFFF against U+E000..U+FFFF and
for lone surrogates against surrogate pairs. The default `sort()`
comparator, `util.inspect` with `sorted: true` and dynamic comparisons
already used UTF-16 order; the default comparator of a dynamic array's
`sort()` did not.
Typed comparisons and the dynamic array sort now use `scr_str_cmp_u16`,
and the IR's `strCmp` node no longer has a `utf16` flag. The two orders
agree unless both of the first differing bytes are at least 0xED, so:
- the inline wrapper decides differing first words, or last words of
equal lengths up to 16 bytes, in byte order when neither word has a
byte of at least 0xED (a SWAR test). Other differing words, and the
first bytes of shorter strings, decide by their first differing byte:
in byte order when one of the two is below 0xED, and by plane when both
are lead bytes from 0xEE to 0xF4 (a supplementary character orders
below U+E000..U+FFFF);
- otherwise a proven-ASCII string on either side compares with `memcmp`
(`scr_str_cmp`), and other pairs call `scr_str_cmp_u16_bytes`. It finds
the first differing byte with words up to byte 32, then with 32-byte
SSE2 or NEON blocks where the target has them. The call is marked as
only reading its arguments, so repeated comparisons of one pair merge.
- `scr_str_cmp_u16` itself takes the same `memcmp` path for proven-ASCII
strings.
The native compiler sorts record fields with `<` and checks the order
with `sort()`, so it reported SC9001 for 5888-inspect-sorted.ts, whose
object has the keys "\u{1F600}" and "\uffff". 5888 runs in the
native-frontend self-host check. Adds corpus 6023.
node:test changes, matching Node 24.22:
- mock.fn() and t.mock.fn() without an original return undefined and
record the arguments of each call, whatever the callers pass. The
original and the initial implementation are a function that returns
undefined, so restore() brings that function back.
- mock.fn<F>() with an explicit function type records F's parameters
when F's return type is void or includes undefined. A return type
without undefined, such as mock.fn<() => number>(), throws SC2020.
mock.fn()'s default type, (...args: any[]) => undefined, maps to the
checked-dynamic rest ABI used for program-declared (...args: unknown[])
functions: the calls log records the argument array, and the mock's
wrapper spreads it into the implementation.
A function whose fixed parameters are all unknown or any, or that has
none, now converts to a checked-dynamic rest signature without fixed
parameters, such as (...args: unknown[]) => void: each call passes the
arguments in order, undefined for the ones the caller left out. This is
how () => {} reaches mockImplementation and mockImplementationOnce, and
it applies to any such slot in TypeScript programs, which threw SC1090
before.
The mock-fn-default fixture and corpus program 5816 compare with Node,
plain and with SCRIPTC_SAN=1.
jose 6.2.12 now compiles natively through its root entry (`import { jwtVerify,
errors } from "jose"`), from TypeScript and JavaScript, with chained SignJWT
builders and string durations:
- A discarded `a && b;` / `a || b;` statement whose value form fails on its own
result type (jose's secs(): `(!m || m[4] && m[1]) && invalid()`) lowers as
`if (a) b;` / `if (!a) b;`. The value form still lowers first, so statements
that compiled before are unchanged.
- In JavaScript, a number-keyed read on a `T[] | null | undefined` value outside
an optional chain (`m[2]` after a statement ruled `m` non-null) reads the
array arm like any element read: a group that did not participate is
undefined instead of a trap.
- A JavaScript fluent method whose returns all end in `this` (including comma
sequences) narrows a used call result back to the receiver's subclass. A
receiver lowered at an ancestor of the class a call needs is bridged with a
verified downcast instead of an upcast the validator rejected (the
"upcast JWTClaimsBuilder does not extend SignJWT" internal error).
- `typeof navigator` is "object" and `navigator.userAgent` is "Node.js/24", as
in Node 24, for the free name in JavaScript and under @types/node. Both fold
to literals; other members refuse by name.
- TypeScript object destructuring of an `any` result of a JavaScript function
(jose's jwtVerify) reads its members like the member reads that already
compiled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>