Skip to content
Closed
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
4 changes: 4 additions & 0 deletions changelog.d/11711-strict-arguments-descriptors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Strict escaping `arguments` objects now keep their `length` and restricted
`callee` attributes in the shared key layout, with the thrower accessor in the
object's own slot. Construction no longer installs per-object entries in the
address-keyed property and accessor descriptor tables.
41 changes: 41 additions & 0 deletions crates/perry-runtime/src/gc/tests/arguments_objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,23 @@ fn arguments_bulk_construction_handles_empty_and_uncached_arities() {
let accessor = get_accessor_descriptor(args as usize, "callee").unwrap();
assert_eq!(accessor.get, accessor.set);
assert_ne!(accessor.get, 0);
let descriptors = &crate::state::state().descriptors;
assert!(
!descriptors
.property_descriptors
.borrow()
.keys()
.any(|(owner, _)| *owner == args as usize),
"an arguments object must not have address-keyed attributes"
);
assert!(
!descriptors
.accessor_descriptors
.borrow()
.keys()
.any(|(owner, _)| *owner == args as usize),
"the restricted callee must live in its own slot"
);
}
}

Expand All @@ -121,6 +138,30 @@ fn arguments_shared_keys_survive_moving_gc_without_a_live_arguments_owner() {
assert_eq!(get(after, "length").bits(), 3.0f64.to_bits());
}

#[test]
fn restricted_callee_accessor_survives_moving_gc_without_descriptor_entries() {
let _guard = CopyingNurseryTestGuard::new(1);
register_scanners();
let undefined = f64::from_bits(crate::value::TAG_UNDEFINED);
let args = arguments(&[1.0], undefined, true);
js_shadow_slot_set(0, ptr_bits(args as usize));
gc_collect_minor();

let moved = (js_shadow_slot_get(0) & POINTER_MASK) as *mut ObjectHeader;
assert_ne!(moved, args, "the arguments object must actually evacuate");
let attrs = get_property_attrs(moved as usize, "callee").unwrap();
assert!(!attrs.writable() && !attrs.enumerable() && !attrs.configurable());
let accessor = get_accessor_descriptor(moved as usize, "callee").unwrap();
assert_eq!(accessor.get, accessor.set);
assert_ne!(accessor.get, 0);
assert!(!crate::state::state()
.descriptors
.accessor_descriptors
.borrow()
.keys()
.any(|(owner, _)| *owner == moved as usize));
}

#[test]
fn arguments_values_callee_and_mapping_survive_moving_gc() {
let _guard = CopyingNurseryTestGuard::new(1);
Expand Down
30 changes: 16 additions & 14 deletions crates/perry-runtime/src/object/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,20 +395,22 @@ fn arguments_object_alloc(
if restricted_callee {
let thrower = thrower_closure_value();
obj.with_mut_ptr::<ObjectHeader, _>(|obj| {
set_property_attrs(
obj as usize,
"length".to_string(),
PropertyAttrs::new(true, false, true),
);
super::descriptor_state::install_fresh_accessor_property(
obj as usize,
"callee".to_string(),
AccessorDescriptor {
get: thrower.to_bits(),
set: thrower.to_bits(),
},
PropertyAttrs::new(false, false, false),
);
// Both attributes were born in the canonical key layout. The
// restricted callee's getter and setter live in its own value
// slot, as they do for ordinary accessor properties. Installing
// descriptors here would re-edit that layout on every call and
// leave entries in the address-keyed descriptor tables.
unsafe {
super::accessor_pair::store_own_accessor(
obj as usize,
"callee",
Some(super::accessor_pair::pair_from(&AccessorDescriptor {
get: thrower.to_bits(),
set: thrower.to_bits(),
})),
);
}
super::descriptor_state::note_accessor_born_with_keys(obj as usize);
});
} else {
obj.with_mut_ptr(|obj| {
Expand Down
7 changes: 7 additions & 0 deletions crates/perry-runtime/src/object/descriptor_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,13 @@ pub(crate) fn note_attrs_born_with_keys(obj: usize) {
GLOBAL_DESCRIPTORS_IN_USE.store(true, Ordering::Relaxed);
}

/// An accessor born in the object's attributed key layout also needs the
/// read-path accessor gate, even though no descriptor install runs.
pub(crate) fn note_accessor_born_with_keys(obj: usize) {
note_attrs_born_with_keys(obj);
state().descriptors.accessors_in_use.set(true);
}

/// Look up the property descriptor for (obj, key). Returns None if no entry exists,
/// in which case the JS default `{ writable: true, enumerable: true, configurable: true }` applies.
pub(crate) fn get_property_attrs(obj: usize, key: &str) -> Option<PropertyAttrs> {
Expand Down
1 change: 0 additions & 1 deletion scripts/ci_e2e_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@
"temp_root_operand_temporaries",
"typed_array_rmw_8692",
"typed_array_update_lowering",
"typed_shape_declared_at_allocation",
"typed_shape_descriptor",
"typed_shape_descriptors",
"system_boolean_result",
Expand Down
Loading