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
37 changes: 21 additions & 16 deletions crates/perry-runtime/src/array/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1541,39 +1541,44 @@ pub(crate) fn object_sort(recv: f64, cmp_validated: *const ClosureHeader) -> f64
};
let len = al_length(recv);
unsafe {
// Rooted temp array: keeps accessor-produced values alive across
// comparator calls (a Rust Vec would be invisible to the GC scan).
let temp = js_array_alloc_with_length(len.clamp(0, u32::MAX as i64) as u32);
let temp_elems = (temp as *mut u8).add(std::mem::size_of::<ArrayHeader>()) as *mut f64;
// Root BOTH the receiver value and the collection temp for the whole
// protocol: `al_has`/`al_get`/`al_set` fire user accessors (and the
// comparator runs inside `sort_rooted_values`) — any of them can
// allocate and sweep or move either object, so every raw pointer is
// re-derived from its rooted handle after each such call.
let scope = crate::gc::RuntimeHandleScope::new();
let recv_handle = scope.root_nanbox_f64(recv);
Comment on lines 1542 to +1550

Copy link
Copy Markdown

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP --type=rust -C3 '\bfn al_length\b' crates/perry-runtime/src/array/generic.rs
rg -nP --type=rust -C4 '\bobject_sort\s*\(' crates/perry-runtime/src/array

Repository: PerryTS/perry

Length of output: 4470


🏁 Script executed:

sed -n '260,340p' crates/perry-runtime/src/array/generic.rs
printf '\n---\n'
sed -n '1536,1575p' crates/perry-runtime/src/array/generic.rs
printf '\n---\n'
sed -n '520,560p' crates/perry-runtime/src/array/sort.rs
printf '\n---\n'
sed -n '630,660p' crates/perry-runtime/src/array/sort.rs

Repository: PerryTS/perry

Length of output: 9530


🏁 Script executed:

sed -n '1,120p' crates/perry-runtime/src/array/sort.rs
printf '\n---\n'
rg -nP --type=rust -C3 '\bnon_array_object_receiver\b' crates/perry-runtime/src

Repository: PerryTS/perry

Length of output: 8521


Root recv before al_length in object_sort

al_length(recv) can run user code for object receivers (length accessors/proxy get) and can also allocate the "length" key string. js_array_sort_default and js_array_sort_with_comparator pass a raw receiver into object_sort, so root it before the length read; rooting afterward leaves a moving-GC window.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-runtime/src/array/generic.rs` around lines 1542 - 1550, In
object_sort, root the receiver before calling al_length(recv) because that
length lookup can invoke user code and allocate, leaving a GC/moving-window if
recv is still raw. Move the RuntimeHandleScope creation and recv_handle rooting
ahead of the length read, and keep using the rooted handle consistently in
js_array_sort_default and js_array_sort_with_comparator paths where object_sort
is entered.

let temp = super::sort::RootedArrayElems::new(
&scope,
js_array_alloc_with_length(len.clamp(0, u32::MAX as i64) as u32),
);
let mut count = 0usize;
let mut undef_count = 0usize;
for j in 0..len {
if al_has(recv, j) {
let v = al_get(recv, j);
if al_has(recv_handle.get_nanbox_f64(), j) {
let v = al_get(recv_handle.get_nanbox_f64(), j);
if v.to_bits() == TAG_UNDEFINED {
undef_count += 1;
} else {
// GC_STORE_AUDIT(BARRIERED): temp collection array rebuilt below.
ptr::write(temp_elems.add(count), v);
temp.set(count, v);
count += 1;
}
}
}
(*temp).length = count as u32;
rebuild_array_layout(temp);
super::sort::sort_rooted_values(temp_elems, count, cmp);
rebuild_array_layout(temp);
(*temp.arr()).length = count as u32;
rebuild_array_layout(temp.arr());
let _ = super::sort::sort_rooted_values(temp.arr(), count, cmp);
for j in 0..count {
al_set(recv, j as i64, *temp_elems.add(j));
al_set(recv_handle.get_nanbox_f64(), j as i64, temp.get(j));
}
for j in count..count + undef_count {
al_set(recv, j as i64, undef());
al_set(recv_handle.get_nanbox_f64(), j as i64, undef());
}
for j in (count + undef_count) as i64..len {
al_delete(recv, j);
al_delete(recv_handle.get_nanbox_f64(), j);
}
recv_handle.get_nanbox_f64()
}
recv
}

/// `Array.prototype.concat` over a non-real-array receiver: the receiver is
Expand Down
Loading