A fresh array literal written to a checked-dynamic slot is built as a
dynamic array. On a member of a typed view of the global object
(`(globalThis as typeof globalThis & { cache?: number[] }).cache ??= []`)
the program reads it back through that declaration, and each typed read
extracted a new native copy. `cache().push(x)` was lost, and two reads
were never the same array. Node keeps one array.
When the literal is the right side of a write (`=`, `??=`, `||=`) to a
member of the global object, and the member is declared as a native
array, build the native array and store it boxed. Every typed read then
extracts that same array, and untyped reads see it through its live
capsule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BigInt of byte storage joined the elements with "," for every kind, so a
Buffer, whose text is its UTF-8 decoding, and a DataView, whose text is
"[object DataView]", converted as typed arrays. BigInt now parses the
value's ToString, which the runtime value decides, as String does. This also
covers a Buffer held in a Uint8Array slot.
Corpus 7403 adds Buffers, a DataView, and a Buffer in a typed array slot.
Refs #1647
dyn.mathFold steps the iterator of an untyped iterable, so the generator
dispatch pass now counts it among the runtime entry points that step
iterators. A generator whose only consumer is Math.min or Math.max then has
its step hooks registered, and the spread folds its values instead of
throwing "Reading this native iterator result has no lowering".
A CCM decipher skipped its one update() when the data was empty, and
final() accepted when an update had run without failing, so it never
compared the tag. setAuthTag() with any tag of the right length, then
update() with empty data, then final(), succeeded: anyone could forge an
empty message, or one with different AAD or a different declared
plaintextLength, without the key.
A decipher's update() now runs the authenticated decryption for empty
data too. As in OpenSSL, the tag covers the nonce, the AAD and the
length, so the right tag still opens an empty message and any other
tag, AAD or length fails in final() with "Unsupported state or unable to
authenticate data". final() succeeds only after a tag comparison has
succeeded. A cipher's empty update() is unchanged.
A fresh TypeScript data literal passed to a checked-dynamic slot became a
typed view whose record layout requires every property, so a JavaScript
callee deleting one (zod 4's `normalizeParams` deletes `params.message`
from `.refine(fn, { message })`) threw "Cannot delete property". When
the callee is implemented in JavaScript (a call through an untyped value,
an untyped parameter of a JavaScript function, or a non-library `.d.ts`
signature whose parameter is untyped or makes one of the literal's
properties optional), the literal has no typed alias and is now built as
the plain object the callee receives. Standard-library and TypeScript
callees, and literals with methods or accessors, keep their native
storage.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A method whose name an untyped write can replace is called through the
instance's property lookup: a ternary between the dynamic call of an
installed replacement and the native call, both boxed into a dynamic
value and converted back to the method's result type. A result with no
dynamic form, such as an Iterable<T> (its generator arm does not box),
stayed unboxed in the dynamic ternary, which failed IR validation with
"ternary else-branch: expected dyn, got union". Methods of generic class
instantiations always take this path once any write can replace their
name, because their prototype data is not modeled, so importing zod
(whose JavaScript has unknown-key writes) broke every such call.
Such a result now keeps its native type: the native call's value is used
as it is, and only a replacement's dynamic result takes the checked
conversion into the result type. Results that box are lowered as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
structuredClone in an island of a value holding native records, class
instances or objects (interceptor objects V8's serializer cannot clone)
clones a plain copy of that data, as Node clones the objects themselves.
An island array of primitives or strings (or arrays of those) returned
directly or through a promise crosses as a copy, which the checked cast
then meets, instead of failing the cast as an object handle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BigInt(typedArray) compiled in JavaScript but stopped at run time with
SC2020. Node converts the typed array through ToPrimitive to its elements
joined with "," and parses that text: one element reads as its value, an
empty array as 0n, and several elements, fractions, NaN, Infinity or an
exponent form throw the SyntaxError for the joined text.
- BigInt of a typed array lowers to bigint.parse of its "," join, the same
conversion Node performs.
- A union whose arms are typed arrays and other BigInt sources dispatches
each typed-array arm the same way.
Corpus 7403 covers every supported element kind, negative and clamped
values, float formatting (2 ** 60 reads through its shortest decimal
text), empty arrays and views, the SyntaxError cases and results in
arithmetic, and matches Node.
Refs #1647
Co-authored-by: Quino Terrasa <26749475+espetro@users.noreply.github.com>
`"closeAllConnections" in server` (the feature test servers use before
calling it) was refused for an http.Server and the other native handles.
A member Node's declarations give the class is on its prototype, so the
test now answers true after evaluating the receiver. Undeclared names
keep the refusal, since internal fields can still be present.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A module namespace converted to an untyped object, and a computed
namespace read, use the module's resolved exports, which include the
members of `export *` declarations.
- `super(message, options)` through a runtime Error parent installs the
cause as Error does: an inherited `cause` counts, the value is read
through its getter, and the getter's exception propagates.
- `value instanceof Error` on an instance of a class whose heritage is
evaluated at run time tests that class's prototype chain for
Error.prototype. A program with such a class checks prototype chains
for `instanceof Error` on untyped values.
- The cache of names written on the global object is a Lowerer field
instead of a WeakMap keyed by the Lowerer.
- errors.test.ts: `"prototype" in Map` answers from the builtin
constructor's keys now, so it leaves the refused reads and joins a
check of `in` probes against Node's answers.
Hono's mergePath reads base?.at(-1) on an untyped parameter. The
checked-dynamic method dispatch answered at() on strings with a
not-supported error. It now returns the UTF-16 code unit at the index,
counting from the end for a negative index, and undefined outside the
string, as String.prototype.at does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Math.min(...src) and Math.max(...src) over an untyped source now take its
values the way a spread does: arrays, strings, bytes and collections pack,
a native iterator (a Map's keys()) drains, and other iterables (a generator,
an object with a Symbol.iterator method) step their iterator to the end.
Before, only the packing kinds were accepted and every other iterable threw
the spread TypeError.
Lowering collects a function-local class with the body declaring it, so
a local subclass whose function lowers after its base's constructor was
missing from new.target's dispatch, which then answered with the base for
its instances. The refusal no longer depends on that order: the program's
function-local classes are found once, and one that extends the
constructor's class, directly or through other classes its extends
clauses name, refuses new.target as a collected one does.