fix(runtime): link Object.create synthetic class_id to original class chain (#1805) - #2244
Merged
Merged
Conversation
… chain (#1805) `Object.create(Object.getPrototypeOf(instance))` produced an object that wasn't `instanceof` the original class, even though property reads and getter dispatch through the synthetic-prototype chain worked correctly (the #711 / #809 wiring). `js_instanceof`'s chain walk keys off `get_parent_class_id`, but the synthetic class id allocated by `js_object_create` had no parent edge linking it to the original class — so the walk stopped at the unregistered synthetic id and returned false. Fix: at synthetic-cid registration time, also record `(synthetic_cid → proto_ptr.class_id)` in CLASS_REGISTRY when the proto pointer carries a non-zero class_id. In Perry's model `Object.getPrototypeOf(instance)` returns the instance itself (see `js_object_get_prototype_of`), so `proto_ptr` is a real class instance whose `class_id` field IS the user class's id. Linking that as the synthetic cid's parent lets the `js_instanceof` chain walk reach the original class — chained `Object.create` cases also work because the chain transitively walks `synthetic_2 → synthetic_1 → ClassId`. Surfaced by effect's `SchemaAST.annotations`, which clones AST nodes via `Object.create(Object.getPrototypeOf(ast), Object.getOwnPropertyDescriptors(ast))`. Refs #1758, #809. ## Test plan - [x] `test-files/test_issue_1805_object_create_instanceof.ts` covers plain `Object.create(proto)`, the descriptor form, the chained case, and the `Object.create(null)` negative — byte-identical with Node. - [x] `cargo fmt --all -- --check` clean. - [x] `cargo test --release -p perry-runtime --lib` — 2 flaky failures (`gc::tests::copying::test_copying_minor_rewrites_exact_object_pointer_slot_only`, `object::tests::text_encoding_stream_globals_construct_readable_writable_shape`) both pass in isolation; pre-existing thread-state flakiness.
This was referenced May 29, 2026
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.
Summary
Fix #1805 —
Object.create(Object.getPrototypeOf(instance))producedan object that wasn't
instanceofthe original class, even thoughproperty reads and getter dispatch through the synthetic-prototype
chain worked correctly (the #711 / #809 wiring).
js_instanceof's chain walk keys offget_parent_class_id, but thesynthetic class id allocated by
js_object_createhad no parent edgelinking it to the original class — so the walk stopped at the
unregistered synthetic id and returned false.
Fix: at synthetic-cid registration time, also record
(synthetic_cid → proto_ptr.class_id)inCLASS_REGISTRYwhen the proto pointercarries a non-zero class_id. In Perry's model
Object.getPrototypeOf(instance)returns the instance itself (seejs_object_get_prototype_of), soproto_ptrhere is a real classinstance whose
class_idfield IS the user class's id. Linking it asthe synthetic cid's parent lets the chain walk reach the original
class; chained
Object.createcases keep working because the walktransitively reaches the root via
synthetic_2 → synthetic_1 → ClassId.Surfaced by effect's
SchemaAST.annotations, which clones AST nodesvia
Object.create(Object.getPrototypeOf(ast), Object.getOwnPropertyDescriptors(ast)). Refs #1758, #809.Test plan
test-files/test_issue_1805_object_create_instanceof.tscoversplain
Object.create(proto), the descriptor form, the chained case,and the
Object.create(null)negative — byte-identical with Node.cargo fmt --all -- --checkclean.cargo test --release -p perry-runtime --lib— 2 flaky failures(
gc::tests::copying::test_copying_minor_rewrites_exact_object_pointer_slot_only,object::tests::text_encoding_stream_globals_construct_readable_writable_shape)both pass in isolation; pre-existing thread-state flakiness.