use crate::rubysys::types::{c_int, c_void, Id, Value};
pub type EventFlag = u32;
pub const RUBY_EVENT_NONE: EventFlag = 0x0000;
pub const RUBY_EVENT_LINE: EventFlag = 0x0001;
pub const RUBY_EVENT_CLASS: EventFlag = 0x0002;
pub const RUBY_EVENT_END: EventFlag = 0x0004;
pub const RUBY_EVENT_CALL: EventFlag = 0x0008;
pub const RUBY_EVENT_RETURN: EventFlag = 0x0010;
pub const RUBY_EVENT_C_CALL: EventFlag = 0x0020;
pub const RUBY_EVENT_C_RETURN: EventFlag = 0x0040;
pub const RUBY_EVENT_RAISE: EventFlag = 0x0080;
pub const RUBY_EVENT_ALL: EventFlag = 0x00ff;
pub const RUBY_EVENT_B_CALL: EventFlag = 0x0100;
pub const RUBY_EVENT_B_RETURN: EventFlag = 0x0200;
pub const RUBY_EVENT_THREAD_BEGIN: EventFlag = 0x0400;
pub const RUBY_EVENT_THREAD_END: EventFlag = 0x0800;
pub const RUBY_EVENT_FIBER_SWITCH: EventFlag = 0x1000;
pub const RUBY_EVENT_SCRIPT_COMPILED: EventFlag = 0x2000;
pub const RUBY_EVENT_RESCUE: EventFlag = 0x4000;
pub const RUBY_EVENT_TRACEPOINT_ALL: EventFlag = 0xffff;
pub const RUBY_EVENT_RESERVED_FOR_INTERNAL_USE: EventFlag = 0x03_0000;
pub const RUBY_INTERNAL_EVENT_SWITCH: EventFlag = 0x04_0000;
pub const RUBY_EVENT_SWITCH: EventFlag = 0x04_0000;
pub const RUBY_INTERNAL_EVENT_NEWOBJ: EventFlag = 0x10_0000;
pub const RUBY_INTERNAL_EVENT_FREEOBJ: EventFlag = 0x20_0000;
pub const RUBY_INTERNAL_EVENT_GC_START: EventFlag = 0x40_0000;
pub const RUBY_INTERNAL_EVENT_GC_END_MARK: EventFlag = 0x80_0000;
pub const RUBY_INTERNAL_EVENT_GC_END_SWEEP: EventFlag = 0x100_0000;
pub const RUBY_INTERNAL_EVENT_GC_ENTER: EventFlag = 0x200_0000;
pub const RUBY_INTERNAL_EVENT_GC_EXIT: EventFlag = 0x400_0000;
pub const RUBY_INTERNAL_EVENT_OBJSPACE_MASK: EventFlag = 0x7f0_0000;
pub const RUBY_INTERNAL_EVENT_MASK: EventFlag = 0xffff_0000;
pub type EventHookFlag = c_int;
pub const RUBY_EVENT_HOOK_FLAG_SAFE: EventHookFlag = 0x01;
pub const RUBY_EVENT_HOOK_FLAG_DELETED: EventHookFlag = 0x02;
pub const RUBY_EVENT_HOOK_FLAG_RAW_ARG: EventHookFlag = 0x04;
pub type EventHookFunction = rutie_callback!(type fn(
event: EventFlag,
data: Value,
receiver: Value,
method_id: Id,
klass: Value,
));
pub type TracePointFunction = rutie_callback!(type fn(tpval: Value, data: *mut c_void));
#[repr(C)]
pub struct TraceArg {
_private: [u8; 0],
}
#[cfg_attr(rutie_dllimport, link(name = "rutie_ruby"))]
extern "C" {
pub fn rb_add_event_hook(func: EventHookFunction, events: EventFlag, data: Value);
pub fn rb_add_event_hook2(
func: EventHookFunction,
events: EventFlag,
data: Value,
hook_flag: EventHookFlag,
);
pub fn rb_remove_event_hook(func: Option<EventHookFunction>) -> c_int;
pub fn rb_remove_event_hook_with_data(func: Option<EventHookFunction>, data: Value) -> c_int;
pub fn rb_thread_add_event_hook(
thread: Value,
func: EventHookFunction,
events: EventFlag,
data: Value,
);
pub fn rb_thread_add_event_hook2(
thread: Value,
func: EventHookFunction,
events: EventFlag,
data: Value,
hook_flag: EventHookFlag,
);
pub fn rb_thread_remove_event_hook(thread: Value, func: Option<EventHookFunction>) -> c_int;
pub fn rb_thread_remove_event_hook_with_data(
thread: Value,
func: Option<EventHookFunction>,
data: Value,
) -> c_int;
pub fn rb_tracepoint_new(
target_thread_not_supported_yet: Value,
events: EventFlag,
func: TracePointFunction,
data: *mut c_void,
) -> Value;
pub fn rb_tracepoint_disable(tpval: Value) -> Value;
pub fn rb_tracepoint_enable(tpval: Value) -> Value;
pub fn rb_tracepoint_enabled_p(tpval: Value) -> Value;
pub fn rb_tracearg_from_tracepoint(tpval: Value) -> *mut TraceArg;
pub fn rb_tracearg_binding(trace_arg: *mut TraceArg) -> Value;
pub fn rb_tracearg_callee_id(trace_arg: *mut TraceArg) -> Value;
pub fn rb_tracearg_defined_class(trace_arg: *mut TraceArg) -> Value;
pub fn rb_tracearg_eval_script(trace_arg: *mut TraceArg) -> Value;
pub fn rb_tracearg_event(trace_arg: *mut TraceArg) -> Value;
pub fn rb_tracearg_event_flag(trace_arg: *mut TraceArg) -> EventFlag;
pub fn rb_tracearg_instruction_sequence(trace_arg: *mut TraceArg) -> Value;
pub fn rb_tracearg_lineno(trace_arg: *mut TraceArg) -> Value;
pub fn rb_tracearg_method_id(trace_arg: *mut TraceArg) -> Value;
pub fn rb_tracearg_object(trace_arg: *mut TraceArg) -> Value;
pub fn rb_tracearg_parameters(trace_arg: *mut TraceArg) -> Value;
pub fn rb_tracearg_path(trace_arg: *mut TraceArg) -> Value;
pub fn rb_tracearg_raised_exception(trace_arg: *mut TraceArg) -> Value;
pub fn rb_tracearg_return_value(trace_arg: *mut TraceArg) -> Value;
pub fn rb_tracearg_self(trace_arg: *mut TraceArg) -> Value;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::binding::{thread, vm};
use std::sync::atomic::{AtomicUsize, Ordering};
static CALLS: AtomicUsize = AtomicUsize::new(0);
static RAW_CALLS: AtomicUsize = AtomicUsize::new(0);
rutie_callback! {
fn count_call(_event: EventFlag, _data: Value, _receiver: Value, _method_id: Id, _klass: Value) {
CALLS.fetch_add(1, Ordering::SeqCst);
}
}
rutie_callback! {
fn count_raw(_data: Value, trace_arg: *mut TraceArg) {
assert_eq!(unsafe { rb_tracearg_event_flag(trace_arg) }, RUBY_EVENT_CALL);
RAW_CALLS.fetch_add(1, Ordering::SeqCst);
}
}
#[test]
fn test_event_hooks() {
crate::on_ruby_thread(|| unsafe {
vm::eval_string("def rutie_hooked = 1");
let data = vm::eval_string("Object.new");
rb_add_event_hook(count_call, RUBY_EVENT_CALL, data);
vm::eval_string("rutie_hooked; rutie_hooked");
assert_eq!(rb_remove_event_hook(Some(count_call)), 1);
vm::eval_string("rutie_hooked");
assert_eq!(CALLS.swap(0, Ordering::SeqCst), 2);
let raw: rutie_callback!(type fn(Value, *mut TraceArg)) = count_raw;
let raw: EventHookFunction = std::mem::transmute(raw);
rb_add_event_hook2(
raw,
RUBY_EVENT_CALL,
data,
RUBY_EVENT_HOOK_FLAG_SAFE | RUBY_EVENT_HOOK_FLAG_RAW_ARG,
);
vm::eval_string("rutie_hooked");
assert_eq!(rb_remove_event_hook_with_data(Some(raw), data), 1);
assert_eq!(RAW_CALLS.swap(0, Ordering::SeqCst), 1);
let current = thread::current();
rb_thread_add_event_hook(current, count_call, RUBY_EVENT_CALL, data);
rb_thread_add_event_hook2(
current,
count_call,
RUBY_EVENT_RETURN,
data,
RUBY_EVENT_HOOK_FLAG_SAFE,
);
vm::eval_string("rutie_hooked");
assert_eq!(CALLS.swap(0, Ordering::SeqCst), 2);
assert_eq!(
rb_thread_remove_event_hook_with_data(current, Some(count_call), data),
2
);
rb_thread_add_event_hook(current, count_call, RUBY_EVENT_CALL, data);
assert_eq!(rb_thread_remove_event_hook(current, Some(count_call)), 1);
vm::eval_string("rutie_hooked");
assert_eq!(CALLS.load(Ordering::SeqCst), 0);
});
}
}