Skip to content

Commit 85c89c3

Browse files
author
Ralph Küpper
committed
fix(runtime): root constructor receivers across a later ToString coercion (#6949 shape b)
1 parent 1804991 commit 85c89c3

3 files changed

Lines changed: 37 additions & 0 deletions

File tree

crates/perry-runtime/src/builtins/formatting/boxed_primitives.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,15 @@ pub extern "C" fn js_boxed_number_new(value: f64) -> f64 {
299299
#[no_mangle]
300300
pub extern "C" fn js_boxed_string_new(value: f64, has_arg: i32) -> f64 {
301301
let obj = crate::object::js_object_alloc(CLASS_ID_BOXED_STRING, 0);
302+
// #6949(b): both branches below allocate — `js_string_from_bytes` for the
303+
// empty-string case and `js_string_coerce` otherwise, the latter running a
304+
// user `toString`/`valueOf` for a POINTER_TAG value — so either can collect
305+
// and EVACUATE while `obj` sits in a raw Rust local. Every use below
306+
// (`register_boxed_primitive_payload`, the two `install_string_wrapper_*`
307+
// calls, `attach_boxed_primitive_prototype`, and the returned NaN-box)
308+
// dereferences or keys on it.
309+
let scope = crate::gc::RuntimeHandleScope::new();
310+
let obj_handle = scope.root_raw_mut_ptr(obj);
302311
// `new String()` (no args) is spec'd to box "", not "undefined".
303312
let ptr = if has_arg == 0 {
304313
crate::string::js_string_from_bytes(std::ptr::null(), 0)
@@ -309,6 +318,7 @@ pub extern "C" fn js_boxed_string_new(value: f64, has_arg: i32) -> f64 {
309318
}
310319
js_string_coerce(value)
311320
};
321+
let obj = obj_handle.get_raw_mut_ptr::<crate::object::ObjectHeader>();
312322
let boxed = f64::from_bits(crate::value::JSValue::string_ptr(ptr).bits());
313323
register_boxed_primitive_payload(obj, boxed);
314324
install_string_wrapper_indices(obj, ptr);

crates/perry-runtime/src/disposable.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,8 +461,25 @@ pub extern "C" fn js_suppressed_error_new(error: f64, suppressed: f64, message:
461461
// properties { writable:true, enumerable:false, configurable:true }. The
462462
// `name` default ("SuppressedError") lives on `SuppressedError.prototype`,
463463
// so it is *not* set as an own property here.
464+
// #6949(b): `obj` is a raw Rust local — neither a GC root nor a shadow slot
465+
// — and everything below it allocates: `js_string_from_bytes` per key,
466+
// `js_object_set_field_by_name` when the object grows, and
467+
// `js_string_coerce` on the message. Any of those can collect and EVACUATE.
468+
//
469+
// A single rebind after the coercion would not be enough here for two
470+
// reasons: the closure captures `obj` BY VALUE, so every `set_nonenum` call
471+
// would keep using the address captured at definition time; and
472+
// `object_set_static_prototype` at the end keys a SIDE TABLE on
473+
// `obj as usize`, so a stale address does not fault — it files the
474+
// prototype under an address nothing will look up, and `instanceof
475+
// SuppressedError` quietly stops resolving.
476+
//
477+
// So root once and re-read at every use, which is what the handle gives.
478+
let scope = crate::gc::RuntimeHandleScope::new();
479+
let obj_handle = scope.root_raw_mut_ptr(obj);
464480
let set_nonenum = |key: &str, value: f64| {
465481
let key_ptr = js_string_from_bytes(key.as_ptr(), key.len() as u32);
482+
let obj = obj_handle.get_raw_mut_ptr::<crate::object::ObjectHeader>();
466483
js_object_set_field_by_name(obj, key_ptr, value);
467484
crate::object::set_property_attrs(
468485
obj as usize,
@@ -484,11 +501,13 @@ pub extern "C" fn js_suppressed_error_new(error: f64, suppressed: f64, message:
484501
};
485502
set_nonenum("message", message_val);
486503
}
504+
let obj = obj_handle.get_raw_mut_ptr::<crate::object::ObjectHeader>();
487505
let result = js_nanbox_pointer(obj as i64);
488506
// Link the instance to `SuppressedError.prototype` so `name`/`message`
489507
// defaults and `instanceof SuppressedError` resolve through the chain.
490508
let proto = crate::object::builtin_prototype_value("SuppressedError");
491509
if proto.to_bits() != TAG_UNDEFINED && js_nanbox_get_pointer(proto) != 0 {
510+
let obj = obj_handle.get_raw_mut_ptr::<crate::object::ObjectHeader>();
492511
crate::object::prototype_chain::object_set_static_prototype(obj as usize, proto.to_bits());
493512
}
494513
result

crates/perry-runtime/src/messaging.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,15 @@ pub extern "C" fn js_broadcast_channel_new(name: f64) -> f64 {
606606
"constructor",
607607
get_global_constructor("BroadcastChannel"),
608608
);
609+
// #6949(b): `js_string_coerce` allocates for every shape except an
610+
// already-heap STRING_TAG value, so it can collect and EVACUATE — and
611+
// `obj`, allocated a few lines up, is a raw Rust local: neither a GC root
612+
// nor a shadow slot. Every `set_field`/`install_method` below writes
613+
// through it. Root it across the coercion and re-read.
614+
let scope = crate::gc::RuntimeHandleScope::new();
615+
let obj_handle = scope.root_raw_mut_ptr(obj);
609616
let name_ptr = crate::builtins::js_string_coerce(name);
617+
let obj = obj_handle.get_raw_mut_ptr::<object::ObjectHeader>();
610618
let name_value = f64::from_bits(JSValue::string_ptr(name_ptr).bits());
611619
set_field(obj, "name", name_value);
612620
install_method(obj, "close", noop0 as *const u8, 0);

0 commit comments

Comments
 (0)