`const { hasOwnProperty } = Object.prototype`, `Object.prototype.constructor`
and `hasOwnProperty.call(Object.prototype, "constructor")` hit the runtime
"Object.prototype reflection and mutation" fence (acorn, ini,
safe-stable-stringify and qs read the prototype this way). Reads of the
prototype's constructor and shared methods now answer the same interned
values the inherited reads answer, own-property checks see its twelve own
members (none enumerable), and Object.prototype.toString.call(error)
answers "[object Error]" instead of "[object Object]". Mutation and the
remaining reflection (isPrototypeOf, __proto__, accessors) stay fenced.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fast-deep-equal and every isPlainObject idiom compare `o.constructor ===
Object` and `o.valueOf === Object.prototype.valueOf`. Natively a dynamic
read of an inherited Object.prototype member answered undefined and the
JavaScript identifier `Object` was a string identity token, so
equal({a: 1}, {a: 1}) was false with no diagnostic.
The builtin constructors Object, Function, Date, RegExp, Map, Set,
Promise, WeakMap and WeakSet now read as interned function values (in
TypeScript too, where they were refused), and dynamic values answer
`.constructor` with the same functions: plain objects (including
Object.create chains), arrays, functions, class instances and the native
Date/RegExp/Map/Set/WeakMap/WeakSet/Promise values. A member no nearer
prototype defines falls back to the interned Object.prototype method
`Object.prototype.<name>` reads (valueOf, toString, hasOwnProperty,
propertyIsEnumerable), respecting the types that define their own
toString or valueOf. Typed records and class instances read these
members through their dynamic view instead of hitting the stdlib fence.
`instanceof` accepts the constructors, and `Object(v)` / `new Object()`
work through a stored value; calling the other constructors through one
stays fenced.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The multi-argument Date constructor was refused (SC2020), which blocked
date-fns and consumer code that builds dates from calendar fields. Typed
callers now lower to a seven-f64 runtime call (omitted trailing fields
complete with the spec's defaults) and JavaScript callers pass their
arguments through ToNumber in order. The runtime mirrors V8: MakeDay
bounds the untruncated year and month, years 0-99 map to 1900-1999,
fields roll over, and the local time value converts to UTC in the host
zone with the same UTC(t) the date parser uses, then TimeClip. Spread
arguments stay fenced.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Date.parse and new Date(string) answered NaN natively for every form
outside ISO 8601 with an explicit offset and the X509 certificate shape,
so RFC 1123 HTTP dates ("Mon, 01 Jan 2024 00:00:00 GMT") never parsed and
the fresh/express/send conditional GET never matched. The runtime now
falls back to a port of V8's DateParser (the ES5 date-time format, then
the Safari-compatible legacy grammar: weekday and month names, RFC 2822
offsets, zone abbreviations, AM/PM, parenthesized comments, slashes) for
exactly the strings the bounded grammars reject, so the timestamps they
accept cost nothing more. Zone-less dates are local time, converted with
the spec's UTC(t) over the host zone (DST gaps read with the offset before
the transition, overlaps answer the earlier instant).
With native parsing matching Node for every input, the SC6005 literal
warning has nothing left to report and is retired rather than extended to
non-literal inputs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each local-time getter (getFullYear, getMonth, getDate, getHours, ...,
getTimezoneOffset) called localtime_r and walked the calendar for its
own field. The thread now keeps its last local conversion, keyed by the
instant and a timezone generation, so reading several local fields of
one Date converts it once. The UTC walk and the second computation use
integer division instead of floor.
Writing or deleting process.env.TZ calls tzset and starts a new
generation, so later local-time reads use the new zone, as in Node.
Before, the zone the process started with stayed in effect.
Both fixtures declare a function-local class whose base has no static
lowering (`class FileURL extends URL {}`, `class Counter extends
Readable`). The SC1090 for it used to come from the whole-program type
queries, which collected the class outside any statement, so the build
failed. Those queries now keep a refused local class out of their
results and drop its diagnostics; the class declaration reports the
refusal when it lowers, and in a JavaScript source a refused statement
becomes a runtime fence. Both programs therefore compile and throw the
fence when they reach a construct without a lowering, like the other
fixtures listed with a run refusal.
Class dynamic dispatch decides whether a class family keeps a fixed
field layout from the property names a delete can remove. It cached that
set with ??=, but the scan returns null when the program reflects on
properties, so a program that does recomputed it, walking every source
file, for each class in every dispatch wave. Only an undefined value now
means the set has not been computed.
npm-static re-export pruning read and parsed a package's package.json
for every file whose purity it checked, and for every package boundary
an import crossed. Each plan now parses each package.json once.
Output is unchanged. three.js nodes lowering takes 6 s less and an
Effect fixture's frontend load 4 s less.
Node runs runNextTicks after each timer and each immediate callback, which
reports the unhandled rejections the callback left before the next one runs.
The event loop now leaves the timer phase when a callback leaves the
rejection ledger non-empty, the check phase waits for that report, and the
check phase reports the ledger after each immediate's checkpoint.
When several timers expired in the same turn, a later timer ran before an
earlier timer's rejection was reported. On macOS the 2 ms and 3 ms timers in
5809-process-exit-and-rejections.cjs expire together, so its uncaught
'thrown string' was printed before the TypeError rejection.
5996-rejections-between-macrotasks.cjs makes five timers expire in the same
turn and checks the order across three immediates.
The gate read an empty symbol list when nm could not start, was killed by
a signal, or exited with a non-zero status, so the dynamic-tier symbol
check and the executable comparison passed without any symbols. nmOutput
now throws with the binary, the reason and nm's stderr.
Rerun scripts/ci-corpus-costs.mjs over the 120 green Linux corpus shard
logs (plain and san, 20 shards each) of main CI runs 39142848734,
39142619508 and 39142079655 (307387c8e, 18c50b55d, b85dd44cd). Their
Linux lanes were all green; only macOS jobs were still queued.
This measures the programs added since the previous refresh, including
5770-5773, 5960-5962, 5973, 6041-6043, the CommonJS require programs
6140-6145 and 5023/5024. Coverage of current main's corpus goes from
2992/3012 (0.9934) to 3011/3012 (0.9997); the one unmeasured program,
scratch-string-slices.ts, landed after those runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
298 of the 3008 corpus programs had no measured cost, which left main
three unmeasured programs above the shard harness's 90% coverage floor:
any PR adding a few corpus programs failed shard.test.ts. Rerun
scripts/ci-corpus-costs.mjs over the 120 green Linux corpus shard logs
of main runs 39131161610, 39130670620 and 39126633135 (31b52e215,
aba3c3a97, 572b26ef8), and drop the entry of the renumbered
5942-local-class-observed-methods.ts. 2989 programs are now measured.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
node:test changes, matching Node 24.22:
- before and after hooks outside any suite run, with the root's context,
whose name is "<root>". They did not run before.
- A module-level before hook runs at its before() call, as Node runs the
hooks of its already-started root at once: its synchronous part runs
before the code after the call, and the first top-level test waits for
an async hook's promise. Every module-level before hook runs, and an
async hook's rejection is not reported as an unhandled rejection.
- Module-level after hooks run after the last top-level test or suite.
- A throwing root before hook fails each top-level test with the first
failure; the test's body and beforeEach hooks do not run, and its
afterEach hooks do. Each top-level suite fails with the error, and the
tests and suites in it are cancelled before they start: they report
without a duration and fail with "test did not finish before its parent
and was cancelled", listed in the failing section in completion order.
- A throwing root after hook reports the entry file as a failed result,
after the top-level results, and lists it in the failing section at the
hook's location. It is not counted, and the exit code is unchanged.
- Hooks record the "file:line:col" of their call for that location.
The root-hooks, root-before-throws and root-after-throws fixtures compare
the reporter output, the order of the hooks against the module code, and
the exit codes with Node, plain and with SCRIPTC_SAN=1.
chalk's callable builder (a function whose prototype chain is set with
Object.setPrototypeOf, with style getters from Object.defineProperties)
already ran on the checked-dynamic model. What failed was the code
around it. Each fix is general and sits on a path that used to end in a
refusal or a runtime fence, or is confined to JavaScript files:
- package.json "imports" inside a --npm-static package resolve to the
shipped JS with the "node" condition hoisted, like the tsgo fs shadow;
- JavaScript object literals that the file reshapes at run time (keyed
writes with runtime keys, delete, Object.defineProperty/ies) map to
the checked-dynamic type, with the literals nested in them;
- a JavaScript class whose only member is a constructor that returns a
value and never reads `this` answers that value from `new` (the return
used to be discarded silently);
- `const { env } = process` binds the data member in JavaScript;
- `o && x` / `o || x` with an always-truthy object, array or function
left operand;
- Number.parseInt/parseFloat over checked-dynamic values apply ToString;
- Function.prototype as a value is one compiler-emitted function;
- calls of String.prototype-only names (bold, italics, ...) on untyped
receivers call the receiver's own or inherited member; a string
receiver throws the refusal at run time.
Programs without these patterns compile to identical bytes: the runtime
benchmark workloads, the self-hosted compiler (LLVM and executable) and
the TypeScript corpus IR were compared against the baseline.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
--cachegrind now also runs differing corpus executables (without
arguments, accepting intentional non-zero exits), so emitter changes that
only touch corpus programs get per-function instruction deltas. AGENTS.md
mentions the dynamic-tier symbol rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Top-level `this` in a CommonJS JavaScript file, also inside top-level arrow functions, read as undefined; Node binds it to the module's original exports object. It is now a per-module object created when the module starts: typeof, truthiness, identity and passing it along (the UMD wrapper `(function (root) { ... })(this)`, CoffeeScript's `.call(this)`) behave as in Node. Exports stay the static export table, so a property access through top-level `this` is a named refusal rather than a write no requirer would see. ES modules keep undefined.
The UMD host test `typeof module !== "undefined" && module.exports` read `module.exports` through the module handle as undefined inside functions, so such modules took the non-CommonJS branch (this is what broke ipaddr.js). `module.exports` used only for its truthiness now answers true when every `module.exports =` in the file assigns an object or function.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`const { inspect } = require("util"); typeof inspect` printed "string", as did `import { inspect } from "node:util"` in an ES module and `typeof promises` from `fs`. JavaScript sources represent builtin values without their own lowering as identity tokens (strings), and typeof answered from that representation. typeof on a builtin import binding in a JavaScript source now answers "function" for exported functions and classes and "object" for exported objects; the binding read has no effects. TypeScript sources compile as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Whole-program analyses that query types before reachability is known (the computed-key write census for JavaScript method dispatch, and the runtime-optional array analysis) can name a function-local class whose collection is refused. No statement owns that refusal there, so it escaped as an uncaught PoisonError from collectClassShapeInner and crashed the compiler (the ts-api-utils probe, @typescript-eslint/typescript-estree, and small TypeScript programs). These analyses now run under Lowerer.speculativeTypeQueries: a refused local class is unmappable for the query and its diagnostics are dropped, and the class's own declaration collects it again and reports them if the program reaches it. Collection and body lowering keep their poison handling.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`let e = map[k]; if (e === undefined) { e = {}; map[k] = e } e[c] = v` on a `Record<string, Record<string, string>>` compiled, then aborted with "record has no key (typed slot…)" where Node prints the result. Without noUncheckedIndexedAccess a keyed record read keeps its typed trap unless the next statement tests the binding for absence, and only `if (!e)` counted. Comparisons with the missing value now count too (`=== undefined`, `== null`, `!== void 0`, `typeof e === "undefined"` and their mirrored forms) for index-signature record reads whose checker type lacks undefined. Module-scope declarations get the same treatment: their pre-registered global takes the undefined arm before any body reads it, and the initializer lowers as the absence probe. Array reads, annotated bindings and noUncheckedIndexedAccess programs compile as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under noUncheckedIndexedAccess, `let e = map[k]; if (e === undefined) { e = {}; map[k] = e } e[c] = v` failed with SC9001 "recordKeySet receiver: expected record, got union". A runtime-optional binding read with a computed key reaches the record read and write lowerings as its `record | undefined` storage. Those lowerings now hold the receiver, the key and, for writes, the value in temporaries and throw Node's TypeError for an undefined receiver ("Cannot read properties of undefined (reading 'k')", "Cannot set properties of undefined (setting 'k')") after evaluating them in Node's order. Every other receiver builds the same IR as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
node:test changes, matching Node 24.22:
- A describe or suite body may take its SuiteContext, with name,
fullName, filePath, attempt, passed and diagnostic. Its passed is false
until the suite has finished, after its after hooks.
- before and after hooks may take the suite's context, and beforeEach
and afterEach hooks the test's. A test's t.before, t.after,
t.beforeEach and t.afterEach hooks may take the test's context.
- SuiteContext maps to the runner node that TestContext maps to, so the
TestContext members that SuiteContext shares lower the same way.
The suite-context fixture compares the reporter output and the order of
the bodies and hooks with Node, plain and with SCRIPTC_SAN=1.