fix(typedarray): throw on prototype accessor reads off-instance (#4140)#4174
Merged
Conversation
The `%TypedArray%.prototype` `buffer`/`byteLength`/`byteOffset`/`length` getters are installed as reflection-only builtin accessors that deliberately don't flip the `ACCESSORS_IN_USE` hot-path gate (#2060). On top of that, the per-kind prototypes (`Uint8Array.prototype`, …) carry no recorded `[[Prototype]]` link in the chain the property getter walks — they resolve to the shared `%TypedArray%.prototype` only through `Object.getPrototypeOf`'s `OBJ_FLAG_TYPED_ARRAY_PROTO` flag. So a plain value read off the prototype (`Uint8Array.prototype.buffer`) never reached the inherited accessor and silently returned the empty slot (`undefined`) instead of Node's `TypeError` (test262 `built-ins/TypedArrayConstructors/<T>/prototype/not-typedarray-object.js`, 11 types). Resolve the getter off the shared intrinsic for both the intrinsic itself and any object flagged `OBJ_FLAG_TYPED_ARRAY_PROTO`. A genuine concrete typed array short-circuits far earlier in `js_object_get_field_by_name`, so the receiver here is never a real instance and the getter throws — matching Node. Guarded by a cheap key filter + heap-pointer/`GC_TYPE_OBJECT` check so ordinary reads (and non-pointer `obj` values threaded through the generic getter) pay nothing and are never dereferenced. New parity fixture `typedarray-prototype-accessor-throws.ts` exercises all 11 typed-array types, the intrinsic, an `Object.create`-derived object, and real instances; byte-identical to `node --experimental-strip-types`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the
not-typedarray-objectfamily in #4140PR #4143 fixed the headline
BYTES_PER_ELEMENTgap. The one remainingchecklist item it marked "could not reproduce" —
not-typedarray-object.js(×11 concrete types) — was tested wrong (it called the abstract
%TypedArray%()rather than reading the prototype's accessor). It is in fact still failing on
main:The prototype object is an ordinary object with no
[[ViewedArrayBuffer]]slot, so reading these accessors off it must throw
TypeError.Root cause
The four
%TypedArray%.prototypegetters are installed as reflection-onlybuiltin accessors that don't flip the
ACCESSORS_IN_USEhot-path gate (#2060),and the per-kind prototypes carry no recorded
[[Prototype]]link in thechain the property getter walks — they reach the shared
%TypedArray%.prototypeonly via
Object.getPrototypeOf'sOBJ_FLAG_TYPED_ARRAY_PROTOflag. So a plainvalue read off the prototype never reached the inherited accessor and returned
the empty slot.
Fix
In
js_object_get_field_by_name, for the four accessor names, resolve thegetter off the shared intrinsic when
objis the intrinsic itself or any objectflagged
OBJ_FLAG_TYPED_ARRAY_PROTO. Real instances short-circuit far earlier,so the receiver here is never a concrete typed array and the getter throws —
matching Node. Guarded by a cheap key filter + heap-pointer /
GC_TYPE_OBJECTcheck so ordinary reads (and non-pointer
objvalues threaded through thegeneric getter) pay nothing and are never dereferenced.
Verification
typedarray-prototype-accessor-throws.ts— all 11 types, theintrinsic, an
Object.create(Uint8Array.prototype)derived object, and realinstances — byte-identical to
node --experimental-strip-types.globalsnode-suite sweep on an isolated build: PASS=40 / FAIL=9, the exactsame 9 pre-existing failures as the unmodified base (no new regressions); the
new fixture and the fix(typedarray): BYTES_PER_ELEMENT own props on constructor + prototype (#4140) #4143
typedarray-constructor-own-props.tsboth pass.cargo test -p perry-runtimegreen;cargo fmt --checkclean.Out of scope (pre-existing, filed-worthy separately)
(arr as any).length = -1on a regular array segfaults — reproduces on a cleanmainbuild (bothNO_AUTO_OPTIMIZEand auto-optimize), unrelated to thischange. This is what makes
array-length-invalid.ts/structured-clone-transfer.tsfail in the sweep above.
propertyIsEnumerableon a TA prototype returns[object Object]not a boolean.Function.prototype-style buffer getter.call(realInstance)throws (Perrydoesn't fully model
ArrayBufferbehind a view).