Summary
#8133 named four js_class_method_bind sites binding a movable heap string's interior. Fixing it (#8177) surfaced six in perry-runtime — and the same defect systemically in perry-stdlib's handle-property dispatch, where eight sub-dispatchers capture the forwarded name pointer directly.
The contract being violated
js_class_method_bind(instance, name_ptr, name_len) stores the name pointer in the bound closure's capture, and dispatch_bound_method (closure/dispatch/bound.rs:12) re-reads it at call time. So the pointer must outlive the bind — it cannot point into a movable GC heap string, whose contents are unreachable once a collection moves it.
Where it still happens
Eight sub-dispatchers in perry-stdlib forward the caller's pointer straight into the bind:
sqlite — four sites
tls — two sites
emitter_als — two sites
Reachable from ordinary code that stores a method rather than calling it:
const f = db.run; // sqlite
const g = emitter.on; // emitter_als
const h = als.getStore; // emitter_als
Also found on perry-runtime's primitive-receiver arm ((5).toString), which #8177 did not cover.
Why this will not show up by accident
#8177 established the reproduction conditions, and they are narrow:
- A literal
dec.decode lowers to rodata and never reaches these arms at all. Only a computed key does — const k = "dec" + "ode"; obj[k].
- Pre-fix, the symptom was a silent wrong answer:
decode=undefined followed by a throw, where node prints decode=hi. No instrument fired.
- Under
PERRY_GC_ZEAL=1 PERRY_GC_PROTECT_FROMSPACE=1 it became a SIGBUS reported as RETIRED FROM-SPACE … retired_by_minor=#0.
So a test that binds a literal name and calls it immediately will pass on broken code. Any fixture here needs a computed key and enough allocation to actually move the string; #8177's used ~400k allocations.
Suggested shape
#8177's fix is worth copying rather than re-deriving: it replaced the is_timer_handle_method_key predicate with a 'static-returning lookup and removed key_ptr/key_len from text_handle_property entirely. With no predicate and no pointer parameter, the bug is unwritable rather than merely fixed. The stdlib dispatchers can take the same treatment.
Related
#8133 (closed by #8177), #7747 (the first two sites), docs/src/internals/gc-rooting-invariant.md.
Summary
#8133 named four
js_class_method_bindsites binding a movable heap string's interior. Fixing it (#8177) surfaced six in perry-runtime — and the same defect systemically in perry-stdlib's handle-property dispatch, where eight sub-dispatchers capture the forwarded name pointer directly.The contract being violated
js_class_method_bind(instance, name_ptr, name_len)stores the name pointer in the bound closure's capture, anddispatch_bound_method(closure/dispatch/bound.rs:12) re-reads it at call time. So the pointer must outlive the bind — it cannot point into a movable GC heap string, whose contents are unreachable once a collection moves it.Where it still happens
Eight sub-dispatchers in perry-stdlib forward the caller's pointer straight into the bind:
sqlite— four sitestls— two sitesemitter_als— two sitesReachable from ordinary code that stores a method rather than calling it:
Also found on perry-runtime's primitive-receiver arm (
(5).toString), which #8177 did not cover.Why this will not show up by accident
#8177 established the reproduction conditions, and they are narrow:
dec.decodelowers to rodata and never reaches these arms at all. Only a computed key does —const k = "dec" + "ode"; obj[k].decode=undefinedfollowed by a throw, where node printsdecode=hi. No instrument fired.PERRY_GC_ZEAL=1 PERRY_GC_PROTECT_FROMSPACE=1it became a SIGBUS reported asRETIRED FROM-SPACE … retired_by_minor=#0.So a test that binds a literal name and calls it immediately will pass on broken code. Any fixture here needs a computed key and enough allocation to actually move the string; #8177's used ~400k allocations.
Suggested shape
#8177's fix is worth copying rather than re-deriving: it replaced the
is_timer_handle_method_keypredicate with a'static-returning lookup and removedkey_ptr/key_lenfromtext_handle_propertyentirely. With no predicate and no pointer parameter, the bug is unwritable rather than merely fixed. The stdlib dispatchers can take the same treatment.Related
#8133 (closed by #8177), #7747 (the first two sites),
docs/src/internals/gc-rooting-invariant.md.