Skip to content

Commit 6504ee5

Browse files
committed
feat(tracer): handle more Ruby types
1 parent c444d1c commit 6504ee5

File tree

5 files changed

+440
-31
lines changed

5 files changed

+440
-31
lines changed

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

Lines changed: 187 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::{
66
os::raw::{c_char, c_void},
77
path::Path,
88
ptr,
9+
collections::HashMap,
910
};
1011

1112
use rb_sys::{
@@ -21,9 +22,12 @@ use rb_sys::{
2122
rb_raise, rb_eIOError,
2223
rb_sym2id, rb_id2name, rb_id2sym, rb_obj_classname, rb_num2long
2324
};
24-
use rb_sys::{RARRAY_LEN, RARRAY_CONST_PTR, RSTRING_LEN, RSTRING_PTR, RB_INTEGER_TYPE_P, RB_TYPE_P, RB_SYMBOL_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};
2526
use rb_sys::{Qtrue, Qfalse, Qnil};
26-
use runtime_tracing::{Tracer, Line, ValueRecord, TypeKind, EventLogKind, TraceLowLevelEvent, CallRecord, FullValueRecord, ReturnRecord, RecordEvent};
27+
use runtime_tracing::{
28+
Tracer, Line, ValueRecord, TypeKind, TypeSpecificInfo, TypeRecord, FieldTypeRecord, TypeId,
29+
EventLogKind, TraceLowLevelEvent, CallRecord, FullValueRecord, ReturnRecord, RecordEvent,
30+
};
2731

2832
#[repr(C)]
2933
struct RTypedData {
@@ -58,6 +62,15 @@ extern "C" {
5862
data_type: *const rb_data_type_t,
5963
) -> VALUE;
6064
fn rb_check_typeddata(obj: VALUE, data_type: *const rb_data_type_t) -> *mut c_void;
65+
fn rb_num2dbl(val: VALUE) -> f64;
66+
fn rb_obj_is_kind_of(obj: VALUE, class: VALUE) -> VALUE;
67+
fn rb_path2class(path: *const c_char) -> VALUE;
68+
fn rb_const_defined(klass: VALUE, name: ID) -> VALUE;
69+
fn rb_const_get(klass: VALUE, name: ID) -> VALUE;
70+
static rb_cTime: VALUE;
71+
static rb_cRegexp: VALUE;
72+
static rb_cStruct: VALUE;
73+
static rb_cRange: VALUE;
6174
}
6275

6376
struct Recorder {
@@ -69,6 +82,66 @@ struct Recorder {
6982
inst_meth_id: ID,
7083
parameters_id: ID,
7184
class_id: ID,
85+
struct_types: HashMap<String, runtime_tracing::TypeId>,
86+
}
87+
88+
fn value_type_id(val: &ValueRecord) -> runtime_tracing::TypeId {
89+
use ValueRecord::*;
90+
match val {
91+
Int { type_id, .. }
92+
| Float { type_id, .. }
93+
| Bool { type_id, .. }
94+
| String { type_id, .. }
95+
| Sequence { type_id, .. }
96+
| Tuple { type_id, .. }
97+
| Struct { type_id, .. }
98+
| Variant { type_id, .. }
99+
| Reference { type_id, .. }
100+
| Raw { type_id, .. }
101+
| Error { type_id, .. }
102+
| None { type_id } => *type_id,
103+
Cell { .. } => runtime_tracing::NONE_TYPE_ID,
104+
}
105+
}
106+
107+
unsafe fn struct_value(
108+
recorder: &mut Recorder,
109+
class_name: &str,
110+
field_names: &[&str],
111+
field_values: &[VALUE],
112+
depth: usize,
113+
to_s_id: ID,
114+
) -> ValueRecord {
115+
let mut vals = Vec::new();
116+
for v in field_values {
117+
vals.push(to_value(recorder, *v, depth - 1, to_s_id));
118+
}
119+
120+
let type_id = if let Some(id) = recorder.struct_types.get(class_name) {
121+
*id
122+
} else {
123+
let field_types: Vec<FieldTypeRecord> = field_names
124+
.iter()
125+
.zip(&vals)
126+
.map(|(n, v)| FieldTypeRecord {
127+
name: (*n).to_string(),
128+
type_id: value_type_id(v),
129+
})
130+
.collect();
131+
let typ = TypeRecord {
132+
kind: TypeKind::Struct,
133+
lang_type: class_name.to_string(),
134+
specific_info: TypeSpecificInfo::Struct { fields: field_types },
135+
};
136+
let id = recorder.tracer.ensure_raw_type_id(typ);
137+
recorder.struct_types.insert(class_name.to_string(), id);
138+
id
139+
};
140+
141+
ValueRecord::Struct {
142+
field_values: vals,
143+
type_id,
144+
}
72145
}
73146

74147
unsafe extern "C" fn recorder_free(ptr: *mut c_void) {
@@ -122,6 +195,7 @@ unsafe extern "C" fn ruby_recorder_alloc(klass: VALUE) -> VALUE {
122195
inst_meth_id,
123196
parameters_id,
124197
class_id,
198+
struct_types: HashMap::new(),
125199
});
126200
let ty = std::ptr::addr_of!(RECORDER_TYPE) as *const rb_data_type_t;
127201
rb_data_typed_object_wrap(klass, Box::into_raw(recorder) as *mut c_void, ty)
@@ -186,35 +260,40 @@ unsafe fn value_to_string(val: VALUE, to_s_id: ID) -> Option<String> {
186260
Some(String::from_utf8_lossy(slice).to_string())
187261
}
188262

189-
unsafe fn to_value(tracer: &mut Tracer, val: VALUE, depth: usize, to_s_id: ID) -> ValueRecord {
263+
unsafe fn to_value(recorder: &mut Recorder, val: VALUE, depth: usize, to_s_id: ID) -> ValueRecord {
190264
if depth == 0 {
191-
let type_id = tracer.ensure_type_id(TypeKind::Error, "No type");
265+
let type_id = recorder.tracer.ensure_type_id(TypeKind::Error, "No type");
192266
return ValueRecord::None { type_id };
193267
}
194268
if NIL_P(val) {
195-
let type_id = tracer.ensure_type_id(TypeKind::Error, "No type");
269+
let type_id = recorder.tracer.ensure_type_id(TypeKind::Error, "No type");
196270
return ValueRecord::None { type_id };
197271
}
198272
if val == (Qtrue as VALUE) || val == (Qfalse as VALUE) {
199-
let type_id = tracer.ensure_type_id(TypeKind::Bool, "Bool");
273+
let type_id = recorder.tracer.ensure_type_id(TypeKind::Bool, "Bool");
200274
return ValueRecord::Bool { b: val == (Qtrue as VALUE), type_id };
201275
}
202276
if RB_INTEGER_TYPE_P(val) {
203277
let i = rb_num2long(val) as i64;
204-
let type_id = tracer.ensure_type_id(TypeKind::Int, "Integer");
278+
let type_id = recorder.tracer.ensure_type_id(TypeKind::Int, "Integer");
205279
return ValueRecord::Int { i, type_id };
206280
}
281+
if RB_FLOAT_TYPE_P(val) {
282+
let f = rb_num2dbl(val);
283+
let type_id = recorder.tracer.ensure_type_id(TypeKind::Float, "Float");
284+
return ValueRecord::Float { f, type_id };
285+
}
207286
if RB_SYMBOL_P(val) {
208287
let id = rb_sym2id(val);
209288
let name = CStr::from_ptr(rb_id2name(id)).to_str().unwrap_or("");
210-
let type_id = tracer.ensure_type_id(TypeKind::String, "Symbol");
289+
let type_id = recorder.tracer.ensure_type_id(TypeKind::String, "Symbol");
211290
return ValueRecord::String { text: name.to_string(), type_id };
212291
}
213292
if RB_TYPE_P(val, rb_sys::ruby_value_type::RUBY_T_STRING) {
214293
let ptr = RSTRING_PTR(val);
215294
let len = RSTRING_LEN(val) as usize;
216295
let slice = std::slice::from_raw_parts(ptr as *const u8, len);
217-
let type_id = tracer.ensure_type_id(TypeKind::String, "String");
296+
let type_id = recorder.tracer.ensure_type_id(TypeKind::String, "String");
218297
return ValueRecord::String { text: String::from_utf8_lossy(slice).to_string(), type_id };
219298
}
220299
if RB_TYPE_P(val, rb_sys::ruby_value_type::RUBY_T_ARRAY) {
@@ -223,14 +302,106 @@ unsafe fn to_value(tracer: &mut Tracer, val: VALUE, depth: usize, to_s_id: ID) -
223302
let ptr = RARRAY_CONST_PTR(val);
224303
for i in 0..len {
225304
let elem = *ptr.add(i);
226-
elements.push(to_value(tracer, elem, depth - 1, to_s_id));
305+
elements.push(to_value(recorder, elem, depth - 1, to_s_id));
306+
}
307+
let type_id = recorder.tracer.ensure_type_id(TypeKind::Seq, "Array");
308+
return ValueRecord::Sequence { elements, is_slice: false, type_id };
309+
}
310+
if RB_TYPE_P(val, rb_sys::ruby_value_type::RUBY_T_HASH) {
311+
let pairs = rb_funcall(val, rb_intern(b"to_a\0".as_ptr() as *const c_char), 0);
312+
let len = RARRAY_LEN(pairs) as usize;
313+
let ptr = RARRAY_CONST_PTR(pairs);
314+
let mut elements = Vec::new();
315+
for i in 0..len {
316+
let pair = *ptr.add(i);
317+
if RARRAY_LEN(pair) < 2 { continue; }
318+
let pair_ptr = RARRAY_CONST_PTR(pair);
319+
let key = *pair_ptr.add(0);
320+
let val_elem = *pair_ptr.add(1);
321+
elements.push(struct_value(recorder, "Pair", &["k", "v"], &[key, val_elem], depth, to_s_id));
227322
}
228-
let type_id = tracer.ensure_type_id(TypeKind::Seq, "Array");
323+
let type_id = recorder.tracer.ensure_type_id(TypeKind::Seq, "Hash");
229324
return ValueRecord::Sequence { elements, is_slice: false, type_id };
230325
}
326+
if rb_obj_is_kind_of(val, rb_cRange) != 0 {
327+
let begin_val = rb_funcall(val, rb_intern(b"begin\0".as_ptr() as *const c_char), 0);
328+
let end_val = rb_funcall(val, rb_intern(b"end\0".as_ptr() as *const c_char), 0);
329+
return struct_value(recorder, "Range", &["begin", "end"], &[begin_val, end_val], depth, to_s_id);
330+
}
331+
let set_id = rb_intern(b"Set\0".as_ptr() as *const c_char);
332+
if rb_const_defined(rb_cObject, set_id) != 0 {
333+
let set_cls = rb_const_get(rb_cObject, set_id);
334+
if rb_obj_is_kind_of(val, set_cls) != 0 {
335+
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));
342+
}
343+
let type_id = recorder.tracer.ensure_type_id(TypeKind::Seq, "Set");
344+
return ValueRecord::Sequence { elements, is_slice: false, type_id };
345+
}
346+
}
347+
if rb_obj_is_kind_of(val, rb_cTime) != 0 {
348+
let sec = rb_funcall(val, rb_intern(b"to_i\0".as_ptr() as *const c_char), 0);
349+
let nsec = rb_funcall(val, rb_intern(b"nsec\0".as_ptr() as *const c_char), 0);
350+
return struct_value(recorder, "Time", &["sec", "nsec"], &[sec, nsec], depth, to_s_id);
351+
}
352+
if rb_obj_is_kind_of(val, rb_cRegexp) != 0 {
353+
let src = rb_funcall(val, rb_intern(b"source\0".as_ptr() as *const c_char), 0);
354+
let opts = rb_funcall(val, rb_intern(b"options\0".as_ptr() as *const c_char), 0);
355+
return struct_value(recorder, "Regexp", &["source", "options"], &[src, opts], depth, to_s_id);
356+
}
357+
if rb_obj_is_kind_of(val, rb_cStruct) != 0 {
358+
let class_name = cstr_to_string(rb_obj_classname(val)).unwrap_or_else(|| "Struct".to_string());
359+
let members = rb_funcall(val, rb_intern(b"members\0".as_ptr() as *const c_char), 0);
360+
let values = rb_funcall(val, rb_intern(b"values\0".as_ptr() as *const c_char), 0);
361+
let len = RARRAY_LEN(values) as usize;
362+
let mem_ptr = RARRAY_CONST_PTR(members);
363+
let val_ptr = RARRAY_CONST_PTR(values);
364+
let mut names: Vec<&str> = Vec::new();
365+
let mut vals: Vec<VALUE> = Vec::new();
366+
for i in 0..len {
367+
let sym = *mem_ptr.add(i);
368+
let id = rb_sym2id(sym);
369+
let cstr = rb_id2name(id);
370+
let name = CStr::from_ptr(cstr).to_str().unwrap_or("?");
371+
names.push(name);
372+
vals.push(*val_ptr.add(i));
373+
}
374+
return struct_value(recorder, &class_name, &names, &vals, depth, to_s_id);
375+
}
376+
let open_struct_id = rb_intern(b"OpenStruct\0".as_ptr() as *const c_char);
377+
if rb_const_defined(rb_cObject, open_struct_id) != 0 {
378+
let open_struct = rb_const_get(rb_cObject, open_struct_id);
379+
if rb_obj_is_kind_of(val, open_struct) != 0 {
380+
let h = rb_funcall(val, rb_intern(b"to_h\0".as_ptr() as *const c_char), 0);
381+
return to_value(recorder, h, depth - 1, to_s_id);
382+
}
383+
}
231384
let class_name = cstr_to_string(rb_obj_classname(val)).unwrap_or_else(|| "Object".to_string());
385+
// generic object
386+
let ivars = rb_funcall(val, rb_intern(b"instance_variables\0".as_ptr() as *const c_char), 0);
387+
let len = RARRAY_LEN(ivars) as usize;
388+
let ptr = RARRAY_CONST_PTR(ivars);
389+
let mut names: Vec<&str> = Vec::new();
390+
let mut vals: Vec<VALUE> = Vec::new();
391+
for i in 0..len {
392+
let sym = *ptr.add(i);
393+
let id = rb_sym2id(sym);
394+
let cstr = rb_id2name(id);
395+
let name = CStr::from_ptr(cstr).to_str().unwrap_or("?");
396+
names.push(name);
397+
let value = rb_funcall(val, rb_intern(b"instance_variable_get\0".as_ptr() as *const c_char), 1, sym);
398+
vals.push(value);
399+
}
400+
if !names.is_empty() {
401+
return struct_value(recorder, &class_name, &names, &vals, depth, to_s_id);
402+
}
232403
let text = value_to_string(val, to_s_id).unwrap_or_default();
233-
let type_id = tracer.ensure_type_id(TypeKind::Raw, &class_name);
404+
let type_id = recorder.tracer.ensure_type_id(TypeKind::Raw, &class_name);
234405
ValueRecord::Raw { r: text, type_id }
235406
}
236407

@@ -244,7 +415,7 @@ unsafe fn record_variables(recorder: &mut Recorder, binding: VALUE) -> Vec<FullV
244415
let id = rb_sym2id(sym);
245416
let name = CStr::from_ptr(rb_id2name(id)).to_str().unwrap_or("");
246417
let value = rb_funcall(binding, recorder.local_get_id, 1, sym);
247-
let val_rec = to_value(&mut recorder.tracer, value, 10, recorder.to_s_id);
418+
let val_rec = to_value(recorder, value, 10, recorder.to_s_id);
248419
recorder.tracer.register_variable_with_full_value(name, val_rec.clone());
249420
let var_id = recorder.tracer.ensure_variable_id(name);
250421
result.push(FullValueRecord { variable_id: var_id, value: val_rec });
@@ -276,7 +447,7 @@ unsafe fn record_parameters(recorder: &mut Recorder, binding: VALUE, defined_cla
276447
}
277448
let name = CStr::from_ptr(name_c).to_str().unwrap_or("");
278449
let value = rb_funcall(binding, recorder.local_get_id, 1, name_sym);
279-
let val_rec = to_value(&mut recorder.tracer, value, 10, recorder.to_s_id);
450+
let val_rec = to_value(recorder, value, 10, recorder.to_s_id);
280451
if register {
281452
recorder.tracer.register_variable_with_full_value(name, val_rec.clone());
282453
let var_id = recorder.tracer.ensure_variable_id(name);
@@ -378,7 +549,7 @@ unsafe extern "C" fn event_hook_raw(data: VALUE, arg: *mut rb_trace_arg_t) {
378549
// register parameter types first
379550
let _ = record_parameters(recorder, binding, defined_class, mid, false);
380551
}
381-
let self_rec = to_value(&mut recorder.tracer, self_val, 10, recorder.to_s_id);
552+
let self_rec = to_value(recorder, self_val, 10, recorder.to_s_id);
382553
recorder
383554
.tracer
384555
.register_variable_with_full_value("self", self_rec.clone());
@@ -405,7 +576,7 @@ unsafe extern "C" fn event_hook_raw(data: VALUE, arg: *mut rb_trace_arg_t) {
405576
} else if (ev & RUBY_EVENT_RETURN) != 0 {
406577
recorder.tracer.register_step(Path::new(&path), Line(line));
407578
let ret = rb_tracearg_return_value(arg);
408-
let val_rec = to_value(&mut recorder.tracer, ret, 10, recorder.to_s_id);
579+
let val_rec = to_value(recorder, ret, 10, recorder.to_s_id);
409580
recorder.tracer.register_variable_with_full_value("<return_value>", val_rec.clone());
410581
recorder.tracer.events.push(TraceLowLevelEvent::Return(ReturnRecord { return_value: val_rec }));
411582
} else if (ev & RUBY_EVENT_RAISE) != 0 {

0 commit comments

Comments
 (0)