diff --git a/crates/perry-ext-events/src/error_monitor.rs b/crates/perry-ext-events/src/error_monitor.rs new file mode 100644 index 0000000000..4360b19a92 --- /dev/null +++ b/crates/perry-ext-events/src/error_monitor.rs @@ -0,0 +1,41 @@ +//! `events.errorMonitor` dispatch for the ext-events EventEmitter twin +//! (#4633). Split out of `lib.rs` to keep it under the 2000-line cap. + +use super::*; + +/// String key under which a listener registered via the `events.errorMonitor` +/// symbol lands: `event_name_from_bits` stringifies symbol event names, and +/// `Symbol.for("events.errorMonitor")` renders as this. Mirrors the stdlib +/// twin's constant so the two implementations stay behaviorally identical. +pub(super) const ERROR_MONITOR_EVENT_NAME: &str = "Symbol(events.errorMonitor)"; + +/// Node's `events.errorMonitor` semantics (#4633): listeners installed under +/// the monitor symbol observe every `'error'` emit BEFORE the regular +/// `'error'` listeners run, without counting as error handling - an +/// unhandled `'error'` still throws after the monitor fires. Mirrors +/// `dispatch_error_monitor` in perry-stdlib's events twin. +pub(super) unsafe fn dispatch_error_monitor( + emitter: &mut EventEmitterHandle, + handle: Handle, + arg: Option, +) { + let snapshot: Vec = match emitter.events.get(ERROR_MONITOR_EVENT_NAME) { + Some(v) if !v.is_empty() => v.clone(), + _ => return, + }; + if snapshot.iter().any(|l| l.once) { + if let Some(v) = emitter.events.get_mut(ERROR_MONITOR_EVENT_NAME) { + v.retain(|l| !l.once); + } + emitter.prune_event_if_empty(ERROR_MONITOR_EVENT_NAME); + } + for l in snapshot { + if l.callback != 0 { + let args: &[f64] = match arg.as_ref() { + Some(a) => std::slice::from_ref(a), + None => &[], + }; + let _ = call_emitter_listener(handle, l.callback, args); + } + } +} diff --git a/crates/perry-ext-events/src/lib.rs b/crates/perry-ext-events/src/lib.rs index 7c33fe8f14..b6b7d2f64e 100644 --- a/crates/perry-ext-events/src/lib.rs +++ b/crates/perry-ext-events/src/lib.rs @@ -28,6 +28,8 @@ use std::collections::{HashMap, HashSet}; use std::ffi::c_void; use std::sync::{Mutex, MutexGuard, Once, OnceLock}; +mod error_monitor; +use error_monitor::dispatch_error_monitor; mod max_listeners; mod messages; mod target_helpers; @@ -1169,6 +1171,7 @@ pub unsafe extern "C" fn js_event_emitter_emit( let first_arg = first_arg_or_undefined(args_ptr); let emitted_args = collect_emit_args(args_ptr); if event_name == "error" { + dispatch_error_monitor(emitter, handle, Some(first_arg)); let has_error_once = emitter .pending_once_promises .get("error") @@ -1249,6 +1252,7 @@ pub unsafe extern "C" fn js_event_emitter_emit0(handle: Handle, event_bits: i64) let empty_args = js_array_alloc(0); if event_name == "error" { let error_value = undefined_value(); + dispatch_error_monitor(emitter, handle, None); let has_error_once = emitter .pending_once_promises .get("error") diff --git a/crates/perry-runtime/src/event_target.rs b/crates/perry-runtime/src/event_target.rs index fb3019cd3d..aa8f4e3c62 100644 --- a/crates/perry-runtime/src/event_target.rs +++ b/crates/perry-runtime/src/event_target.rs @@ -388,7 +388,11 @@ unsafe fn is_event_target(target: *const ObjectHeader) -> bool { if target.is_null() { return false; } - if (target as usize) < crate::gc::GC_HEADER_SIZE + 0x10000 { + // Handle-based receivers (EventEmitter ids live at 0x38000..0x40000, + // widget/stream handles lower) are small integers, not heap pointers. + // Probing the GcHeader at handle-8 read unmapped memory and SIGSEGV'd + // when events.on(emitter, ...) validated its target (#4633). + if crate::value::addr_class::is_handle_band(target as usize) { return false; } let gc_header = diff --git a/crates/perry-runtime/src/node_stream_readwrite.rs b/crates/perry-runtime/src/node_stream_readwrite.rs index f0ba300899..4b9f77acdf 100644 --- a/crates/perry-runtime/src/node_stream_readwrite.rs +++ b/crates/perry-runtime/src/node_stream_readwrite.rs @@ -76,7 +76,12 @@ pub(super) fn string_value_eq(value: f64, expected: &[u8]) -> bool { pub(super) fn object_ptr_from_value(value: f64) -> Option<*mut ObjectHeader> { let raw = raw_ptr_from_value(value); - if raw < 0x10000 || crate::buffer::is_registered_buffer(raw) { + // The handle band (EventEmitter ids sit at 0x38000..0x40000, + // widget/stream handles lower) is never a heap object. The old 0x10000 + // floor let an EventEmitter handle through to the GcHeader probe at + // raw-8, which is unmapped memory (#4633 SIGSEGV in + // events.on(emitter, name, { signal }) target validation). + if crate::value::addr_class::is_handle_band(raw) || crate::buffer::is_registered_buffer(raw) { return None; } unsafe {