Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions crates/perry-codegen/src/expr/property_get/generic_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,25 @@ pub(crate) fn lower_generic_property_get(
let object_type_ok = ctx.block().icmp_eq(I32, &object_type, "1");
let is_object = ctx.block().and(I1, &is_object, &object_type_ok);

// #6080: a receiver that has ever had a property/accessor descriptor
// installed (`Object.defineProperty`) needs descriptor-aware dispatch —
// an accessor must fire on reads, a non-writable slot must reject stores.
// The PIC hit path is a raw slot load: if the site was primed on a plain
// data property and `defineProperty` later converts that key to a getter
// (or a different descriptor), `keys_array` is unchanged, so the stale
// hit path would return the raw slot and bypass the getter entirely.
// OBJ_FLAG_HAS_DESCRIPTORS lives in the GcHeader `_reserved` i16 at
// offset -6; force a miss (→ `js_object_get_field_ic_miss`, which honors
// descriptors) whenever it is set. Mirrors the guard in
// `class_field_inline_guard.rs`. Cost: 1 sub + load i16 + and + cmp,
// folded into the existing `hit` cond_br.
let reserved_addr = ctx.block().sub(I64, &safe_obj_handle, "6");
let reserved_ptr = ctx.block().inttoptr(I64, &reserved_addr);
let reserved = ctx.block().load(crate::types::I16, &reserved_ptr);
let has_desc = ctx.block().and(crate::types::I16, &reserved, "2048"); // OBJ_FLAG_HAS_DESCRIPTORS (0x800)
let no_desc = ctx.block().icmp_eq(crate::types::I16, &has_desc, "0");
let is_object = ctx.block().and(I1, &is_object, &no_desc);

// Load obj->keys_array at offset 16 of ObjectHeader.
let keys_addr = ctx.block().add(I64, &safe_obj_handle, "16");
let keys_ptr_p = ctx.block().inttoptr(I64, &keys_addr);
Expand Down
21 changes: 21 additions & 0 deletions test-files/test_gap_6080_defineproperty_after_prime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// #6080: the read inline-cache must not bypass a descriptor installed AFTER
// the call-site was primed. Priming f(a) caches a raw slot for `x`; a later
// Object.defineProperty converting `x` to a getter (or redefining the data
// value) must be honored on the next read, not served stale from the cache.
function f(o: any) { return o.x; }
const a: any = { x: 1 };
console.log(f(a)); // prime -> 1
Object.defineProperty(a, "x", { get() { return 42; } });
console.log(f(a)); // must observe the getter -> 42

function g(o: any) { return o.y; }
const b: any = { y: 10 };
g(b); // prime
Object.defineProperty(b, "y", { value: 99, writable: false });
console.log(g(b)); // must observe redefined value -> 99

// A receiver that gets a descriptor must still read its OTHER, untouched
// own keys correctly (slow path), and normal objects keep hitting the cache.
const d: any = { p: 1, q: 2 };
Object.defineProperty(d, "p", { get() { return 100; } });
console.log(d.q, d.p); // 2 100
Loading