-
-
Notifications
You must be signed in to change notification settings - Fork 162
fix(runtime): observe prototype replacement in method calls #9169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| //! #9131: a per-instance `[[Prototype]]` override wins over the class vtable. | ||
| //! | ||
| //! Split out of `get_field_by_name_tail.rs`, which is at the 2000-line cap. | ||
| //! Both of that file's own-key misses — the keyless arm and the shaped-receiver | ||
| //! arm — ask the same question, so it lives here once instead of twice. | ||
|
|
||
| use crate::object::ObjectHeader; | ||
| use crate::value::JSValue; | ||
|
|
||
| /// An explicit per-instance `[[Prototype]]` REPLACES the class's declaration | ||
| /// prototype; it is not an extra link in front of the original vtable. So when | ||
| /// the own-key scan misses, walk that authoritative chain before exposing class | ||
| /// getters or methods, and do not resurrect the old class surface when the | ||
| /// custom chain also misses — hence `Some(undefined)` rather than `None` once | ||
| /// an override is present. | ||
| /// | ||
| /// `None` means no override was installed and the caller keeps its existing | ||
| /// class-vtable fallback. | ||
| pub(super) fn inherited_field_if_overridden( | ||
| obj: *const ObjectHeader, | ||
| key: *const crate::string::StringHeader, | ||
| ) -> Option<JSValue> { | ||
| if key.is_null() { | ||
| return None; | ||
| } | ||
| if !crate::object::prototype_chain::object_has_prototype_override(obj as usize) { | ||
| return None; | ||
| } | ||
| Some( | ||
| crate::object::prototype_chain::resolve_inherited_field(obj as usize, key) | ||
| .unwrap_or_else(JSValue::undefined), | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1249,6 +1249,40 @@ pub unsafe extern "C-unwind" fn js_native_call_method( | |
| // dispatch tower below it is orders of magnitude more expensive. | ||
| let object = || object_handle.get_nanbox_f64(); | ||
| let jsval = || JSValue::from_bits(object().to_bits()); | ||
|
|
||
| // An explicit `Object.setPrototypeOf(instance, proto)` replaces the | ||
| // instance's class prototype. Resolve a method value through ordinary | ||
| // property lookup before any class/native dispatch: that lookup preserves | ||
| // own-property precedence and, for a miss, the per-instance chain is | ||
| // authoritative rather than falling back to the original class vtable. | ||
| if jsval().is_pointer() { | ||
| let candidate = jsval().as_pointer::<ObjectHeader>() as usize; | ||
| if crate::value::addr_class::is_above_handle_band(candidate) | ||
| && crate::object::is_valid_obj_ptr(candidate as *const u8) | ||
| && super::prototype_chain::object_has_prototype_override(candidate) | ||
| { | ||
| let method_key = | ||
| crate::string::js_string_from_bytes(method_name.as_ptr(), method_name.len() as u32); | ||
| if !method_key.is_null() { | ||
| let receiver = object(); | ||
| let receiver_ptr = | ||
| JSValue::from_bits(receiver.to_bits()).as_pointer::<ObjectHeader>(); | ||
| let method = super::js_object_get_field_by_name(receiver_ptr, method_key); | ||
| let method_handle = root_scope.root_nanbox_f64(f64::from_bits(method.bits())); | ||
| let receiver = object(); | ||
| let bound = crate::closure::clone_closure_rebind_this( | ||
| method_handle.get_nanbox_f64().to_bits(), | ||
| receiver, | ||
| ); | ||
| let args = refreshed_args(); | ||
| return crate::closure::js_native_call_value( | ||
| f64::from_bits(bound), | ||
| args.as_ptr(), | ||
| args.len(), | ||
| ); | ||
|
Comment on lines
+1278
to
+1282
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: printf '%s\n' '--- repository guidance ---'
find /tmp/coderabbit-repo-knowledge/perryts-perry-d4a878bc -type f -name '*.md' -maxdepth 3 -print
printf '%s\n' '--- changed hunk ---'
git diff -- crates/perry-runtime/src/object/native_call_method.rs | sed -n '1,220p'
printf '%s\n' '--- target context ---'
sed -n '1190,1325p' crates/perry-runtime/src/object/native_call_method.rs
printf '%s\n' '--- IMPLICIT_THIS references ---'
rg -n -C 4 'IMPLICIT_THIS|js_native_call_value' crates/perry-runtime/src/object/native_call_method.rs crates/perry-runtime/src | head -240Repository: PerryTS/perry Length of output: 36105 🏁 Script executed: printf '%s\n' '--- relevant local dispatch helpers ---'
sed -n '280,345p' crates/perry-runtime/src/object/native_call_method.rs
sed -n '835,910p' crates/perry-runtime/src/object/native_call_method.rs
sed -n '1245,1290p' crates/perry-runtime/src/object/native_call_method.rs
printf '%s\n' '--- bound helper definitions and call implementation ---'
rg -n -C 8 'fn clone_closure_rebind_this|clone_closure_rebind_this|pub fn js_native_call_value|fn js_native_call_value|js_native_call_value' crates/perry-runtime/src
printf '%s\n' '--- named regression test source ---'
rg -n -C 12 'fused_next_routes_other_iterators_through_the_generic_arm|fused_for_of_tests' .Repository: PerryTS/perry Length of output: 50370 🏁 Script executed: printf '%s\n' '--- exact clone helper ---'
rg -n 'clone_closure_rebind_this' crates/perry-runtime/src/closure.rs crates/perry-runtime/src/closure crates/perry-runtime/src
printf '%s\n' '--- exact native call helper ---'
rg -n 'js_native_call_value' crates/perry-runtime/src/closure.rs crates/perry-runtime/src/closure
printf '%s\n' '--- target test definition ---'
rg -n -C 20 'fused_next_routes_other_iterators_through_the_generic_arm' crates/perry-runtime
printf '%s\n' '--- iterator override dispatch ---'
sed -n '395,515p' crates/perry-runtime/src/object/iterator_prototypes.rsRepository: PerryTS/perry Length of output: 27544 🏁 Script executed: printf '%s\n' '--- clone helper implementation ---'
sed -n '1170,1245p' crates/perry-runtime/src/closure/dynamic_props.rs
printf '%s\n' '--- value-call receiver and dispatch logic ---'
sed -n '1,150p' crates/perry-runtime/src/closure/dispatch/value_call.rs
printf '%s\n' '--- closure receiver helpers and flags ---'
rg -n -C 6 'CAPTURES_THIS|this_value|implicit_this|rebind' crates/perry-runtime/src/closure crates/perry-runtime/src/object/native_call_method.rs | head -260
printf '%s\n' '--- test module context ---'
sed -n '520,615p' crates/perry-runtime/src/collection_iter_object.rsRepository: PerryTS/perry Length of output: 44147 🏁 Script executed: printf '%s\n' '--- complete clone helper ---'
sed -n '1195,1265p' crates/perry-runtime/src/closure/dynamic_props.rs
printf '%s\n' '--- complete value-call dispatch tail ---'
sed -n '145,285p' crates/perry-runtime/src/closure/dispatch/value_call.rs
printf '%s\n' '--- js_for_of_next and prototype-override predicate ---'
rg -n -C 18 'js_for_of_next|object_has_prototype_override' crates/perry-runtime/src/collection_iter_object.rs crates/perry-runtime/src/object crates/perry-runtime/src
printf '%s\n' '--- array iterator construction and next method registration ---'
rg -n -C 14 'array_values_iter|array_iterator_next_thunk|ARRAY_ITERATOR_PROTOTYPE_PTR|next' crates/perry-runtime/src/array/iterator.rs crates/perry-runtime/src/object/iterator_prototypes.rs | head -260Repository: PerryTS/perry Length of output: 50370 🏁 Script executed: printf '%s\n' '--- iterator thunk definitions ---'
rg -n -C 12 'array_iterator_next_thunk|map_iterator_next_thunk|set_iterator_next_thunk|string_iterator_next_thunk|js_register.*iterator|ITERATOR_PROTOTYPE_PTR' crates/perry-runtime/src/object/iterator_prototypes.rs crates/perry-runtime/src/array/iterator.rs
printf '%s\n' '--- prototype materialization and override flag writes ---'
rg -n -C 10 'PROTO_OVERRIDE|object_has_prototype_override|setPrototypeOf|set_prototype|materialize.*prototype|prototype.*override' crates/perry-runtime/src/object/prototype_chain.rs crates/perry-runtime/src/object crates/perry-runtime/src/array/iterator.rs | head -260
printf '%s\n' '--- closure representation for native thunks ---'
rg -n -C 10 'array_iterator_next_thunk|func_ptr.*iterator|js_closure_alloc.*thunk|CLOSURE_TYPE_TAG|global_this_builtin_noop_thunk' crates/perry-runtime/src/closure crates/perry-runtime/src/object/iterator_prototypes.rs | head -220Repository: PerryTS/perry Length of output: 50369 Set 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| } | ||
| } | ||
| } | ||
| // RAII recursion depth guard: prevent stack overflow from circular module deps. | ||
| // The guard auto-decrements on drop, covering all ~20 return points in this function. | ||
| // When max depth is hit, return a pointer to a static empty object instead of undefined. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Root
method_keybefore property lookup.method_keyis a raw GC pointer.js_object_get_field_by_namecan traverse user-defined prototype state and invoke an accessor that allocates. A moving collection can then invalidate this pointer while the lookup still uses it.Store the key in a
RuntimeHandleScopehandle and reload the rewritten pointer when calling the lookup.Based on learnings, raw Rust pointer locals are neither GC roots nor reliable pins across allocating or user-code-invoking operations.
🤖 Prompt for AI Agents
Source: Learnings