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
41 changes: 41 additions & 0 deletions crates/perry-ext-events/src/error_monitor.rs
Original file line number Diff line number Diff line change
@@ -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<f64>,
) {
let snapshot: Vec<Listener> = 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);
}
}
}
4 changes: 4 additions & 0 deletions crates/perry-ext-events/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
6 changes: 5 additions & 1 deletion crates/perry-runtime/src/event_target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
7 changes: 6 additions & 1 deletion crates/perry-runtime/src/node_stream_readwrite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down