Severity: P1 (silent wrong property value; getter bypass)
Found by: audit fable-audit-perry-2.md (code-verified against emitted IR).
Two coherence holes in the inline property-get PIC (@perry_ic_N caches), both mirroring bug classes already fixed elsewhere.
(a) ABA staleness — cache keyed on a raw keys-array address, never GC-registered/invalidated
The inline hit predicate is obj->keys_array == cache[0] && keys != 0 (+ gc/object-type checks); ic_miss primes cache[0] = keys as i64 (crates/perry-codegen/src/expr/property_get/generic_dispatch.rs:319-340, object/field_get_set/ic_miss.rs:385-386). The @perry_ic_N globals are zeroinitializer, never registered with the GC, never rewritten on evacuation, never cleared on sweep. Dynamically-built objects' keys arrays are ordinary collectable allocations; when one dies and its address is recycled by a different-shape keys array, the pointer-match falsely hits and loads a wrong slot — silently. The sibling transition cache had this exact production bug and got content-validation in #6006 (object/mod.rs:824-837, "silently mis-places property values at bundle scale"); the read PIC has no equivalent.
- Fix: pin/immortalize
GC_FLAG_SHAPE_SHARED keys arrays (they are exactly the cacheable population), or add a global GC-generation epoch checked beside the cache.
(b) defineProperty accessor installed after priming is bypassed
Converting an existing data prop to an accessor flips ACCESSORS_IN_USE and the per-object OBJ_FLAG_HAS_DESCRIPTORS, but the generic PIC hit path (generic_dispatch.rs:351-371) checks neither the disable flag nor the descriptor bit (only ic_miss's can_cache stops future priming), and converting the prop leaves keys_array untouched — so a previously-primed site keeps returning the stale raw slot instead of invoking the getter.
function f(o){ return o.x; } const a = { x: 1 };
f(a); Object.defineProperty(a, "x", { get(){ return 42; } });
console.log(f(a)); // node: 42 perry: 1
- Fix: fold
OBJ_FLAG_HAS_DESCRIPTORS (already in the GcHeader being dereferenced) into the hit predicate — one load+and+icmp, mirroring the class-field guard at class_field_inline_guard.rs:152.
Confidence: high (emitted IR shows no descriptor/epoch check on the hit path).
Severity: P1 (silent wrong property value; getter bypass)
Found by: audit
fable-audit-perry-2.md(code-verified against emitted IR).Two coherence holes in the inline property-get PIC (
@perry_ic_Ncaches), both mirroring bug classes already fixed elsewhere.(a) ABA staleness — cache keyed on a raw keys-array address, never GC-registered/invalidated
The inline hit predicate is
obj->keys_array == cache[0] && keys != 0(+ gc/object-type checks);ic_missprimescache[0] = keys as i64(crates/perry-codegen/src/expr/property_get/generic_dispatch.rs:319-340,object/field_get_set/ic_miss.rs:385-386). The@perry_ic_Nglobals arezeroinitializer, never registered with the GC, never rewritten on evacuation, never cleared on sweep. Dynamically-built objects' keys arrays are ordinary collectable allocations; when one dies and its address is recycled by a different-shape keys array, the pointer-match falsely hits and loads a wrong slot — silently. The sibling transition cache had this exact production bug and got content-validation in #6006 (object/mod.rs:824-837, "silently mis-places property values at bundle scale"); the read PIC has no equivalent.GC_FLAG_SHAPE_SHAREDkeys arrays (they are exactly the cacheable population), or add a global GC-generation epoch checked beside the cache.(b) defineProperty accessor installed after priming is bypassed
Converting an existing data prop to an accessor flips
ACCESSORS_IN_USEand the per-objectOBJ_FLAG_HAS_DESCRIPTORS, but the generic PIC hit path (generic_dispatch.rs:351-371) checks neither the disable flag nor the descriptor bit (onlyic_miss'scan_cachestops future priming), and converting the prop leaveskeys_arrayuntouched — so a previously-primed site keeps returning the stale raw slot instead of invoking the getter.OBJ_FLAG_HAS_DESCRIPTORS(already in the GcHeader being dereferenced) into the hit predicate — one load+and+icmp, mirroring the class-field guard atclass_field_inline_guard.rs:152.Confidence: high (emitted IR shows no descriptor/epoch check on the hit path).