class ADSL::Extract::Instrumenter

Attributes

instrumentation_filters[RW]
method_locals_stack[R]
stack_depth[R]

Public Class Methods

get_instance() click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 25
def self.get_instance()
  @instance
end
instrumented() click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 33
def self.instrumented()
  # a dummy method injected into the AST
end
new(instrument_domain = Dir.pwd) click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 84
def initialize(instrument_domain = Dir.pwd)
  @instrument_domain = instrument_domain
  @replacers = []
  @stack_depth = 0
  @method_locals_stack = []

  # mark the instrumentation
  replace :defn, :defs do |sexp|
    mark_sexp_instrumented sexp
  end

  # make sure the instrumentation propagates through calls
  replace :call do |sexp|
    # expected format: s(:call, object, method_name, *args)
    # replaced with Extract::Instrumenter.e(instrumenter_id, object, method_name, *args)
    original_object = sexp.sexp_body[0] || s(:self)
    original_method_name = sexp.sexp_body[1]
    original_args = sexp.sexp_body[2..-1]

    next sexp if [s(:self), nil].include? original_object and Kernel.respond_to? original_method_name

    s(:call, nil, :ins_call, original_object, s(:lit, original_method_name), *original_args)
  end
end

Public Instance Methods

convert_root_defs_into_defn(sexp) click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 152
def convert_root_defs_into_defn(sexp)
  sexp.sexp_type == :defs ? s(:defn, *sexp[2..-1]) : sexp
end
exec_within() { |self| ... } click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 49
def exec_within
  Instrumenter.instance_variable_set(:@instance, self) if @stack_depth == 0
  @stack_depth += 1
  @method_locals_stack << create_locals if respond_to? :create_locals

  return yield(self)
ensure
  @stack_depth -= 1
  @method_locals_stack.pop
  Instrumenter.instance_variable_set(:@instance, nil) if @stack_depth == 0
end
execute_instrumented(object, method_name, *args, &block) click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 145
def execute_instrumented(object, method_name, *args, &block)
  self.exec_within do
    instrument object, method_name
    return object.send method_name, *args, &block
  end
end
instrument(object, method_name) click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 166
def instrument(object, method_name)
  if should_instrument? object, method_name
    begin
      # this is complex because I want to avoid using .method on non-class objects
      # because they might implement method themselves
      method = object.singleton_class.instance_method method_name
      
      source = method.source
      
      # Ruby 2.0.0 support is in development as of writing this
      sexp = ruby_parser.process source

      unless sexp.nil?
        sexp = convert_root_defs_into_defn sexp

        instrumented_sexp = instrument_sexp sexp
        
        new_code = Ruby2Ruby.new.process instrumented_sexp

        object.replace_method method_name, new_code
        
        new_code
      else
        source
      end
    rescue MethodSource::SourceNotFoundError
    end
  end
end
instrument_sexp(sexp) click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 196
def instrument_sexp(sexp)
  return nil if sexp.nil?
  @replacers.reverse_each do |types, block, options|
    sexp = sexp.block_replace *types, options, &block
  end
  sexp
end
instrument_string(source) click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 156
def instrument_string(source)
  sexp = ruby_parser.process source
  unless sexp.nil?
    instrumented_sexp = instrument_sexp sexp
    new_code = Ruby2Ruby.new.process instrumented_sexp
  else
    source
  end
end
mark_sexp_instrumented(sexp) click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 61
def mark_sexp_instrumented(sexp)
  raise 'Already instrumented' if sexp_instrumented? sexp
  
  first_stmt = sexp[3]

  if first_stmt[0] != :call or
      first_stmt[1] != Instrumenter.to_sexp or
      first_stmt[2] != :instrumented
    new_stmt = s(:call, Instrumenter.to_sexp, :instrumented)
    sexp.insert 3, new_stmt
  end
  sexp
end
method_locals() click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 37
def method_locals
  @method_locals_stack.last
end
previous_locals() click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 41
def previous_locals
  @method_locals_stack[-2]
end
replace(*types, &block) click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 133
def replace(*types, &block)
  options = types.last.is_a?(Hash) ? types.pop : {}
  @replacers << [types, block, options]
end
root_locals() click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 45
def root_locals
  @method_locals_stack.first
end
ruby_parser() click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 29
def ruby_parser
  RUBY_VERSION >= '2' ? Ruby19Parser.new : RubyParser.for_current_ruby
end
sexp_instrumented?(sexp) click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 75
def sexp_instrumented?(sexp)
  first_stmt = sexp[3]
  return (first_stmt[0] == :call and
      first_stmt[1] == Instrumenter.to_sexp and
      first_stmt[2] == :instrumented)
rescue MethodSource::SourceNotFoundError
  return
end
should_instrument?(object, method_name) click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 109
def should_instrument?(object, method_name)
  return false if object.is_a?(Fixnum) or object.is_a?(Symbol)
 
  method = object.singleton_class.instance_method method_name

  return false if method.source_location.nil?
  return false if method.owner == Kernel
  return false if @instrument_domain && !(method.source_location.first =~ /^#{@instrument_domain}.*$/)

  (instrumentation_filters || []).each do |filter|
    return false unless filter.allow_instrumentation? object, method_name
  end
  
  source = method.source
  sexp = ruby_parser.process source
  !sexp_instrumented? sexp
rescue MethodSource::SourceNotFoundError
  # sometimes this happens because the method_source gem bugs out with evals etc
  return false
rescue NameError => e
  # ghost method with no available source
  return false
end
with_replace(*types, replacer) { || ... } click to toggle source
# File lib/adsl/extract/instrumenter.rb, line 138
def with_replace(*types, replacer)
  replace *types, replacer
  yield
ensure
  @replacers.pop
end