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
3 changes: 3 additions & 0 deletions changelog.d/11022-spread-iterator-debug-unwind.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- Preserve exceptions thrown by custom iterators during array push spread when using debug or test runtime archives, so JavaScript `catch` handlers receive them instead of the process aborting (#11010).
33 changes: 33 additions & 0 deletions crates/perry-runtime/src/array/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1241,8 +1241,27 @@ pub(crate) fn array_from_spread_value(value: f64) -> *mut ArrayHeader {
throw_not_iterable(value());
}

// This helper runs the user-observable iterator protocol and can therefore
// throw through the generated caller. Debug/test archives transport that
// throw with Rust unwinding, so their outer ABI must permit it. Production
// uses Perry's raw exception transport and must retain the plain C boundary;
// see closure/dispatch/value_call.rs (#8479).
#[cfg(panic = "abort")]
#[no_mangle]
pub extern "C" fn js_array_spread_append(dest: *mut ArrayHeader, source: f64) -> *mut ArrayHeader {
js_array_spread_append_impl(dest, source)
}

#[cfg(not(panic = "abort"))]
#[no_mangle]
pub extern "C-unwind" fn js_array_spread_append(
dest: *mut ArrayHeader,
source: f64,
) -> *mut ArrayHeader {
js_array_spread_append_impl(dest, source)
}

fn js_array_spread_append_impl(dest: *mut ArrayHeader, source: f64) -> *mut ArrayHeader {
// Materializing an intercepted iterator can allocate and move the
// destination. Keep it rooted across that protocol walk and re-read it
// before appending. Ordinary dense arrays need no temporary: the same
Expand Down Expand Up @@ -1557,8 +1576,22 @@ fn settled_promise_value(value: f64) -> Option<f64> {

/// Used by spread on generators, Array.from on generators, etc.
/// Calls `.next()` in a loop until `.done` is true, collecting `.value` entries.
// `.next()` is arbitrary user code. Match the conditional ABI on the dynamic
// call bridges so a debug/test archive can carry a catchable throw across this
// exported helper without rustc's abort-on-unwind guard.
#[cfg(panic = "abort")]
#[no_mangle]
pub extern "C" fn js_iterator_to_array(iter_f64: f64) -> *mut ArrayHeader {
js_iterator_to_array_impl(iter_f64)
}

#[cfg(not(panic = "abort"))]
#[no_mangle]
pub extern "C-unwind" fn js_iterator_to_array(iter_f64: f64) -> *mut ArrayHeader {
js_iterator_to_array_impl(iter_f64)
}

fn js_iterator_to_array_impl(iter_f64: f64) -> *mut ArrayHeader {
use crate::closure;
use crate::object::{js_object_get_field_by_name, ObjectHeader};
use crate::string::js_string_from_bytes;
Expand Down
3 changes: 2 additions & 1 deletion crates/perry/tests/issue_10058_push_spread_scaling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ fn spread_push_is_iterator_correct_and_gc_safe_on_reused_destinations() {
assert_success("Node oracle", &node);
for moving_gc in [false, true] {
let perry = run(&binary, moving_gc);
assert_success("compiled fixture", &perry);
let mode = if moving_gc { "moving GC" } else { "plain" };
assert_success(&format!("compiled fixture ({mode})"), &perry);
assert_eq!(
perry.stdout,
node.stdout,
Expand Down
Loading