Skip to content

Commit e77431b

Browse files
committed
fix(tracer): avoid crashes for uninitialized objects
1 parent 008d278 commit e77431b

File tree

1 file changed

+66
-20
lines changed
  • gems/native-tracer/ext/native_tracer/src

1 file changed

+66
-20
lines changed

gems/native-tracer/ext/native_tracer/src/lib.rs

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ use rb_sys::{
2222
rb_raise, rb_eIOError,
2323
rb_sym2id, rb_id2name, rb_id2sym, rb_obj_classname, rb_num2long
2424
};
25-
use rb_sys::{RARRAY_LEN, RARRAY_CONST_PTR, RSTRING_LEN, RSTRING_PTR, RB_INTEGER_TYPE_P, RB_TYPE_P, RB_SYMBOL_P, RB_FLOAT_TYPE_P, NIL_P};
25+
use rb_sys::{RARRAY_LEN, RARRAY_CONST_PTR, RSTRING_LEN, RSTRING_PTR, RB_INTEGER_TYPE_P, RB_TYPE_P, RB_SYMBOL_P, RB_FLOAT_TYPE_P, NIL_P, rb_protect};
2626
use rb_sys::{Qtrue, Qfalse, Qnil};
27+
use std::os::raw::c_int;
2728
use runtime_tracing::{
2829
Tracer, Line, ValueRecord, TypeKind, TypeSpecificInfo, TypeRecord, FieldTypeRecord, TypeId,
2930
EventLogKind, TraceLowLevelEvent, CallRecord, FullValueRecord, ReturnRecord, RecordEvent,
@@ -71,6 +72,7 @@ extern "C" {
7172
static rb_cRegexp: VALUE;
7273
static rb_cStruct: VALUE;
7374
static rb_cRange: VALUE;
75+
fn rb_method_boundp(klass: VALUE, mid: ID, ex: c_int) -> VALUE;
7476
}
7577

7678
struct Recorder {
@@ -260,6 +262,30 @@ unsafe fn value_to_string(val: VALUE, to_s_id: ID) -> Option<String> {
260262
Some(String::from_utf8_lossy(slice).to_string())
261263
}
262264

265+
unsafe extern "C" fn call_to_s(arg: VALUE) -> VALUE {
266+
let data = &*(arg as *const (VALUE, ID));
267+
rb_funcall(data.0, data.1, 0)
268+
}
269+
270+
unsafe fn value_to_string_safe(val: VALUE, to_s_id: ID) -> Option<String> {
271+
if RB_TYPE_P(val, rb_sys::ruby_value_type::RUBY_T_STRING) {
272+
let ptr = RSTRING_PTR(val);
273+
let len = RSTRING_LEN(val) as usize;
274+
let slice = std::slice::from_raw_parts(ptr as *const u8, len);
275+
return Some(String::from_utf8_lossy(slice).to_string());
276+
}
277+
let mut state: c_int = 0;
278+
let data = (val, to_s_id);
279+
let str_val = rb_protect(Some(call_to_s), &data as *const _ as VALUE, &mut state);
280+
if state != 0 {
281+
return None;
282+
}
283+
let ptr = RSTRING_PTR(str_val);
284+
let len = RSTRING_LEN(str_val) as usize;
285+
let slice = std::slice::from_raw_parts(ptr as *const u8, len);
286+
Some(String::from_utf8_lossy(slice).to_string())
287+
}
288+
263289
unsafe fn to_value(recorder: &mut Recorder, val: VALUE, depth: usize, to_s_id: ID) -> ValueRecord {
264290
if depth == 0 {
265291
let type_id = recorder.tracer.ensure_type_id(TypeKind::Error, "No type");
@@ -314,7 +340,9 @@ unsafe fn to_value(recorder: &mut Recorder, val: VALUE, depth: usize, to_s_id: I
314340
let mut elements = Vec::new();
315341
for i in 0..len {
316342
let pair = *ptr.add(i);
317-
if RARRAY_LEN(pair) < 2 { continue; }
343+
if !RB_TYPE_P(pair, rb_sys::ruby_value_type::RUBY_T_ARRAY) || RARRAY_LEN(pair) < 2 {
344+
continue;
345+
}
318346
let pair_ptr = RARRAY_CONST_PTR(pair);
319347
let key = *pair_ptr.add(0);
320348
let val_elem = *pair_ptr.add(1);
@@ -333,15 +361,17 @@ unsafe fn to_value(recorder: &mut Recorder, val: VALUE, depth: usize, to_s_id: I
333361
let set_cls = rb_const_get(rb_cObject, set_id);
334362
if rb_obj_is_kind_of(val, set_cls) != 0 {
335363
let arr = rb_funcall(val, rb_intern(b"to_a\0".as_ptr() as *const c_char), 0);
336-
let len = RARRAY_LEN(arr) as usize;
337-
let ptr = RARRAY_CONST_PTR(arr);
338-
let mut elements = Vec::new();
339-
for i in 0..len {
340-
let elem = *ptr.add(i);
341-
elements.push(to_value(recorder, elem, depth - 1, to_s_id));
364+
if RB_TYPE_P(arr, rb_sys::ruby_value_type::RUBY_T_ARRAY) {
365+
let len = RARRAY_LEN(arr) as usize;
366+
let ptr = RARRAY_CONST_PTR(arr);
367+
let mut elements = Vec::new();
368+
for i in 0..len {
369+
let elem = *ptr.add(i);
370+
elements.push(to_value(recorder, elem, depth - 1, to_s_id));
371+
}
372+
let type_id = recorder.tracer.ensure_type_id(TypeKind::Seq, "Set");
373+
return ValueRecord::Sequence { elements, is_slice: false, type_id };
342374
}
343-
let type_id = recorder.tracer.ensure_type_id(TypeKind::Seq, "Set");
344-
return ValueRecord::Sequence { elements, is_slice: false, type_id };
345375
}
346376
}
347377
if rb_obj_is_kind_of(val, rb_cTime) != 0 {
@@ -358,6 +388,11 @@ unsafe fn to_value(recorder: &mut Recorder, val: VALUE, depth: usize, to_s_id: I
358388
let class_name = cstr_to_string(rb_obj_classname(val)).unwrap_or_else(|| "Struct".to_string());
359389
let members = rb_funcall(val, rb_intern(b"members\0".as_ptr() as *const c_char), 0);
360390
let values = rb_funcall(val, rb_intern(b"values\0".as_ptr() as *const c_char), 0);
391+
if !RB_TYPE_P(members, rb_sys::ruby_value_type::RUBY_T_ARRAY) || !RB_TYPE_P(values, rb_sys::ruby_value_type::RUBY_T_ARRAY) {
392+
let text = value_to_string(val, to_s_id).unwrap_or_default();
393+
let type_id = recorder.tracer.ensure_type_id(TypeKind::Raw, &class_name);
394+
return ValueRecord::Raw { r: text, type_id };
395+
}
361396
let len = RARRAY_LEN(values) as usize;
362397
let mem_ptr = RARRAY_CONST_PTR(members);
363398
let val_ptr = RARRAY_CONST_PTR(values);
@@ -384,6 +419,11 @@ unsafe fn to_value(recorder: &mut Recorder, val: VALUE, depth: usize, to_s_id: I
384419
let class_name = cstr_to_string(rb_obj_classname(val)).unwrap_or_else(|| "Object".to_string());
385420
// generic object
386421
let ivars = rb_funcall(val, rb_intern(b"instance_variables\0".as_ptr() as *const c_char), 0);
422+
if !RB_TYPE_P(ivars, rb_sys::ruby_value_type::RUBY_T_ARRAY) {
423+
let text = value_to_string(val, to_s_id).unwrap_or_default();
424+
let type_id = recorder.tracer.ensure_type_id(TypeKind::Raw, &class_name);
425+
return ValueRecord::Raw { r: text, type_id };
426+
}
387427
let len = RARRAY_LEN(ivars) as usize;
388428
let ptr = RARRAY_CONST_PTR(ivars);
389429
let mut names: Vec<&str> = Vec::new();
@@ -408,6 +448,9 @@ unsafe fn to_value(recorder: &mut Recorder, val: VALUE, depth: usize, to_s_id: I
408448
unsafe fn record_variables(recorder: &mut Recorder, binding: VALUE) -> Vec<FullValueRecord> {
409449
let mut result = Vec::new();
410450
let vars = rb_funcall(binding, recorder.locals_id, 0);
451+
if !RB_TYPE_P(vars, rb_sys::ruby_value_type::RUBY_T_ARRAY) {
452+
return result;
453+
}
411454
let len = RARRAY_LEN(vars) as usize;
412455
let ptr = RARRAY_CONST_PTR(vars);
413456
for i in 0..len {
@@ -426,13 +469,19 @@ unsafe fn record_variables(recorder: &mut Recorder, binding: VALUE) -> Vec<FullV
426469
unsafe fn record_parameters(recorder: &mut Recorder, binding: VALUE, defined_class: VALUE, mid: ID, register: bool) -> Vec<FullValueRecord> {
427470
let mut result = Vec::new();
428471
let method_sym = rb_id2sym(mid);
472+
if rb_method_boundp(defined_class, mid, 0) == 0 {
473+
return result;
474+
}
429475
let method_obj = rb_funcall(defined_class, recorder.inst_meth_id, 1, method_sym);
430476
let params_ary = rb_funcall(method_obj, recorder.parameters_id, 0);
477+
if !RB_TYPE_P(params_ary, rb_sys::ruby_value_type::RUBY_T_ARRAY) {
478+
return result;
479+
}
431480
let params_len = RARRAY_LEN(params_ary) as usize;
432481
let params_ptr = RARRAY_CONST_PTR(params_ary);
433482
for i in 0..params_len {
434483
let pair = *params_ptr.add(i);
435-
if RARRAY_LEN(pair) < 2 {
484+
if !RB_TYPE_P(pair, rb_sys::ruby_value_type::RUBY_T_ARRAY) || RARRAY_LEN(pair) < 2 {
436485
continue;
437486
}
438487
let pair_ptr = RARRAY_CONST_PTR(pair);
@@ -545,20 +594,18 @@ unsafe extern "C" fn event_hook_raw(data: VALUE, arg: *mut rb_trace_arg_t) {
545594
let mid_sym = rb_tracearg_callee_id(arg);
546595
let mid = rb_sym2id(mid_sym);
547596
let defined_class = rb_funcall(self_val, recorder.class_id, 0);
597+
let mut args = Vec::new();
548598
if !NIL_P(binding) {
549-
// register parameter types first
550-
let _ = record_parameters(recorder, binding, defined_class, mid, false);
599+
args = record_parameters(recorder, binding, defined_class, mid, true);
551600
}
552-
let self_rec = to_value(recorder, self_val, 10, recorder.to_s_id);
601+
let class_name = cstr_to_string(rb_obj_classname(self_val)).unwrap_or_else(|| "Object".to_string());
602+
let text = value_to_string_safe(self_val, recorder.to_s_id).unwrap_or_default();
603+
let self_type = recorder.tracer.ensure_type_id(TypeKind::Raw, &class_name);
604+
let self_rec = ValueRecord::Raw { r: text, type_id: self_type };
553605
recorder
554606
.tracer
555607
.register_variable_with_full_value("self", self_rec.clone());
556608

557-
let mut args = if NIL_P(binding) {
558-
vec![]
559-
} else {
560-
record_parameters(recorder, binding, defined_class, mid, true)
561-
};
562609
args.insert(0, recorder.tracer.arg("self", self_rec));
563610
recorder.tracer.register_step(Path::new(&path), Line(line));
564611
let name_c = rb_id2name(mid);
@@ -567,7 +614,6 @@ unsafe extern "C" fn event_hook_raw(data: VALUE, arg: *mut rb_trace_arg_t) {
567614
} else {
568615
String::new()
569616
};
570-
let class_name = cstr_to_string(rb_obj_classname(self_val)).unwrap_or_else(|| "Object".to_string());
571617
if class_name != "Object" {
572618
name = format!("{}#{}", class_name, name);
573619
}

0 commit comments

Comments
 (0)