Project

General

Profile

Feature #6714

Updated by nobu (Nobuyoshi Nakada) about 10 years ago

# =begin 
 = Abstract 

 Introducing code injection framework.    Different from set_trace_func(), this framework injects codes only specified points. 

 Note that this proposal is not implemented and well designed (only rough idea) but I dumped it to discuss about this topic.    It has (huge) possibility to miss 2.0 spec deadline (should be 3.0 spec?). 


 # = Background 

 To trace, debug, profile and any analysis ruby code, Ruby provides `set_trace_func()' method.    set_trace_func() is enough powerful to do them.    However set_trace_func() injects codes every tracing points.    It cause huge performance impact if you have interest restricted places. 

 Another problem is that set_trace_func() can not affect program behavior.    For example, we can not insert type checking code for specific method invocation. 

 Related works with introducing codes are described below.    Please point out if you know another related works. 

 ## == Bytecode instrumentation 

 JVM has JVMTI interface <http://docs.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#bci> to inject any code with bytecode instrumentation.    It can be done because JVM bytecode is well defined and become concrete specification.    However, Ruby doesn't have any well-defined common bytecode and difficult to make such common bytecode (at least Ruby 2.0 spec deadline, this August). 

 Manipulate bytecode directly has other problems: 
 * Needs more knowledge about bytecode 
 * Difficult to make `well-formed' bytecode sequence 

 ## == AOP (Aspect Oriented Programming) 

 Aspect-Oriented programming frameworks provides `join points' which we can insert codes <http://www.eclipse.org/aspectj/doc/released/progguide/language.html>.    Such `join points' design is well abstracted comparing with bytecode instrumentation. 

 In fact, AOT compilers such as Aspect-J use bytecode instrumentation. 

 ## == Module#prepend 

 We already have Module#prepend that enable to insert any program before/after method invocation. 

 Example: 

     

   module EachTracer # call tracing method before/after each method 
       
     def each(*args) 
         
       before_each 
       begin 
         begin 
           super # call original each 
       ensure 
         ensure 
           after_each 
         end 
       end 
     end 
    
     
   end 
  
   class Array 
       
     prepend EachTracer 
    
       
  
     def before_each 
         
       p:before_each 
       
     end 
       
     def after_each 
         
       p:after_each 
       end 
     end 
    
     
   end 
  
   %w(a b c).each{|c|} 
     
   #=> outputs :before_each and :after_each 

 However, Module#prepend only works for method invocation. 

 # = Proposal 

 Introduce code injection framework.    It should provide two features: (1) "where should insert codes?" and (2) "what code should be insert?". 

 RubyVM::InstructionSequence#each_point(point_name) is temporal API for (1).    each_point invoke block with CodePoint object.    CodePint#set_proc (or something) is for (2). 

 Example (it is rough API idea): 

     

   def m1 
       
     m2(1) 
       
     m2(1, 2, 3) 
       
     m3() 
       
     m4() 
     
   end 
    
     
  
   # insert proc before m2 method invocation 
   method(:m1).iseq.each_point(:before_call){|point| 
     method(:m1).iseq.each_point(:before_call){|point| 
       # point is CodePoint object. 
       
     if point.selector == :m2 
       point.set_proc{|*args| 
         point.set_proc{|*args| 
           p "before call m2 with #{args.inspect}" 
         
       } 
       
     end 
     
   } 
    
     
  
   # another idea 
   method(:m1).iseq.each_point(:invoke_method){|point| 
     method(:m1).iseq.each_point(:invoke_method){|point| 
       if point.selector == :m2 
       point.insert_proc_before{|*args| 
         point.insert_proc_before{|*args| 
           p "before call m2 with #{args.inspect}" 
         
       } 
       
     else point.selector == :m3 
       point.insert_proc_after{|retval| 
         point.insert_proc_after{|retval| 
           p "after call m2 with return value #{retval}" 
         
       } 
       
     else point.slector == :m4 
       point.replace_proc{|*args| 
         point.replace_proc{|*args| 
           p "cancel invoking m4 and call this proc instead" 
         
       } 
       end 
     end 
   end 

 Injection points are categorized into 3 types: 

 * (1) before/after invoke something 
   * method call (before method call) 
   * method call (after method call) 
   * block invocation (before) 
   * block invocation (after) 
   * super invocation (before) 
   * super invocation (after) 

 * (2) enter/leave (not needed?) 
   * method (enter) (set_trace_func/call) 
   * method (leave) (set_trace_func/return) 
   * class/module definition (enter) (set_trace_func/class) 
   * class/module definition (leave) (set_trace_func/end) 
   * block (enter) 
   * block (leave) 
   * rescue (enter) 
   * rescue (leave) 
   * ensure (enter) 
   * ensure (leave) 

 * (3) misc 
   * read variable ($gv, @iv, @@cv) 
   * write variable ($gv, @iv, @@cv) 
   * read constant (Const) 
   * define constant (Const) 
   * method definition 
   * newline (set_trace_func/line) 

 This proposal can introduce (limited) code manipulation without any bytecode knowledge. 

 ## == Usecase 

 * inserting specific break points for debugger 
 * inserting specific analysis points for profiler 
 * inserting type checking code generated by rdoc 
 * making Aspect-J like tool (note that Module#prepend is enough if you only want to replace method invocation behavior) 

 Any other idea? 

 ## == Limitation 

 It is impossible to inject any code into methods implemented by C. 

 I'm afraid that this proposed API makes magical (unreadable) codes for script kiddies :P 

 I repeat it again: Note that this proposal is not implemented and well designed (only rough idea) but I dumped it to discuss about this topic.    It has (huge) possibility to miss 2.0 spec deadline (should be 3.0 spec?). 

 --- 
 Thanks, 
 Koichi 

 =end 

Back