Commit Graph
2626 Commits
Author SHA1 Message Date
Chris Tate 7bb7cfb6a3 Merge main 2026-10-15 17:36:41 -05:00
Chris Tate 1251a04fdb Merge main 2026-10-15 17:34:50 -05:00
Chris Tate 1be58648bc Merge main 2026-10-15 17:31:00 -05:00
Chris Tate a128918c50 Support dns.lookup's Node forms, lookupService, getServers and the result order (#1053) 2026-10-15 17:26:47 -05:00
Chris Tate afad516b91 Add net socket address fields, write and end callbacks, and server connection limits (#1052) 2026-10-15 17:09:27 -05:00
Chris Tate 404af8fd6e Partition large wasm32 programs like native ones (#1040) 2026-10-15 17:03:31 -05:00
Chris Tate 55a660f0e2 Add dns.lookup's Node forms, lookupService, getServers and the default result order
- dns.lookup(hostname, callback), lookup(hostname, family, callback)
  and lookup(hostname, options, callback) with family (0/4/6, 'IPv4',
  'IPv6'), all, order and verbatim, validated with Node's messages.
  getaddrinfo answers any family, ordered verbatim or one family first;
  literal addresses answer on the next tick, an empty hostname gives
  Node's null answer and DEP0118 warning, and failures carry code,
  errno, syscall and hostname.
- dns.lookupService(address, port, callback) checks the address and
  port as Node does and answers getnameinfo's host and service.
- dns.getServers() lists the nameservers in /etc/resolv.conf, and
  getDefaultResultOrder/setDefaultResultOrder keep the default order
  ('verbatim').
- A typed callback receives "" and NaN (or []) after a failure, where
  Node passes undefined; the limitations page says so.
Corpus 5648-5650 cover each case; the options-record diagnostics no longer
list the all and hints options as refused.
2026-10-15 16:59:20 -05:00
Chris Tate bfb0ca8ffc Print the net corpus's server and client lines in a fixed order
The server's 'connection' and the client's 'connect' race, and macOS runs
them in the other order from Linux. 5644 now prints both sides once both
have run, and 5645 keeps the server's and the client's events in separate
lists printed one after the other.
2026-10-15 16:42:32 -05:00
Chris Tate f65a6fad14 Merge main 2026-10-15 16:34:46 -05:00
Chris Tate 8e5577b8f1 Merge main 2026-10-15 16:32:49 -05:00
Chris Tate aa5e626b06 Merge main 2026-10-15 16:32:19 -05:00
Chris Tate a5a3588d3d Add net socket address fields, write/end/setTimeout callbacks, close(hadError) and server connection limits
- socket.remotePort, remoteFamily, localAddress, localPort, localFamily,
  bytesRead, readyState, connecting and pending. The peer's fields are
  cached at first read as remoteAddress is (undefined while
  connecting), and the local ones are undefined while connecting and
  after close.
- write(data, cb) runs cb with null once the data is flushed, and
  end(cb) / end(data, cb) once the socket finishes.
- 'close' listeners receive hadError, 'ready' fires after 'connect',
  and 'data' listeners may take the string chunks setEncoding gives.
- setTimeout(ms, cb) adds cb as a once('timeout') listener (0 removes
  it), and setTimeout throws ERR_OUT_OF_RANGE for a negative, NaN or
  infinite msecs where it disabled the timer before. setKeepAlive
  chains.
- server.maxConnections closes connections past the cap and emits
  'drop' with their addresses; server.getConnections(cb) answers the
  count on the next tick.
- net.isIP, isIPv4 and isIPv6 follow Node's address patterns.
- A callback adapted to a void return keeps the identity of the
  function it wraps, so removing it by that function finds it.
Corpus 5644-5647 cover each case.
2026-10-15 15:59:17 -05:00
Chris Tate a8586d2c8d Merge main 2026-10-15 15:31:10 -05:00
Chris Tate 05eb35aacc Drain process.nextTick before promise jobs after every callback
After each setImmediate callback the loop now runs the process.nextTick
queue to exhaustion before promise jobs and queueMicrotask callbacks,
and repeats until both are empty, as Node does after every macrotask.
Before, only promise jobs ran between immediates, so a tick queued by
one immediate ran after the next immediate.

The timer phase and the I/O dispatch stations also return to the
checkpoint when only ticks are pending, and the dispatch hooks yield
between event batches for pending ticks as they do for promise jobs.
2026-10-15 14:57:16 -05:00
Chris Tate 1ffbb28255 Follow Node's getOptions in the fs option checks
The fs validation ladder and the callback dispatch check their options
argument as Node's getOptions does. null, undefined and functions take
the defaults, and a string is the encoding. Numbers, booleans, bigints
and symbols throw ERR_INVALID_ARG_TYPE ('The "options" argument must be
one of type string or object'). assertEncoding lets 'buffer' through;
before, every 'buffer' encoding threw ERR_INVALID_ARG_VALUE.

With 'buffer':
- readFile and readFileSync read the file first, so a missing file
  reports ENOENT. Then buffer.toString('buffer') throws
  ERR_UNKNOWN_ENCODING; an empty file answers "".
- fs.mkdtemp's callback receives the path as a Buffer.
- mkdtempSync, opendirSync, createReadStream and createWriteStream
  throw the fence error.

fs.mkdtemp's callback form now checks its encoding. It also returns its
path in the requested encoding: re-encoded text for names other than
utf8, as the binding does.

Corpus 5545 covers these forms against Node 24.22.
2026-10-15 14:54:07 -05:00
Chris Tate d0cff5e6f3 Run the npm case fixtures natively and fix what they found
The npm case fixtures lost their only native test with the native-only
compilation change. tests/harness/npm.test.ts runs every case again with the
default static npm compile and compares it with Node. A KNOWN table records
the cases the compile refuses (with the diagnostic code), the cases whose
run reaches a deferred refusal (the code in the program's output), and the
two that still diverge, which run as expected failures. The suite joins the
case-sharded CI corpus files.

Fixes from the run:

- A package imported only by subpath (`pkg/env.js` from a package with no
  "main", "exports" or index file) was refused as having no runtime entry.
  Eligibility now resolves the imported specifier when the root has none.
- diagnostics_channel: a throwing subscriber stopped publish, and a
  throwing bindStore transform stopped runStores. Node runs the remaining
  subscribers, skips that store, and reports each error as uncaught on a
  next tick; the runtime now does the same.
- A '#' import that the package's "imports" field maps to another package
  failed at run time with Node's "not defined" error, which Node does not
  raise. It is now a compile-time SC1010 refusal.
- An uncaught runtime refusal whose code is an SC code now names the code.
- Mapping a type in the optional array-element analysis could lower an
  unsupported local class and let its PoisonError escape as a compiler
  crash; the analysis now treats that element ABI as unknown, so the
  program fails with the recorded diagnostic.
- A lexical module binding initialized from an unchecked array element
  holding a function failed validation (SC9001): its widened storage was a
  union, which a TDZ guard cannot hold. Such bindings now keep checked-value
  storage and their guard.
- A dynamically reached ArrayBuffer had no constructor property.
2026-10-15 14:15:33 -05:00
Chris Tate 93371f83c9 Merge main 2026-10-15 13:42:34 -05:00
Chris Tate 9aab9af547 Fix wasm32 array lengths past 2^31 and pending top-level await exit codes
The backend converted size_t lengths and indexes to numbers with sitofp. On
wasm32 size_t is 32 bits, and an array length reaches 2^32 - 1, so a sparse
array of length 4294967295 read -1. sizeToDouble picks uitofp for a 32-bit
size_t and keeps sitofp for 64 bits, whose sign bit a length never reaches,
so native output is unchanged.

An unsettled top-level await always exited 13. Node exits with
`process.exitCode || 13` and runs the exit listeners first, which can set
process.exitCode again. scr_promise_finish_top_level now notes 13 only when
the program set no nonzero exit code, and the pending exit returns the
exit code after the listeners, as the other exits do.

Three WASI tests still exercised the embedded-JS fallback that native-only
compilation removed: an embedded-source fetch scan and two npm fixtures
that every target now refuses. They now check the same behavior with
static programs: fetch named only in text compiles, a global fetch call
reports SC3002, and CommonJS and ESM packages compile and match Node.
2026-10-15 13:39:03 -05:00
Chris Tate f7362fc386 Match Node's http keep-alive, header and framing behavior
- server.close() closes idle keep-alive connections, and every open
  connection closes after keepAliveTimeout + keepAliveTimeoutBuffer, so a
  closed server whose clients keep their connections no longer stays up.
  The Keep-Alive response header gives keepAliveTimeout in whole seconds
  and is omitted when it is 0.
- A client request through an Agent with neither keepAlive nor a finite
  maxSockets sends Connection: close.
- "socket hang up" and "aborted" errors carry code ECONNRESET, a client
  head over the header limit fails with HPE_HEADER_OVERFLOW, and TLS
  verification failures carry their openssl code.
- Outgoing header values keep the type they were set with: getHeader and
  getHeaders answer numbers and arrays, appendHeader turns an existing
  value into an array, and setHeader(name, string[]) writes one line per
  element.
- Repeated incoming header fields join with ", " ("; " for cookie),
  Node's single-value fields keep the first value, and set-cookie reads
  as an array under @types/node, on server requests and client responses.
  Fetch reads response headers line by line.
- Expect: 100-continue is answered with 100 Continue before 'request';
  other expectations answer 417.
- HEAD requests and 1xx/204/304 responses send no body or length header,
  and HTTP/1.0 requests without a set length get an unframed body and
  Connection: close.
- The 16 KiB maxHeaderSize applies to the request target plus header
  names and values (431 on the server).
- https.Agent TLS options are refused by name instead of being dropped,
  and server.address() lowers for @types/node's
  string | AddressInfo | null result.

Corpus 5555-5563 and the node-types http-headers-address fixture cover
each case.
2026-10-15 13:06:33 -05:00
Chris Tate 8c88a8f87f Add fs.opendirSync, fs.promises.opendir and fs.Dir (#1044) 2026-10-15 12:24:47 -05:00
Chris Tate 7bc9c78f00 Keep corpus 5407's requests within one read (#1043) 2026-10-15 12:24:25 -05:00
Chris Tate 9377dab890 Add fs.opendirSync, fs.promises.opendir and fs.Dir
opendirSync(path[, options]) and fs.promises.opendir(path[, options])
return a Dir that reads one Dirent at a time in OS order, as libuv's
uv_fs_readdir does. Dir supports path, readSync(), read(), closeSync(),
close() and for await. readSync and read answer null at the end. A
closed Dir throws ERR_DIR_CLOSED from readSync and closeSync, and
rejects with it from read and close.

for await over a Dir reads with read() until null and closes the Dir
in a finally, as Node's Dir.entries() does: normal exhaustion, break
and a throw all close it, and a loop whose body closes the Dir ends
with ERR_DIR_CLOSED.

A Dir also crosses 'unknown' slots as a native handle (Dir), so a
closure returning one can be passed to untyped JavaScript functions.
Through such a slot, path and closeSync() work, and other members throw
the "not supported yet" Error.

Open failures carry the opendir syscall and no path. options.bufferSize
is checked after the directory opens, with Node's ERR_OUT_OF_RANGE
messages. The options must be an object literal with a numeric
bufferSize and a utf8 encoding. Recursive walks, buffer encodings and
the callback forms are not lowered. TypeScript sources get a compile
diagnostic for them; in JavaScript sources, recursive walks and the
callback forms throw the fence error when the call runs.

Corpus 5544 covers both forms against Node 24.22.
2026-10-15 11:34:00 -05:00
Chris Tate b03600d328 Find fs callbacks where Node's signatures put them (#1041) 2026-10-15 11:29:40 -05:00
Chris Tate d7a7409277 Keep corpus 5407's requests within one read
The header-limit program sent heads of up to about 19 KB with long field
names. macOS loopback delivers that in more than one segment, so Node's
server rejected the head with bytes still unread and its close reset the
connection; the client socket had no 'error' listener, so Node exited on
the ECONNRESET after printing the first 431 case.

Every field is now a minimal `h:` line, which keeps each request under
7 KB, and the client socket ignores a reset so the case still ends. The
cases and their output are unchanged.
2026-10-15 11:28:15 -05:00
Chris Tate edda7b99cd Merge main 2026-10-15 11:19:22 -05:00
Chris Tate 374f9c51cd Inline string retains and dynamic value reference counting in release builds (#1030) 2026-10-15 11:18:39 -05:00
Chris Tate c2940acacc Merge main 2026-10-15 11:13:49 -05:00
Chris Tate 8f350a5441 Find fs callbacks where Node's signatures put them 2026-10-15 10:56:41 -05:00
Chris Tate b706f2cef8 Merge main 2026-10-15 10:03:55 -05:00
Chris Tate 14bbb00d79 Partition large wasm32 programs like native ones
wasm32-wasi executables always compiled their program as one LLVM module,
so the object step of a large program ran the whole O2 pipeline and code
generation on a single thread. Native targets divide a module of more than
4 MB of LLVM text into up to 64 partitions that the helper optimizes and
compiles concurrently, importing small functions across partitions for
inlining. Nothing in the helper or the link plan is specific to native
objects: partitions link as ordinary objects with wasm-ld.

nativeProgramPartitions now applies the same size rule to every target and
no longer takes the target. A WASI test emits a program whose internal
callees, state and table entries land in different partitions, at release
and dev optimization, and checks that the partitioned objects are
deterministic and link to the same program as one object.
2026-10-15 09:59:04 -05:00
Chris Tate 27db9f46e0 Give dynamic Dirent values Node's prototype and inspect form (#1037) 2026-10-15 09:56:23 -05:00
Chris Tate 29d5fafa84 Add the missing fs/promises functions and Node's fs argument checks (#1039) 2026-10-15 09:42:05 -05:00
Chris Tate 36d45882bd Close Node 24 gaps in util.types keys, HTTP header limits and unread requests (#1021) 2026-10-15 09:29:01 -05:00
Chris Tate d92b6c4ec8 Merge main 2026-10-15 09:23:58 -05:00
Chris Tate e779d212d8 Merge main 2026-10-15 09:21:42 -05:00
Chris Tate 59cdda3930 Merge main 2026-10-15 09:20:01 -05:00
Chris Tate cffc2ee71f Merge main 2026-10-15 09:18:26 -05:00
Chris Tate f325b5f116 Add the missing fs/promises functions and Node's fs argument checks 2026-10-15 09:13:46 -05:00
Chris Tate 1996d250c9 Compute remainders by powers of two without fmod (#1038) 2026-10-15 09:06:36 -05:00
Chris Tate b7dddbbb39 Compute remainders by powers of two without fmod
`x % c` for a constant c whose magnitude is a power of two of at least 1
now computes `x - trunc(x / c) * c`, which is exact for every double, and
gives a zero result the dividend's sign. Divisors written as `2 ** k`
count as constants. Other divisors keep frem, and a fractional constant
divisor skips the integer check it can never pass.

The inline integer path for unproven operands now accepts dividends up
to 2^62 instead of 2^53. The remainder of two doubles is a double, so
the conversion back is exact, and products such as
`seed * 1103515245 + 12345` no longer call fmod.
2026-10-15 08:36:53 -05:00
Chris Tate 51c2420a78 Inline record result-slot entries and keep array headers out of loops (#1035) 2026-10-15 08:36:39 -05:00
Chris Tate 97cd12d98a Give dynamic Dirent values Node's prototype and inspect form
A Dirent row that becomes a dynamic value (untyped JavaScript, unknown
slots, rows passed from a typed module) was a plain object: its type
probes were missing, so `readdirSync(dir, { withFileTypes: true })
.map((d) => d.isFile())` in a .mjs program threw "d.isFile is not a
function", and Object.keys, JSON.stringify and util.inspect showed the
internal %dtype field.

Both record-to-dyn converters now finish Dirent rows with
scr_dyn_dirent_finish. The row gets a shared Dirent prototype that holds
isDirectory, isFile, isBlockDevice, isCharacterDevice, isSymbolicLink,
isFIFO and isSocket as non-enumerable methods. The entry kind is stored
under an own Symbol(type) key, as in Node, and %dtype becomes
non-enumerable, so a dynamic Dirent still converts back to the typed
row. Live record ops are not emitted for Dirent rows, so key walks read
the finished view. A method called with a nullish receiver throws Node's
TypeError.

util.inspect of a dynamic object now skips non-enumerable properties and
lists own enumerable symbol keys after the string keys, as Node's
default view does. Typed Dirent rows render through the dynamic view, so
both print `Dirent { name: 'a.txt', parentPath: '.', Symbol(type): 1 }`.

The finisher lives in scr_symbol.c. Programs that have the Dirent record
shape link that unit.

Corpus 5565 and 5566 check the untyped and cross-module forms against
Node.
2026-10-15 08:35:14 -05:00
Malte Ubl 0e943fedb0 Bound while counters by their loop condition
While loops discarded every loop-carried fact, so the escape-time counter
in `while (x * x + y * y <= 4 && it < MAX) { ...; it++ }` stayed a double:
its increment, its bound compare and the `it === MAX` test after the loop
all ran in floating point.

guardedLoopCounterFacts bounds a counter that the loop changes only by
increments of positive integer literals outside nested loops, switch and
exception regions, when a conjunct of the condition compares it below a
literal or a binding the loop never writes, and the condition itself never
writes it. The body runs only after that conjunct held, so every pass
starts at most at the bound and climbs by at most the sum of the body's
increments; the counter starts at its entry fact and only grows. The range
holds at every read inside and after the loop, so it also feeds the
loop-carried closure as an invariant.

The mandelbrot counter becomes an i32 slot. Corpus 5333 pins escape-time,
reversed, inclusive, multi-increment, early-exit and module-level counters,
and every case that must stay double (counters written by their condition,
decrements, fractional and unknown steps, nested increments, written
bounds, disjunctions, fractional, -0, NaN and infinite starts, limits past
2^31, 2^32 and 2^53) against Node.
2026-10-15 06:34:13 -07:00
Chris Tate f43504e0c4 Accept KeyObject keys in the fallback CipherKey type
The bundled node:crypto declarations typed CipherKey as BinaryLike only,
so a program compiled without @types/node could not pass a secret
KeyObject to createCipheriv or createDecipheriv, although the runtime
accepts one. CipherKey is now BinaryLike | KeyObject, as in @types/node.

5250 covers keys, IVs, AAD, tags and data that reach the cipher through
records, untyped values, nested objects, Maps, arrays, generics and
KeyObjects.
2026-10-15 08:21:44 -05:00
Chris Tate 1121fe20c2 Merge main 2026-10-15 08:18:16 -05:00
Chris Tate 2c00672bca Merge main 2026-10-15 08:09:02 -05:00
Chris Tate aecd35b6c2 Merge main 2026-10-15 08:03:25 -05:00
Chris Tate 6b7e9539fb Inline record result-slot entries and keep array headers out of loops
A function that returns a record its caller only projects fills the
caller's frame storage through a %into.<name> entry. These entries are
small and called once per use, but LLVM kept them out of line in loops:
record-boundaries' pathLength called %into.pointAt twice per iteration.
They now carry inlinehint, like the indexed-read and interface-dispatch
helpers.

Once inlined, the scalar array reads in them still blocked hoisting the
arrays' header loads: the slow path's scr_arr_state and getter calls had
no memory attributes. They are now read-only cold calls, as the dense
read fallbacks already are (the getter runs only for a present element,
so it does not trap).
2026-10-15 08:02:39 -05:00
Chris Tate 0b468e40a1 Add fs.globSync, fs.glob and fs/promises glob
The three glob entry points run a port of Node 24's
lib/internal/fs/glob.js walk in scr_glob.c: the same LIFO queue of
(path, patterns) items over the sorted directory listings, the same
lstat, stat, readdir, realpath and seen-pattern caches, the same result
set in insertion order, and the same exclusion checks, so matches come
out in Node's order. Patterns compile with the existing minimatch port.
glob_match is split into a compile step and a test step so the walk can
reuse each compiled pattern and test it segment by segment.
nocaseMagicOnly is now an option, because exclude patterns turn it off.

Patterns (a string or string array) and options (cwd as a path or
file: URL, exclude as patterns or a function, followSymlinks,
withFileTypes) are validated in Node's order with Node's errors,
including the internal assertion Node hits for a non-string cwd with
exclude patterns. withFileTypes results are Dirent rows: name,
parentPath and the type probes. parentPath follows Node's rule for each
form: the joined path for globSync, the resolved path for fs/promises
glob.

fs.glob checks the callback and validates synchronously, then delivers
string[] or the walk's error through the filesystem callback queue.
fs/promises glob lowers as the source of a for await loop. The walk runs
when the loop starts, and the matches found before an error bind before
the error is thrown, as Node's async iterator yields them first. The
older glob(3)-based fs.glob branch is removed.

Corpus 5448-5452 and a node-types fixture check the behavior against
Node.
2026-10-15 07:59:19 -05:00
Chris Tate 31ba299f87 Complete fs.Stats in both lanes (#1025) 2026-10-15 07:54:30 -05:00