Commit Graph
1503 Commits
Author SHA1 Message Date
Chris Tate 5c39821726 Merge main 2026-10-17 02:30:43 -05:00
Chris Tate 6fa1f8ce01 Lower util.promisify(stream.finished) and util.promisify(stream.pipeline) (#1177) 2026-10-17 02:19:12 -05:00
Chris Tate 3e83b4a5c7 Lower util.promisify(stream.finished) and util.promisify(stream.pipeline)
Node's util.promisify returns the custom promisified forms of
stream.finished and stream.pipeline, which are stream/promises' functions
of the same name. A const binding of promisify over the node:stream named
imports or namespace members now registers that stream/promises member
as the projection, so calls through the binding lower as the
stream/promises call and accept the stream arguments and options it
accepts.

The fallback declarations gain the matching util.promisify overloads.
pipeline's overload comes first: its (...streams: unknown[]) => unknown
type is assignable to every other target's parameter type.
2026-10-17 01:51:57 -05:00
Chris Tate f7ad1f96a4 Lower Array.fromAsync over arrays
The ESNext array lib is now part of the type world, and Array.fromAsync(source,
mapFn?) compiles when the source is an array of values or of promises. Each
call lowers to an async helper with the spec's loop: elements are awaited
through the async-from-sync iterator, with the same microtask turns as a
`for await` loop over an array, and a mapping function's result is awaited
before it is stored. A throwing or rejecting mapping function and a rejected
element close the iterator before the returned promise rejects, on the turn
Node rejects it. Async generator sources, a thisArg, and arrays that mix
values and promises are refused.
2026-10-17 01:47:40 -05:00
Chris Tate 6d4843ea99 Dispose Disposable-typed values and expose SuppressedError's error and suppressed
`using` and `await using` now accept object literals and other values typed
Disposable, AsyncDisposable or unknown. The declaration reads the value's
[Symbol.dispose] method ([Symbol.asyncDispose] first for `await using`) and
throws Node's TypeErrors for values that are not objects and for missing or
non-callable methods; null and undefined dispose nothing. Scope exit calls the
method with the value as `this`, and `await using` awaits the result of an
async method but not of the sync fallback. A class instance reached through
such a value throws an SC2020 error, because its dynamic view has no methods.

SuppressedError is its own error class: a failing disposal over a pending
error throws one whose `error` is the disposal error and whose `suppressed` is
the pending error, both non-enumerable as in Node. `new SuppressedError(error,
suppressed, message?)` and `instanceof SuppressedError` compile, and
`extends SuppressedError` is refused.
2026-10-16 22:36:18 -05:00
Chris Tate d7a5568726 Revert "Lower child_process.exec's callback forms" (#1161) 2026-10-16 22:05:36 -05:00
Chris Tate 23e92e4cff Match libc++'s copy errors and macOS's PATH_MAX in the fs.cp tests
std::filesystem::copy_file reports a destination that exists but is not
a regular file as invalid_argument in libstdc++ and as not_supported in
libc++, which Node's macOS builds use. cpSync on Apple platforms now
throws ENOTSUP there, as Node does, and the fs errno names include
ENOTSUP.

Corpus 5571 copies a 400-level directory chain instead of 1500 levels,
so its deepest path stays under macOS's 1024-byte PATH_MAX.
2026-10-16 21:53:32 -05:00
Chris Tate 85690cf2f4 Order only timers in the promisified timers test
5628-promisify-timers.ts compared a 1 ms timer with an immediate armed
at the same time. Their order depends on how much time has passed when
the loop reaches its timers phase: once the delay has elapsed, the timer
fires before the check phase. Node prints either order under load. The
test now orders three timers armed together (1, 10 and 30 ms), which
settle in deadline order. The node-types fixture does the same with two
timers.
2026-10-16 21:36:39 -05:00
Chris Tate db94b2d257 Add fs.cpSync, fs.cp and fs/promises cp
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.
2026-10-16 21:06:22 -05:00
Chris Tate d8c2b0f96d Lower child_process.exec's callback forms (#1161) 2026-10-16 20:57:36 -05:00
Chris Tate 6f4334be00 Accept Map arguments in the Set composition methods (#1164) 2026-10-16 20:51:52 -05:00
Chris Tate a5bfafe590 Parse IPv4 hosts in special-scheme URLs like WHATWG URL (#1163) 2026-10-16 20:49:31 -05:00
Chris Tate d53c1b8ba0 Merge main 2026-10-16 20:30:22 -05:00
Chris Tate 58842aa39b Accept Map arguments in the Set composition methods
union, intersection, difference, symmetricDifference, isSubsetOf,
isSupersetOf and isDisjointFrom now compile with a Map argument whose key type
is the Set's element type, as well as with a Set. A Map's set record reads its
keys, and its size, has and keys are the builtins, so the helper loops walk
and test the Map's keys in the order the spec gives. Other set-like arguments
are still refused.
2026-10-16 20:28:04 -05:00
Chris Tate b18a28bdef Parse IPv4 hosts in special-scheme URLs like WHATWG URL
The URL host parser kept a host whose last label is a number as a
domain, so new URL("http://127.1").hostname was "127.1" where Node
answers 127.0.0.1, and hosts such as 1.2.3.256, 0x100000000 and
1.2.3.4.5 parsed where Node throws Invalid URL.

A special-scheme host (file: included) that ends in a number now goes
through the WHATWG IPv4 parser: up to four labels after one trailing
empty label, each a decimal, 0x hexadecimal or leading-zero octal
number, every label but the last at most 255 and the last below
256^(5 - labels). The address serializes as dotted decimal, and any
failure makes the URL invalid. The constructor, URL.parse, URL.canParse,
the host and hostname setters and domainToASCII/domainToUnicode share
the parser. Non-special schemes keep their opaque host as written.
Corpus 5860 covers each form.
2026-10-16 20:27:21 -05:00
Chris Tate 9681454556 Lower util.promisify(setTimeout) and util.promisify(setImmediate)
Node's util.promisify returns the custom promisified form of setTimeout
and setImmediate, which is timers/promises' function of the same name.
A const binding of promisify over the global functions, the node:timers
named imports or the node:timers namespace members now registers that
timers/promises member as the projection, so calls through the binding
lower as the timers/promises call: they fulfill with the given value
(undefined without one) after the delay or in the check phase.

The fallback declarations gain the matching util.promisify overloads.
2026-10-16 20:22:19 -05:00
Chris Tate 2ab9055d76 Accept readline's history and completer options and add the history members (#1085) 2026-10-16 20:17:26 -05:00
Chris Tate cef618b553 Lower child_process.exec's callback forms
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.
2026-10-16 20:14:33 -05:00
Chris Tate cb10fc36fc Link the dyn promise unit when a boxed record holds an async function (#1146) 2026-10-16 20:11:05 -05:00
Chris Tate 85439ae670 Merge main 2026-10-16 19:54:32 -05:00
Chris Tate 85a8381475 Follow a boxed function's result type when linking the dyn promise unit
A boxed function converts its result to dyn, which boxes any promise the
result type holds. The feature scan checked only for a result that is
itself a promise, so boxing a function returning `Promise<T> | undefined`,
a record with an async function field, or an array of async functions
still failed to link with an undefined scr_dyn_new_promise_adapting. The
scan now walks the result type like the other boxed types.

Corpus program 5812 and the runtime-features unit test cover these
results.
2026-10-16 19:46:51 -05:00
Chris Tate 451924b3d0 Lower statSync's throwIfNoEntry option without building the error (#1136) 2026-10-16 19:38:44 -05:00
Chris Tate aef767791b Refuse fs paths with a null byte as Node does (#1144) 2026-10-16 19:33:14 -05:00
Chris Tate 2e52dcd35c Add Readable.from over Sets, iterators and class iterables (#1074) 2026-10-16 19:30:21 -05:00
Chris Tate 98f784e970 Speculate integers only where the static range proof gives up (#1157) 2026-10-16 19:29:53 -05:00
Chris Tate e0692c81c8 Cache constant-key dyn reads per site and follow repeated key sequences in JSON.parse (#1154) 2026-10-16 19:23:30 -05:00
Chris Tate a19fa602c9 Return primitive receivers from Object.setPrototypeOf (#1145) 2026-10-16 19:11:45 -05:00
Chris Tate 5a091f050f Run the zlib one-shot forms on the stream engine (#1139) 2026-10-16 19:11:18 -05:00
Chris Tate 6520cd8223 Call a helper per case in the JavaScript null-byte corpus program
The cases were [label, run] tuples in an array, which JavaScript inference
types as (string | (() => void))[]. console.log(label, ...) then received a
union that includes a function value, so coverage counted two statements as
runtime fences. Each case now calls attempt(label, run) directly; the
output is unchanged.
2026-10-16 19:10:21 -05:00
Chris Tate 26cdf9cc1d Merge main 2026-10-16 19:09:58 -05:00
Chris Tate b93b368625 Propagate errors thrown while reading an iterable's iterator method
scr_dyn_from_iterator answered kind 0 (not iterable) when reading
Symbol.asyncIterator or Symbol.iterator threw, so Readable.from raised
ERR_INVALID_ARG_TYPE in place of the getter's error. It now answers
kind -1 with the error pending, as its declaration states, and
Readable.from throws the getter's error, as Node does. Corpus 5614 covers
a throwing Symbol.iterator getter and a throwing Symbol.asyncIterator
getter.
2026-10-16 19:07:27 -05:00
Chris Tate d92459fefe Throw Node's errors for createHmac without a key and read Hmac.copy as undefined (#1142) 2026-10-16 19:05:42 -05:00
Malte Ubl 8f5ec2d94f Report record width copies at assertions, assignments and array elements
SC6001 only looked at call arguments and typed initializers, and only at
strict field subsets: a target that adds optional properties, the empty
record type, `r as T`, a later `v = r` into a typed binding, and records
stored as elements of a typed array literal copied silently.

The scan now reports a copy whenever the property sets differ, covers
those sites, and reports a class instance that reaches a plain record
type which does not keep its identity (the remaining boundary: interfaces
of only generic methods). Every corpus program still reports no
divergence.
2026-10-16 14:02:02 -07:00
Malte Ubl 6c9c39b3ab Keep class instance identity through structural record views
A class instance passed, stored, returned or asserted as an anonymous
object type or an interface it does not `implements` was copied into a
native record, so writes, identity checks and inspection through the view
missed the instance (`function add(o: { count: number }, n: number)
{ o.count += n }` left a Counter at 0).

ClassProtocols now scans flow sites (arguments, typed initializers and
fields, defaults, returns, arrow bodies, yields, assignments, array
elements, property values, assertions through `unknown`, and annotated
callback parameters) and marks every program object type a class
instance flows into, then propagates the mark along flows between
structural types. Marked types are class-backed protocols: their values
are checked views that keep the original object, like implemented
interfaces already were. Only sites whose source carries identity query
the checker for their target, and a checker panic skips the site.
Assertions read or written member-wise in place keep their direct field
access, and `typeof C` is not an instance.

An assertion of a class instance to such a view boxes it like an
implicit flow, and `view.n++` on a number member lowers to the checked
expanded form.
2026-10-16 14:02:02 -07:00
Malte Ubl 779f255787 Duplicate only loops whose integers multiply or take remainders
A loop whose speculated integers only count and compare gains nothing measurable from a second copy but grows the binary, so a loop is duplicated only when a speculated local or invariant is a multiplication, division or remainder operand, or a speculated write multiplies or takes a remainder. Sum-overflow corpus loops gain a remainder use so they keep exercising their bails.
2026-10-16 11:40:53 -07:00
Malte Ubl 16cac9a47c Speculate only what the static range proof leaves unproven
Locals whose reads the integer range analysis already proves are never shadowed or checked, and a loop with nothing unproven is not duplicated. The unproven targets that remain gain speculative leaves: charCodeAt results, array elements and record or class fields are read once before any check (they have no effect, so a failed check may restart the statement) and checked like any other integer. Module numbers written only by top-level code act as entry-checked invariants in loops of any function, which covers divisors computed at run time.
2026-10-16 11:40:53 -07:00
Malte Ubl 1ad7293e44 Speculate only integer-consumed locals and defer unobserved globals
A loop-written local now gets an integer shadow only when the loop reads it as an integer (multiplication, remainder, bitwise operators, comparisons, indexes) and every local its writes read is speculated, already an integer, or loop-invariant: accumulators that are only added to keep their cheap double add, and values computed from doubles no longer bail on every entry. Statically proven subexpressions use the ordinary integer emission, and sums with one statically bounded operand check the other operand before adding. In loops that run no other code and leave only through their condition or break, module globals are written back on leaving the fast copy instead of on every write.
2026-10-16 11:40:52 -07:00
Malte Ubl 3be474109a Run integer loops speculatively in checked i64 arithmetic
Innermost loops whose number locals are only written by statement-level integer arithmetic run as two copies. The fast copy keeps those locals (and loop-invariant number inputs) in i64 shadow slots holding safe integers and computes the writes with checked arithmetic, also storing the exact double so exits and calls need no write-back. A failed check (overflow past 2^53 - 1, -0, a fractional or non-finite leaf, a zero divisor) re-executes the same statement in the ordinary double copy. Module-level numbers written only by their file's top-level code qualify too.
2026-10-16 11:40:41 -07:00
Malte Ubl a6c6b63cca Follow repeated key sequences when JSON.parse builds objects
Records in a parsed array usually spell the same keys in the same order.
Like V8's JSON parser following a map's expected transition, the dyn
parser now remembers the key sequences it has built in the current parse
as a small tree rooted at the empty object. When the next member's raw
text is exactly `"<expected key>"`, it needs no string scan, no duplicate
search and no key copy, and the entry shares the tree's interned key.

The object holds exactly the keys on its path, and a transition is
recorded only when a put appended a key that was not yet present and
needed no unescaping, so a byte match proves both the decoded key and its
novelty. Duplicates, escaped keys, keys beyond four candidate transitions
and parses past 4096 transitions finish the object on the ordinary path.
The tree is freed at the end of scr_json_parse and scr_json_parse_schema.

The recycled-key check in scr_dyn_obj_put and the true/false/null match
also use the inline scr_key_equal instead of a libc memcmp call, which
took 2.5% of the 31-json dynamic variant.

Sandbox, interleaved medians of 15 runs against the branch base (with the
read-site caches): 31-json as published 2.33 s -> 2.09 s, its
`checksum = checksum + parsed[i].score` variant 2.43 s -> 2.22 s, the
12-key constant-read benchmark 1.68 s -> 1.25 s. Peak RSS is unchanged.
2026-10-16 10:14:11 -07:00
Malte Ubl 455ecc94c5 Cache constant-key reads on checked-dynamic objects per site
Every `value.name` on an any/unknown object went through the shared key
helper's kind tests, scr_dyn_obj_read, scr_dyn_obj_read_receiver and
scr_dyn_find_entry, whose one global hint table is keyed by key length and
first byte. Keys that collide there (k05/k11, or any same-length keys with
the same first letter) evicted each other and fell back to a linear scan.

A constant-key read on an ordinary object now calls scr_dyn_obj_read_ic
with a zero-initialized per-site ScrDynReadIc, thread-local wherever
program globals are. The site remembers the entry position where it last
found the key as an own data property and holds a reference to the
interned key bytes stored there. Objects that spell a key alike share
those bytes, so a hit is one pointer compare at one position; long keys
and share-table collisions compare bytes there instead. Accessors, the
builtin Object.prototype and other receiver kinds keep the previous path.
Sites release their keys with the thread's runtime state (scr_atexit),
and the RC audit lane never fills one, so its live-key count stays exact.

Sandbox, interleaved medians against the branch base: 20k parsed 12-key
records with three constant-key reads each, 300 rounds, 1.66 s -> 1.16 s;
31-json's dynamic loop variant 2.37 s -> 2.35 s (its reads are bound by
the miss on each record's entry array). Peak RSS is unchanged.
2026-10-16 10:14:11 -07:00
Chris Tate 9ef649577c Link the dyn promise unit when a boxed record holds an async function
Boxing a value into unknown converts promises through scr_async_dyn.c.
The runtime feature scan linked that unit only when the boxed value was
itself a promise or a function returning one, so boxing a record with an
async function field, an array of such records, or a union with such an
arm failed to link with an undefined scr_dyn_new_promise_adapting. The
scan now follows record fields, array, Set and Map elements and union
arms.

Corpus program 5812 boxes these values and compares the output with
Node.
2026-10-16 11:05:12 -05:00
Chris Tate 06bcefae17 Return primitive receivers from Object.setPrototypeOf
Object.setPrototypeOf now follows the spec order. A null or undefined
receiver throws "Object.setPrototypeOf called on null or undefined",
then the prototype is checked, and a boolean, number, string, bigint or
symbol receiver is returned unchanged. Previously any receiver that was
not an object or array threw "Object prototype may only be an Object or
null".
2026-10-16 10:49:30 -05:00
Chris Tate 945017d6b5 Refuse fs paths with a null byte as Node does
A string path holding a null byte reached the system call, which saw the
path up to that byte: readFileSync("a\0b") read "a", writeFileSync wrote
it, and existsSync tested it. Node refuses such a path before any file
system access with an ERR_INVALID_ARG_VALUE TypeError naming the argument
("The argument 'path' must be a string, Uint8Array, or URL without null
bytes. Received '...'").

The fs runtime entry points now check their string paths first (each
with Node's argument name: path, prefix, oldPath/newPath, src/dest,
existingPath/newPath, target), so the sync forms throw, the promise forms
reject, and the callback form of rename throws before it queues the
operation. The Received text is util.inspect's quoted string, cut at 128
characters as Node cuts it; the JavaScript lane's validation ladders
share the same rendering, which now picks backticks for a string with
both quote kinds and escapes control characters as inspect does.
existsSync answers false.

Corpus fs-null-byte-paths.ts and fs-null-byte-paths-js.cjs cover the
sync, callback and promise forms in both lanes.
2026-10-16 10:35:19 -05:00
Chris Tate de08ab1920 v0.3.5 2026-10-16 10:33:14 -05:00
Chris Tate 97fafc4215 Read never-reassigned captured, module-level and field bindings as values (#1132) 2026-10-16 10:25:17 -05:00
Chris Tate 7592372b15 Support socket.address(), resetAndDestroy() and dropMaxConnection in net (#1140) 2026-10-16 10:23:46 -05:00
Chris Tate e1f1e235c2 Append to string array elements and o.f = o.f + x fields in place (#1141) 2026-10-16 10:23:23 -05:00
Chris Tate 2b02f53464 Throw Node's errors for createHmac without a key and read Hmac.copy as undefined
createHmac(algorithm) with no key now compiles and throws Node's
ERR_INVALID_ARG_TYPE ("Received undefined") from the key check, which
runs before the digest check as in Node; it used to be refused at
compile time. Reading copy on an Hmac, which has no copy() method,
answers undefined, so `typeof mac.copy` feature checks see what Node
reports. TypeScript already rejects that read.

Corpus 5764 covers createHmac with missing, undefined, null and number
keys, an unknown digest with and without a key, and the copy read.
2026-10-16 10:22:11 -05:00
Chris Tate fe86c85902 Support socket.address(), resetAndDestroy() and dropMaxConnection in net
socket.address(), socket.resetAndDestroy() and server.dropMaxConnection
were refused at compile time, and address() and resetAndDestroy() also
through untyped socket handles. They now match Node:

- address() answers { address, family, port } while the socket has a
  local address, and {} while it connects or once it is destroyed. The
  declared AddressInfo | {} result is a checked-dynamic value, so a type
  assertion to AddressInfo narrows it.
- resetAndDestroy() closes the connection with an RST (SO_LINGER with a
  zero timeout), so the peer reads ECONNRESET, and 'close' reports
  hadError false. A connecting socket resets once it connects: the reset
  is a once('connect') listener, so it runs at that place among the
  'connect' listeners. A destroyed socket is left alone, a TLS socket
  throws ERR_INVALID_HANDLE_TYPE, and the call answers the socket.
- dropMaxConnection is a stored value (undefined until set), since Node
  reads it only in cluster mode.

Corpus 5657 (typed) and 5658 (untyped handles and the TLS throw) and the
node-types fixture net-reset-address.ts cover each member.
2026-10-16 10:01:22 -05:00
Chris Tate 7942054337 Cut per-value overhead of generators, user iterators, and awaits (#1127) 2026-10-16 09:40:38 -05:00