A binding that may hold a value its declared type omits (a function
parameter fed array elements, which can be holes) and that the checker
narrowed to one record or class arm of a union with more than two arms
was read through a checked extraction of that arm. When the binding held
a hole, a member read or method call threw "undefined is not
representable in the target union ..." for a record arm and "Value is
not an instance of 'Shape'" for a class arm, where Node throws "Cannot
read properties of undefined (reading 'a')".
The read now tests for undefined first and throws Node's TypeError, as
the two-arm path already does.
A generic binding inside a function that a nested function read before
the declaration lowered stores its instance into a TDZ box when the
declaration runs. That value was the instance's closure, which the
backend interns for module functions, so every evaluation of the
declaration stored the same function object. JavaScript makes a new one
each time. The stored value is now a closure over a lifted forwarder that
calls the instance, so each evaluation allocates a new closure; the
forwarder is created once per instance. Async and rest-parameter
bindings read this way report SC1090.
Corpus 7180 checks the identities.
When an event-loop callback calls an async function or steps a generator
that enters a store before its first await, the store becomes the
callback's at the end of the prefix. On the main slot it now ends with
that callback, as an enterWith there does: the copy schedules the tick
that clears the main slot, so the store does not reach later callbacks.
new tls.TLSSocket(socket, { isServer: true, secureContext, requestCert:
true }) skipped the options walk that refuses requestCert, so the server
asked for no client certificate and accepted every client, even with
rejectUnauthorized: true. With a secureContext, Node still reads the
socket's own options, so a truthy requestCert, SNICallback and
ALPNCallback now throw the fence there too.
A TLS socket's transport kept its 'secure' and 'session' listeners until
the socket was freed. A handshake that fails never fires 'secure', so a
listener that captures the socket kept the socket and its TLS state alive
after it closed. The transport now drops those listeners when the socket
settles, and ignores listeners added after that.
crypto.subtle.wrapKey exports a secret key (raw, raw-secret or jwk) and
encrypts it, and unwrapKey decrypts and imports one, as Node's
webcrypto.js does: the wrap algorithm normalizes through the wrapKey
registry (AES-KW) and then the encrypt registry (AES-GCM, AES-CBC and
AES-CTR with their dictionaries), the wrapping key's algorithm and
usages are checked, and a JWK payload is its JSON text, padded with
spaces to a multiple of 8 bytes for AES-KW. A wrapped JWK that is not
UTF-8 JSON with a kty member rejects with Node's DataError.
AES-KW is RFC 3394 key wrap over the vendored mbedTLS block cipher, in
scr_webcrypto_aes.c beside the other AES modes: wrapping takes a
multiple of 8 bytes and at least 16, unwrapping a multiple of 8 and at
least 24, and the integrity check value is compared in constant time.
The cipher's failures, and unwrapKey's import errors, reject on a later
loop turn as Node's job promises do.
This completes jose's symmetric key management: 7132 runs the real
jose 6.2.12 PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW,
A128KW and A256KW paths (EncryptJWT and jwtDecrypt) against Node.
getPublicKey replaces wrapKey as the refused method in the fenced
fixture and the JavaScript refusal test.
The key and signature errors formatted into fixed stack buffers and
passed snprintf's return value on as the message length. A message
longer than the buffer, such as "Invalid digest: " with a long algorithm
name from crypto.sign or crypto.verify, then copied bytes past the end of
the buffer into the error. The messages are now sized to their whole
text, so a long name appears in full, as in Node.
The received values in these messages also follow Node: strings as
util.inspect quotes them, cut at 128 characters, and large integers with
ERR_OUT_OF_RANGE's separators. A fractional, NaN or infinite
modulusLength reports "It must be an integer", as validateUint32 does.
The dense path of an element read that may be undefined (the
`%arr.idxOr` reads behind `a[i]` as `T | undefined`, the store
`a[x] = a[y]!`, and the element reads of the lowered array methods)
tested the index against the capacity, then against the hole-free prefix
(`filled`), and only above the prefix against the length and the slot's
state. `filled` is at most the length and the capacity (scr_array.h), so
an index below it needs no other test: the read now tests `filled` first
and takes the value path on that test alone. Above the prefix it tests
the capacity, the length and the state as before.
A double index first branches to the runtime lookup when it is not an
exact integer, then takes the same tests on its i64 value, which a 32-bit
target compares before truncating it.
`for (let lo = a, hi = b; lo < hi; lo++, hi--)` kept both counters as
doubles: the counted-loop match takes a single counter, so every
`a[lo]`/`a[hi]` converted and tested its index. A loop whose two counters
close in on each other by positive literal steps, are not written by the
body and index something now stores both as i64: on every body entry both
lie between the first start and the second. A start without a proven
integer range is tested at run time on a versioned loop, as a counted
loop's is.
The helpers of the typed-array callback methods (forEach, map, filter,
some, every, find, findIndex, findLast, findLastIndex, reduce,
reduceRight) called their callback through the closure's code pointer on
every element, and one helper served every call with the same signature,
so the callback was never inlined. A literal callback now gets a helper
of its own, as for the array methods, and the constant-callback census
accepts the typed-array helpers, so the helper calls that function
directly.
The visit is now a loop on its own counter, `for (let i = 0; i < a.length;
i++)` or down from the last index, instead of a loop on a counter
declared before it, so the counter is an integer and element reads need no
index test. reduce and reduceRight without an initial value read their
seed at the first visited index and start one past it.
Adds reduce, some and map callbacks to the typed-array-kernels workload.
Readable, Writable, Duplex, Transform and PassThrough take the
objectMode option, and a Duplex, Transform or PassThrough the
readableObjectMode and writableObjectMode options, as true or false
literals in an options object literal or in an options value. An
object-mode side holds any value that converts to a checked-dynamic
value: push, unshift, write, end, pipe, pipeline, for await, 'data'
listeners, Readable.from and the transform callback carry values in
TypeScript and JavaScript, and constructor-function streams and options
held in variables do the same. The object-mode high-water mark counts
values and defaults to 16. A readable buffer that mixes strings, Buffers
and other values holds them all, and push(undefined) and other values on
a byte-mode stream report ERR_INVALID_ARG_TYPE through 'error', as in
Node.
In typed code a write or transform callback, or a _write or _transform
method, may declare its chunk with any such type, which converts when
the callback runs. Writes pass the encoding argument to _write and
_transform as Node does: undefined when absent, null when a callback
took its place, otherwise the value unchanged. A parameter declared
string | null | undefined receives that value and one declared string
receives "" for undefined and null. write(chunk, encoding) on a
byte-mode stream encodes a string chunk with any known encoding and
throws ERR_UNKNOWN_ENCODING for an unknown one. Readable.from takes
typed Maps and Sets of any such value, and arrays that mix strings and
Buffers.
for await over a readable-only stream that will emit 'close' now ends
after 'close', as Node's eos does. Buffered writes that complete inside
clearBuffer run their callbacks in order, and 'finish' waits for a
deferred afterWrite that carries a callback, as Node's pendingcb does,
unless a _final callback schedules it.
The 'data' listener adapters take three slots and write and transform
callbacks read their chunk and encoding through runtime accessors, so
RUNTIME_ABI_VERSION is now 26.
A module `let` declared without an initializer whose type uses its null
pointer as the TDZ sentinel (a function, for example) is initialized when
its declaration runs, while its slot is still empty. markUninitializedWrites
marked the assignment statements that run after the declaration as
initializing, but not assignments in expression position. Those ran the
pointer check and threw "ReferenceError: Cannot access 'g' before
initialization" where Node stores the value, so the deferred-resolver
pattern `let resolve!: ...; new Promise((r) => (resolve = r))` failed, as
did `(h) => (g = h)` and `const r = (g = h)`.
An assignment expression to such a binding now becomes an initializing
`assign` whose value a temp holds and yields. Writes that can run before
the declaration keep the flag check, so they still throw Node's
ReferenceError.
A class defined inside a function whose base resolves to a known class
lowered to its class value alone, so the `extends` expression never ran.
Node evaluates it each time the definition runs, so its calls and other
effects were missing (`class Local extends select(2) {}` never called
select). The class value now follows the heritage expression's
evaluation when that expression can have effects: a call, construction,
assignment, update, delete, await or yield outside nested functions.
Module-scope classes already evaluated it.
The pretty-printed IR of the self-hosted LLVM emitter is close to the
JavaScript string length limit on CI checkouts, so any growth of the
emitter fails the test with "Invalid string length".
All Buffer.from paths over an untyped ArrayBuffer or SharedArrayBuffer now
go through one function that takes the byte offset explicitly, as Node's
fromArrayBuffer does. The start-offset RangeError prints the offset as
JavaScript does (-1.5, -1e+21, -Infinity) and builds its message without a
fixed-size buffer, which a large negative offset overran.
In `for (let i = 0; i < out.length; i++) { const j = i * 4; ... px[j + 1] }`
the fast copy of the typed-array loop version computed `j` as a double and
converted every index back. The version gives each affine index operand
its range under the guard's bound on the counter, but kept the range an
operand already had, and the counter of a loop to a length is only known
to be below 2^53, so `i * 4` stayed a double multiply. The version now
narrows the existing range to the guard's.
A constant number local whose initializer has an exact integer form now
keeps that integer in an i64 slot beside the double, and its reads carry
both, so `px[j + 1]` indexes with integer arithmetic. Only optimized
synchronous functions keep these integers, and every other declaration of
the local drops its integer, since one declaration is emitted once per
loop copy.
A generic factory declared to return a constructor type now also extends
when it returns an anonymous class expression (`return class { ... } as
Struct<S>`) or a `const` bound to one (`const Hold = class { ... }; return
Hold`). The result's class value is the class expression's own
constructor type; before, it was read through the class's name, so an
anonymous class refused with SC1090 and SC2008.
Generic arrow and function-expression bindings compile to shared
instances, and their declarations emit no store. Code that can run before
the declaration then diverged from Node.
- Inside a function, a nested function lowered before the declaration
reads the binding through a TDZ box that nothing initialized. That
covers closures above the declaration and hoisted function
declarations. Such a read threw a ReferenceError even after the
declaration ran. The declaration now stores the instance its declared
signature pins into that box.
- At module scope, calls and pinned value reads went straight to the
instances even when a hoisted function or an earlier closure ran them
before the declaration. A binding with such a use now has a flag that
its declaration sets.
- Uses that can run first check the flag.
- A let or const throws Node's ReferenceError before the arguments
run.
- A var runs the arguments and then throws "is not a function".
- Uses that follow the declaration stay direct calls: later statements,
closures created after it, its own initializer, and function
declarations that only such code calls.
A var binding inside a function that is called from a place that can run
before its declaration would need a per-call flag shared with its
closures. That shape now fails with SC1090 instead of calling the
instance.