@@ -6,6 +6,7 @@ use std::{
66 os:: raw:: { c_char, c_void} ,
77 path:: Path ,
88 ptr,
9+ collections:: HashMap ,
910} ;
1011
1112use 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 } ;
2526use 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 ) ]
2933struct 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
6376struct 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
74147unsafe 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