#include "rb_Kernel.h" #include "rb_Kernel_internal.h" #include "RPSender_internal.h" #include "RubySourceSupport.h" /*********** * Kernel * ***********/ void Init_senderKernel() { rb_define_singleton_method( rb_mKernel, "backtrace", rb_RPRuby_Sender_Kernel_backtrace, -1 ); rb_define_singleton_method( rb_mKernel, "backtrace_includes?", rb_RPRuby_Sender_Kernel_backtrace_includes, -1 ); } /*************************************************************************************************************************************************************** **************************************************************************************************************************************************************** Ruby Kernel Methods **************************************************************************************************************************************************************** ***************************************************************************************************************************************************************/ /****************** * __backtrace__ * ******************/ /* * call-seq: * Kernel.backtrace( number_of_frames = nil ) -> [ { :object => object, :method => method }, ... ] * * Return array of hashes with object and method frame information for backtrace. * Specifying number_of_frames will cause only the last number_of_frames to be returned. * Kernel.backtrace returns all frames including the current context (__method__/__callee__). */ VALUE rb_RPRuby_Sender_Kernel_backtrace( int argc, VALUE* args, VALUE rb_self ) { // Get max stack level from args if it is there int c_max_stack_level = 0; if ( argc ) { c_max_stack_level = FIX2INT( args[ 0 ] ); // if max_stack_level is 0 return empty array if ( c_max_stack_level == 0 ) { return rb_ary_new(); } // if max_stack_level < 0, throw error else if ( c_max_stack_level < 0 ) { rb_raise( rb_eArgError, RPRUBY_SENDER_ERROR_STACK_LEVEL_LESS_THAN_ZERO ); } } rb_thread_t* c_thread = GET_THREAD(); // Get the current frame - we're doing a backtrace, so our current working frame to start is the first previous thread rb_control_frame_t* c_current_context_frame = RUBY_VM_PREVIOUS_CONTROL_FRAME( c_thread->cfp ); // c_top_of_control_frame describes the top edge of the stack trace // set c_top_of_control_frame to the first frame in
rb_control_frame_t* c_top_of_control_frame = RUBY_VM_NEXT_CONTROL_FRAME( RUBY_VM_NEXT_CONTROL_FRAME( (void *)( c_thread->stack + c_thread->stack_size ) ) ); VALUE rb_return_array = rb_ary_new(); int c_stack_level = 0; // for each control frame: while ( c_current_context_frame < c_top_of_control_frame && ( argc == 0 || c_stack_level < c_max_stack_level ) ) { VALUE rb_frame_hash = rb_RPRuby_Sender_Kernel_internal_backtraceHashForControlFrame( & c_current_context_frame ); // push hash to array rb_ary_push( rb_return_array, rb_frame_hash ); c_current_context_frame = RUBY_VM_PREVIOUS_CONTROL_FRAME( c_current_context_frame ); c_stack_level++; } return rb_return_array; } /***************************** * __backtrace_includes?__ * ****************************/ /* * call-seq: * Kernel.backtrace_includes?( method_or_object, ... ) -> true or false * Kernel.backtrace_includes?( number_of_frames, method_or_object, ... ) -> true or false * * Returns whether specified methods or objects or classes are in the current backtrace context. * Kernel.backtrace_includes? begins with the prior frame, so asking if the backtrace includes the current method * will only report true if the current method is part of the earlier call chain. */ VALUE rb_RPRuby_Sender_Kernel_backtrace_includes( int argc, VALUE* args, VALUE rb_self ) { // create tracking array VALUE rb_tracking_array = rb_ary_new(); // populate tracking array with methods/objects int c_which_arg = 0; for ( c_which_arg = 0 ; c_which_arg < argc ; c_which_arg++ ) { rb_ary_push( rb_tracking_array, args[ c_which_arg ] ); } rb_thread_t* c_thread = GET_THREAD(); // Get the current frame - we're doing a backtrace, so our current working frame to start is the first previous thread rb_control_frame_t* c_current_context_frame = RUBY_VM_PREVIOUS_CONTROL_FRAME( RUBY_VM_PREVIOUS_CONTROL_FRAME( c_thread->cfp ) ); // c_top_of_control_frame describes the top edge of the stack trace // set c_top_of_control_frame to the first frame in
rb_control_frame_t* c_top_of_control_frame = RUBY_VM_NEXT_CONTROL_FRAME( RUBY_VM_NEXT_CONTROL_FRAME( (void *)( c_thread->stack + c_thread->stack_size ) ) ); // for each control frame: while ( c_current_context_frame < c_top_of_control_frame ) { // iterate each array member int c_which_member; for ( c_which_member = 0 ; c_which_member < RARRAY_LEN( rb_tracking_array ) ; c_which_member++ ) { VALUE rb_array_member = args[ c_which_member ]; int matched = 0; // if rb_array_member is a class if ( TYPE( rb_array_member ) == T_CLASS ) { // if rb_array_member is the current frame's object's class if ( rb_array_member == rb_class_of( c_current_context_frame->self ) ) { matched = 1; } } // if rb_array_member is a method symbol and matches our current frame's method else if ( TYPE( rb_array_member ) == T_SYMBOL ) { if ( rb_to_id( rb_array_member ) == frame_func_id( c_current_context_frame ) ) { matched = 1; } } // if rb_array_member is an object else if ( TYPE( rb_array_member ) == T_OBJECT ) { // if rb_array_member is the current frame's object (self) if ( rb_array_member == c_current_context_frame->self ) { matched = 1; } } // if array member exists in frame, remove from array if ( matched ) { // delete this index rb_ary_delete_at( rb_tracking_array, c_which_member ); // decrement the loop iterator so that the increase is offset // this is necessary since we just removed an index and are iterating vs. the length of the array c_which_member--; } } // if array is empty, return true // we check here as well as at the end so we can stop iterating the backtrace if we find all our items if ( RARRAY_LEN( rb_tracking_array ) == 0 ) { return Qtrue; } c_current_context_frame = RUBY_VM_PREVIOUS_CONTROL_FRAME( c_current_context_frame ); } // if we finish iterating frames and still have items in the array, return false if ( RARRAY_LEN( rb_tracking_array ) > 0 ) { return Qfalse; } // otherwise, return true return Qtrue; } /********************************** * backtraceHashForControlFrame * *********************************/ VALUE rb_RPRuby_Sender_Kernel_internal_backtraceHashForControlFrame( rb_control_frame_t** c_current_frame ) { const char* c_method_name = NULL; int c_sourcefile_line = 0; // create new hash for this frame VALUE rb_frame_hash = rb_hash_new(); VALUE rb_sourcefile_name = Qnil; VALUE rb_sourcefile_line = Qnil; VALUE rb_method_name = Qnil; VALUE rb_object_for_frame = Qnil; if ( ( *c_current_frame )->iseq != 0 ) { if ( ( *c_current_frame )->pc != 0 ) { rb_iseq_t *iseq = ( *c_current_frame )->iseq; // get sourcefile name and set in hash rb_sourcefile_name = iseq->filename; // get sourcefile line and set in hash c_sourcefile_line = rb_vm_get_sourceline( *c_current_frame ); rb_sourcefile_line = INT2FIX( c_sourcefile_line ); // get name of instruction sequence rb_method_name = ID2SYM( rb_intern( StringValuePtr( iseq->name ) ) ); } } else if ( RUBYVM_CFUNC_FRAME_P( *c_current_frame ) ) { // get name of method c_method_name = rb_id2name( ( *c_current_frame )->method_id ); rb_method_name = ( c_method_name == NULL ? Qnil : ID2SYM( rb_intern( c_method_name ) ) ); } else { // The third possibility is that we have an iseq frame with nil params for what we want // In that case we can simply return the next frame *c_current_frame = RUBY_VM_PREVIOUS_CONTROL_FRAME( *c_current_frame ); // in theory this could crash because we are going forward a frame when we don't know what's there // in practice I think we are ok, since we are only jumping forward from nil frames which should never be at the end // at least - I don't think they should... we shall see. // // a fix would be to check the next frame, but that requires access to the thread or the limit cfp, // which requires passing more context; so for now, I'm leaving it there return rb_RPRuby_Sender_Kernel_internal_backtraceHashForControlFrame( c_current_frame ); } // Push values to return hash rb_object_for_frame = ( *c_current_frame )->self; rb_hash_aset( rb_frame_hash, ID2SYM( rb_intern( "object" ) ), rb_object_for_frame ); rb_hash_aset( rb_frame_hash, ID2SYM( rb_intern( "file" ) ), rb_sourcefile_name ); rb_hash_aset( rb_frame_hash, ID2SYM( rb_intern( "line" ) ), rb_sourcefile_line ); rb_hash_aset( rb_frame_hash, ID2SYM( rb_intern( "method" ) ), rb_method_name ); return rb_frame_hash; }