Commit Graph
1977 Commits
Author SHA1 Message Date
Chris Tate 05f2dd5859 Walk Map and Set iterators inside iterator helper objects
A helper object over a Map or Set iterator that is stored and consumed
later (const it = set.values().map(f); for (const x of it) ...) stepped
the runtime's checked iterator through an adapter generator, boxing each
value and resuming two generators per element: 5.1x Node's time on a
100k-element Set.

A stage on m.keys(), m.values(), m.entries() (or a Set's), or on
Iterator.from(set) or Iterator.from(map), now walks the collection inside
the helper's own generator. The walk steps the runtime's native iterator
by entry index through the new scr_dyn_collection_iterator_index and
reads each key or value with the typed intrinsics. The native iterator
keeps the walk live and ends the collection's iteration when it is
exhausted or released, so an abandoned or returned helper leaves the
collection free to compact. Other helper sources over a Map or Set, and
Iterator.from over a Set or Map, use a typed generator over the
collection built the same way.
2026-10-19 00:03:25 -05:00
Chris Tate d4e614a51c Count each statement once in coverage however often it lowers (#1366) 2026-10-18 23:38:30 -05:00
Chris Tate ce03d9a1db Lower string and typed array iterators, Iterator.from and helper return() (#1363) 2026-10-18 22:32:46 -05:00
Chris Tate a1a475c76f Count each statement once in coverage however often it lowers
Speculative lowering lowers some function bodies several times: the
receiver of `(function () { ... }).call(this)` goes through four call
lowerings that each probe it with tryLowerExpression, and each probe lowered
and counted the body. A five-statement program with such a wrapper reported
14 statements, and a fenced statement inside it counted as four failures.

The Lowerer now records which statements and class field initializers are
in the totals and which are counted as failed, and counts each node once.
Corpus 6110's @deferred-fences directive stays 1, its one fenced statement
in a class base lowered once, and its header no longer ties the count to
how many times that body lowers.
2026-10-18 22:30:45 -05:00
Chris Tate 708b1350bf Free errors that crossed into checked-dynamic code and attach Node's error properties (#1359) 2026-10-18 22:17:52 -05:00
Chris Tate dc6a638e63 Lower string and typed array iterators, Iterator.from and helper return()
String iterators (s[Symbol.iterator]()) and typed array values(),
keys() and entries() compile as iterator objects: generators over the
code points and over the indices. The iterator helpers apply to them,
and for...of, spreads and Array.from consume them.

Iterator.from over an array, string, typed array, Set, Map or iterator
object answers the value's own iterator, as Node does for iterators
that already inherit from Iterator.prototype. Objects with their own
next method keep the Iterator fence. As the root of a helper chain,
Iterator.from(x) walks x itself: an array by index, a Set or Map live,
a generator directly.

h.return!() and h.return?.() compile when h is a helper call or a const
initialized with one: helper objects always have return(), so both
spellings are the call. Other iterator objects keep the fence, since the
array, string, Map and Set iterators have no return().

flatMap callbacks may return Sets (their values) and Maps (their
entries), walked live; a string result throws Node's TypeError and
closes the source.
2026-10-18 22:10:17 -05:00
Chris Tate ecff232688 Close Next.js proxy gaps: fetch header and URL-error parity, JSON undefined, and twelve compile gaps (#1341) 2026-10-18 21:54:08 -05:00
Chris Tate 96f59b0f3f Free errors that crossed into checked-dynamic code and attach Node's error properties
The error identity cache (scr_dyn_from_error) keeps one checked-dynamic
node per error so a crossing error compares reference-equal and keeps the
properties set on it. It retained both sides for the whole process, so
every error that crossed a checked-dynamic boundary (a caught error read
through a cast, an error delivered to an untyped callback, a dns failure, a
warning) stayed alive until exit: 100,000 caught AssertionErrors read
through a cast grew the resident set by about 210 MiB.

The cache now sweeps itself. An entry whose error and node are referenced
only by the cache is unreachable (a node holds no reference to its error;
a registered subclass box holds one, and a spawnSync error's `error`
property refers to its own node), so a sweep releases both sides. Sweeps
run when the cache doubles past the entries the last sweep kept, so they
cost amortized constant time per crossing. The cache is per thread, and
objects shared across threads are published (immortal), so a sweep never
races another thread's reference. Live errors and live nodes keep their
entries: identity, rethrowing the same instance, isNativeError and kind
checks are unchanged.

scr_error_set_prop sets an own property on an error's node; dns, assert,
cipher, crypto, util.callbackify and warning errors use it for the
properties they attach. util.inspect of a runtime error renders through
its node: Node's own properties follow the bracket form in key order (a
system error's errno, code, syscall and path, and the extra properties a
throw site attached), and an AssertionError renders at depth 0 with long
string actual/expected values shortened, as its custom inspect does. A
node that contains itself renders as Node's <ref *1>/[Circular *1].

child_process errors carry Node's own properties:
- execSync and execFileSync: status, signal, output, pid, stdout and
  stderr (Buffers, or strings with an encoding option); a spawn failure
  or timeout adds errno, syscall, path, spawnargs and error first.
- execFile's callback and the promisified execFile/exec: code (the exit
  status, or null after a signal), killed, signal and cmd, with stdout and
  stderr when promisified; a spawn failure has errno, code, syscall, path,
  spawnargs and cmd.
The sync forms echo the captured stderr before a timeout throw too, as
Node's inheritStderr does. The cp.execSync libCall takes a flag for Buffer
outputs.

An error's own `stack` stays non-enumerable when assigned through its
node. A worker's record-source table is freed when the worker exits.
2026-10-18 21:53:14 -05:00
Chris Tate f629d03dd8 Merge main 2026-10-18 21:28:03 -05:00
Chris Tate a7e3a9d0aa Cancel running subtests when their parent test times out (#1350) 2026-10-18 21:24:04 -05:00
Chris Tate c3b1303e7a Convert class instances to strings and numbers through their own methods (#1358) 2026-10-18 21:20:19 -05:00
Chris Tate d4d407bb07 Fix npm crashes, internal errors and false TDZ errors from the compatibility research (#1357) 2026-10-18 21:05:11 -05:00
Chris Tate 82683b5b4d Clear timers through handles that may be undefined or null (#1356) 2026-10-18 21:02:08 -05:00
Chris Tate e8e432b6dd Clean up subtests cancelled by their parent's timeout
node:test changes, matching Node 24.22:

- A subtest cancelled because its parent timed out has its mocks
  restored and its mock timers reset when it is cancelled, as Node's
  postRun resets the test's mock tracker. Before, a mocked method stayed
  mocked for the tests that ran after it.
- Its afterEach chain and t.after hooks run at the next turn, once the
  runner has moved on to the next test, as Node runs them when the
  cancelled test's own run resumes. Before, they never ran. The chain is
  copied when the test is cancelled, since its ancestors drop their hooks
  when they finish.
- Its hooks are then dropped, so closures that capture its context do not
  keep it alive. The registration parent is still not restored when the
  cancelled body settles: it belongs to the test that runs then.

The timeout-cancel-cleanup fixture records the mocked method and the hook
order against the next test's body and compares them with Node, plain and
with SCRIPTC_SAN=1. It fails without this change.
2026-10-18 20:58:20 -05:00
Chris Tate 2ed5136923 Convert class instances to strings and numbers through their own methods
String(x), template literals, `+` with a string and Number(x) or unary
plus on a program class instance failed with SC2001 ("values of type
'Money' cannot be compiled yet"), even when the class defines toString.
They now run Node's ToPrimitive protocol through the class's methods:

- `[Symbol.toPrimitive](hint)` when the class defines it, with the hint
  "string" for String(x) and template literals, "default" for `+`, and
  "number" for Number(x) and unary plus.
- Otherwise toString (string hint) or valueOf and then toString (default
  and number hints), and "[object Object]" (NaN as a number) when the
  class defines neither.
- The method dispatches like a call of it. Subclasses that convert
  through a different method are tested first with instanceof, so an
  array of base-class values converts each element by its own class.
- Unions of class instances and primitives (`Box | undefined`) convert
  each arm as it would alone.

Conversions the program can change at run time keep a named refusal: a
toString or valueOf instance property or prototype write, a
Symbol.toStringTag, and conversion methods that can return an object.
Builtin classes and their subclasses keep their existing conversions.
2026-10-18 20:55:25 -05:00
Chris Tate 3b46ea1603 Return undefined from generic functions that read a missing element (#1307) 2026-10-18 20:48:26 -05:00
Chris Tate 684f73d186 Add ThreadSlots, per-thread values on shared objects, to @scriptc/experimental (#1271) 2026-10-18 20:39:11 -05:00
Chris Tate c48983889c Clear timers through handles that may be undefined or null
clearTimeout, clearInterval and clearImmediate refused a handle typed
`Timeout | undefined` or `Timeout | null`, the type of a timer variable
that starts empty (`let timer: ReturnType<typeof setTimeout> | undefined`)
and of `handles[i]!` reads. Such a handle now passes id 0 when it is
absent, which matches no timer, so the clear does nothing, as in Node.
2026-10-18 20:35:23 -05:00
Chris Tate 4e92ec16d8 Compile derived constructors whose super() call leads a comma statement (#1354) 2026-10-18 20:25:58 -05:00
Chris Tate d9ac08e57c Merge main 2026-10-18 20:25:10 -05:00
Chris Tate ee9a0e1c33 Pool keep-alive sockets in http Agents and use globalAgent for default requests (#1230) 2026-10-18 20:21:54 -05:00
Chris Tate c03479abe5 Merge main 2026-10-18 20:16:15 -05:00
Chris Tate 2d37d451e9 Run iterator helpers on untyped iterators (#1352) 2026-10-18 20:07:41 -05:00
Chris Tate ada2657873 Stop creating self-capture cycles and buffer cycle candidates on the release fast path (#1320) 2026-10-18 20:03:40 -05:00
Malte UblandClaude Opus 5.5 18033dc42d Compile derived constructors whose super() call leads a comma statement
Minifiers join a derived constructor's statements into one comma
expression (`super(message, options), this.name = ..., ...;`), which jose
6.2.12 ships in its error classes. The statement lowering already runs
each operand of a statement-level comma as its own statement, so super()
is accepted when only parentheses and comma operators separate it from
its expression statement. Every other expression position keeps the
SC1090 refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:57:35 -07:00
Chris Tate 45d4105351 Compile maybe-promise unions, unknown[] rest parameters, and function-valued Maps and Sets (#1347) 2026-10-18 19:55:09 -05:00
Chris Tate f761f7d75a Count idle pooled sockets in agent.totalSocketCount
agent.totalSocketCount counted only the requests an agent was serving,
so the idle sockets of a keepAlive agent in agent.freeSockets were left
out, and a second request that dialed a new socket while the first was
idle counted 1 instead of 2. As in Node, the count is now the number of
sockets the agent created that have not closed: a socket joins it when
its dial starts and leaves it when it closes, or before the request's
'close' when it does not go back to the pool. A request that takes a
free socket keeps the count.

Corpus 5593 covers reuse, a second socket, agent.destroy() and an agent
without keepAlive.
2026-10-18 19:54:32 -05:00
Malte Ubl cdf731e4da Merge remote-tracking branch 'origin/main' into opt/npm-runtime-bugs
# Conflicts:
#	packages/compiler/src/frontend/lowering/lower-modules.ts
#	packages/compiler/src/frontend/lowering/lower-stmts.ts
#	packages/compiler/src/frontend/program.ts
2026-10-18 17:50:35 -07:00
Malte UblandClaude Opus 5.5 20f15f89fd Let implicit JS instances that fall through return undefined
A JavaScript function specialized per call site takes its declared
return type from JSDoc. bcryptjs's `hash` and `compare` document
`@returns {!Promise}` but return nothing when a callback is passed, so
their instances ended with the "the checker proved every path returns"
trap and every callback-style call aborted with SC9002. Non-generic
JavaScript functions already widen such a return with an undefined arm;
implicit instances now do the same.

The widened `Promise<unknown> | undefined` result is then awaited by
TypeScript callers, which was refused: an await of a promise-of-dynamic
or unit union now goes through the dynamic await, which adopts the
promise or hops once for the unit, as Node does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:48:01 -07:00
Malte UblandClaude Opus 5.5 f8fa60afec Link the promise adapter when a boxed class instance holds a promise
Boxing a class instance as a dynamic value (Object.keys, spread,
inspection) boxes its fields, and a promise field goes through
scr_dyn_new_promise_adapting in scr_async_dyn.c. The link-unit scan
followed promises through records, arrays, unions and functions but not
class instances, so `Object.keys(job)` over a class with an optional
`Promise` field failed to link.

The scan now follows a class instance's fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:48:01 -07:00
Malte UblandClaude Opus 5.5 b6637c882d Check record, array and string fields that construction can read early
A field without an initializer that the constructor assigns only after
handing `this` to other code is undefined in Node until that assignment.
Class-typed fields in that position already take the undefined-armed
deferred-init slot whose reads check it. Record, array and string fields
kept a plain pointer slot, so a method the constructor calls first could
read null and crash with a segfault (`this.internal.n` read from a
method before `this.internal = ...`).

Those field kinds now take the same checked slot when a method or
accessor the constructor calls through `this` (transitively, over the
class and its bases) reads them. Fields only exposed in other ways keep
their plain slot, so constructors whose early calls never touch the
field compile as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:48:00 -07:00
Chris Tate ec79cf2789 Run iterator helpers on untyped iterators
In JavaScript, a receiver without an inferred type, such as an untyped
parameter's xs.values(), is the runtime's checked iterator. Its only
method was next(), so xs.values().filter(f), .map(f) and the other
iterator helpers threw "TypeError: ... is not a function" where Node
runs them, and [].values().reduce(f) threw the same instead of Node's
"Reduce of a done iterator with no initial value".

The native array, string, Map and Set iterators now answer the
Iterator.prototype methods. map, filter, take, drop and flatMap make
helper objects (SCR_DYNH_ITERATOR_HELPER) that pull lazily and support
next(), return(), for...of and spreads, print as
"Object [Iterator Helper] {}" and tag as "[object Iterator Helper]".
toArray, forEach, reduce, some, every and find drive the iterator.

The helpers close the iterator they were made from where Node does:
when a callback throws, when take's budget is spent, on return(), and
before the error for an invalid argument. flatMap closes its inner
iterator first. An error from a close that runs because of another
error is dropped. Invalid arguments throw Node's messages, such as
"number 5 is not a function" and "x must be positive".
2026-10-18 19:42:27 -05:00
Chris Tate 3de2fb56a0 Cancel running subtests when their parent test times out
node:test changes, matching Node 24.22:

- When a test's { timeout } elapses, its subtests that are still running
  are cancelled, the innermost first. Each reports its duration so far and
  fails with "test did not finish before its parent and was cancelled",
  before the parent's result.
- A cancelled subtest's body keeps running unobserved: when it settles it
  reports nothing and leaves the runner's state alone. Before, it reported
  its result after the run's summary.

The timeout-subtests fixture compares the results, counts and failing
section with Node, plain and with SCRIPTC_SAN=1.
2026-10-18 19:28:55 -05:00
Chris Tate 6fd2dedb15 Merge main 2026-10-18 19:04:59 -05:00
Chris Tate 6ab49f33b2 Stringify class instances with JSON.stringify (#1338) 2026-10-18 18:59:22 -05:00
Chris Tate aa9289b9d9 Concatenate untyped Buffer lists as Node does (#1272) 2026-10-18 18:46:36 -05:00
Chris Tate 550e00549c Move callSiteReturnType into ir.ts for devirtualization
Lowering may not import ir/validate.ts, and devirtualize.ts imported
callSiteReturnType from it. The function only reads an IrFunction's
types, so it moves next to typeEquals in ir.ts, which both the
validator and lowering import.

The Date formatter fence's hint now lists toUTCString(), which this
branch lowers, so the date-formatters and stdlib-fence snapshots record
the new hint. toString() and toLocaleString() still have no lowering,
and the fixtures keep them.
2026-10-18 18:40:52 -05:00
Chris Tate 707ac5cf77 Carry Hash and Hmac values through unknown and any slots (#1257) 2026-10-18 18:38:52 -05:00
Chris Tate 8d94e5d993 Refuse RegExp and Promise class fields in JSON.stringify
A Date field reads through the class's checked-dynamic property view and
writes its toJSON text, so json-class-fields.ts compiled and matched Node
while the diagnostics test expected a refusal. The Date cases move to
6000-json-stringify-class-instances.ts.

RegExp and Promise fields read through the view but the runtime JSON
traversal has no writer for them: a RegExp field threw at run time where
Node writes {}, and a Promise field failed to link. Both are now refused
at compile time naming the field, also inside a field's arrays, records
and unions, and the fixture covers them.
2026-10-18 18:34:09 -05:00
Chris Tate 9239521160 Answer instanceof Writable for Duplex streams as Node does
Node's Writable[Symbol.hasInstance] answers true for any stream with a
writable side, so a Duplex, Transform, PassThrough or zlib stream (and
user subclasses of Duplex) is an instance of Writable without inheriting
from it. `x instanceof Writable` tested only Writable's own interval and
answered false for them. It now also tests Duplex's interval for object,
union and catch-binding operands, and the static folds count Duplex
subclasses. Subclasses of Writable keep the prototype test, as in Node.
2026-10-18 18:15:01 -05:00
Chris Tate cf11fbaaeb WebAssembly W0–W2: free handle tags, a cross-thread clone hook, the WebAssembly types and refusals, and a decoder that reports every module a program can load (#1339) 2026-10-18 18:12:42 -05:00
Chris Tate 4dc25a18c1 Lower iterator helpers on generators and Map and Set iterators (#1342) 2026-10-18 18:11:21 -05:00
Chris Tate 14372c8274 Build "is not a function" errors for Hash, Hmac and KeyObject handles at any length
The Hash/Hmac and KeyObject handle dispatchers formatted "<callee> is not
a function" into a 160-byte stack buffer and passed snprintf's return
value as the message length. A callee text longer than about 140 bytes
made that length exceed the buffer, so the error copied bytes past it:
the message ended in stack contents, and sanitized builds aborted. Both
dispatchers now build the message in a growable buffer, as the shared
dynamic-call path does, so the TypeError carries the whole callee text,
as in Node.

Corpus 5774 calls a missing method on a Hash, an Hmac and a KeyObject
through callee texts longer than 160 bytes, and through a short one, and
corpus-costs.json records its plain and sanitized costs.
2026-10-18 18:09:08 -05:00
Chris Tate 05b63df116 Check Buffer.concat's length before the elements of an untyped list
Node's Buffer.concat(list, length) validates length (after returning early
for an empty list) before it checks that every element is a Uint8Array.
The untyped-list path checked the elements first, so a bad element and a
bad length threw ERR_INVALID_ARG_TYPE where Node throws the length's
ERR_OUT_OF_RANGE.
2026-10-18 18:05:09 -05:00
Chris Tate 52303baf4b Keep the promise of async generic functions whose body reads a missing element
The instance result of an async generic function whose body returns an
element or index-signature read that can miss is Promise<T | undefined>:
the promise is always present and settles with undefined. The call site
widened the whole promise instead, so the instance and its calls had
different signatures and every call failed to compile.
2026-10-18 18:01:49 -05:00
Chris Tate dd2444fbb6 Keep React Flight's development-only stack parsing out of production builds, and make stack mode cheaper (#1330) 2026-10-18 18:01:06 -05:00
Malte UblandClaude Opus 5.5 f4a8511dca Compile maybe-promise unions, unknown[] rest parameters, and function-valued Maps and Sets
A union may now hold one promise beside data members (`T | Promise<T>`,
`Promise<R | void> | R | void`). The union tag is the `instanceof Promise`
test, and every operation keeps Node's microtask timing: `await` awaits the
promise arm or takes the one non-thenable turn, an async `return` (block
bodies, concise arrows, then handlers) adopts the promise arm after the
function's own finally blocks, `Promise.resolve` returns the promise arm as
is, and `typeof (u as any).then` reads the tag. Data arms that declare
`then`, two promise arms, and function arms beside a promise stay refused
with diagnostics. Inferred JavaScript literals that mix promises and values
keep the checked-dynamic array.

Rest parameters typed `unknown[]` or `any[]` in TypeScript use the
checked-dynamic variadic ABI that JavaScript rest parameters already use,
including spread forwarding (`(...args) => handler(...args)`).

Functions are now Map values, Map keys and Set elements, compared by
closure identity.

Two pre-existing bugs in the same area are fixed: an async lambda in a
`Promise<void> | void` slot got a `Promise<union>` ABI and threw when called
through the slot, and `return cb()` of such a callback in an async function
dropped the promise instead of adopting it.

The retired-engine 2251 and 2790 diagnostics fixtures (tagged templates
with `any[]` rest tags) now compile and match Node, so they move to the
corpus as 6248 and 6249.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:58:57 -07:00
Chris Tate b32f4e31f3 Merge main 2026-10-18 17:47:55 -05:00
Chris Tate 2f40bcb8d4 Lower iterator helpers on generators and Map and Set iterators
map, filter, take, drop, flatMap, toArray, forEach, reduce, some, every
and find compiled only as one chain from arr.values() or arr.entries()
that ended in a consuming method. Every other form was refused, and in
JavaScript the refusal was thrown at run time.

IteratorObject now maps to the generator type. A consuming method over
a chain of stages fuses into one loop over the source: a generator
driven like for-of, a Map or Set walked live, or an array by index. A
stage on its own builds a helper object: a generator over its source
that supports next(), for...of, spreads, Array.from and yield*. A
for...of whose head is a chain of stages without flatMap runs the
stages inside the loop.

The source is closed where Node closes it: when take reaches its count,
when some, every or find has its answer, when a callback or the loop
body throws, when the loop exits early, and before the RangeError of an
invalid take or drop budget. It is not closed when it is exhausted or
its own next() throws. return() on a helper object before its first
next() closes the source through a new runtime hook,
scr_gen_set_unstarted_return.

When closing an iterator throws while another error propagates, the
first error reaches the caller, as in Node. This applies to for...of
over a generator too, where the error from the close used to replace
the body's error. The try statement takes a dropFinallyErrors flag,
whose cleanup handler calls the new scr_exc_keep_first.

throw() lowers only on values typed as generators. Iterator helpers and
the built-in iterators have no throw method, and a value typed as an
iterator may hold one.

Callbacks in JavaScript bind their unannotated parameters to the
element type, and flatMap callbacks may return generators. A generator
whose record yields share checked storage with its return channel has
each value converted to the declared element type as it is pulled.
2026-10-18 17:46:31 -05:00
Chris Tate 6dff370714 Compile inline require(), computed and chained CommonJS exports, and a module's own exports object (#1329) 2026-10-18 17:45:15 -05:00