Class: Trace::Filter

Inherits:
Object
  • Object
show all
Defined in:
lib/tracefilter.rb

Overview

A class that can be used to test whether certain methods should be excluded. We also convert the hook call to pass a threadframe and an event rather than event and those other 5 parameters.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(excluded_meths = []) ⇒ Filter

Returns a new instance of Filter.



17
18
19
20
21
# File 'lib/tracefilter.rb', line 17

def initialize(excluded_meths = [])
  excluded_meths = excluded_meths.select{|fn| valid_meth?(fn)}
  excluded_meths << self.method(:set_trace_func).to_s
  @excluded = Set.new(excluded_meths.map{|m| m.to_s})
end

Instance Attribute Details

#excludedObject (readonly)

Returns the value of attribute excluded.



14
15
16
# File 'lib/tracefilter.rb', line 14

def excluded
  @excluded
end

#hook_procObject (readonly)

Returns the value of attribute hook_proc.



15
16
17
# File 'lib/tracefilter.rb', line 15

def hook_proc
  @hook_proc
end

Instance Method Details

#<<(meth) ⇒ Object

Add meth to list of trace filters.



30
31
32
33
34
35
36
37
# File 'lib/tracefilter.rb', line 30

def <<(meth)
  if valid_meth?(meth)
    @excluded << meth.to_s
    return true
  else
    return false
  end
end

#add_trace_func(hook_proc, event_mask = nil) ⇒ Object

Replacement for Kernel.add_trace_func. hook_proc should be a Proc that takes two arguments, a string event, and threadframe object.

Raises:

  • (TypeError)


124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/tracefilter.rb', line 124

def add_trace_func(hook_proc, event_mask=nil)
  if hook_proc.nil?
    Kernel.set_trace_func(nil)
    return
  end
  raise TypeError, 
  "trace_func needs to be Proc or nil (is #{hook_proc.class})" unless 
    hook_proc.is_a?(Proc)
  raise TypeError, "arity of hook_proc should be 2 or -3 (is #{hook_proc.arity})" unless 
    2 == hook_proc.arity || -3 == hook_proc.arity
  @hook_proc = hook_proc
  if event_mask
    Trace::add_trace_func(method(:trace_hook).to_proc, event_mask)
  else
    Kernel.add_trace_func(method(:trace_hook).to_proc)
  end
end

#clearObject

Null out list of excluded methods



40
41
42
# File 'lib/tracefilter.rb', line 40

def clear
  @excluded = Set.new
end

#member?(meth) ⇒ Boolean

Returns true if meth is a member of the trace filter set.

Returns:

  • (Boolean)


45
46
47
48
49
50
51
# File 'lib/tracefilter.rb', line 45

def member?(meth)
  if valid_meth?(meth)
    @excluded.member?(meth.to_s)
  else
    false
  end
end

#remove(meth) ⇒ Object

Remove meth from the list of functions to include.



54
55
56
57
# File 'lib/tracefilter.rb', line 54

def remove(meth)
  return nil unless valid_meth?(meth)
  @excluded -= [meth.to_s]
end

#remove_trace_funcObject

FIXME: remove just this trace function



143
144
145
# File 'lib/tracefilter.rb', line 143

def remove_trace_func
    Kernel.clear_trace_func
end

#set_trace_func(hook_proc, event_mask = nil) ⇒ Object

Replacement for Kernel.set_trace_func. proc should be a Proc that takes two arguments, a string event, and threadframe object.

Raises:

  • (TypeError)


150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/tracefilter.rb', line 150

def set_trace_func(hook_proc, event_mask=nil)
  if hook_proc.nil?
    Kernel.set_trace_func(nil)
    return
  end
  raise TypeError, 
  "trace_func needs to be Proc or nil (is #{hook_proc.class})" unless 
    hook_proc.is_a?(Proc)
  raise TypeError, "arity of hook_proc should be 2 or -3 (is #{hook_proc.arity})" unless 
    2 == hook_proc.arity || -3 == hook_proc.arity
  @hook_proc = hook_proc
  if event_mask
    Trace::set_trace_func(method(:trace_hook).to_proc, event_mask)
  else
    Kernel.set_trace_func(method(:trace_hook).to_proc)
  end
end

#trace_hook(event, file, line, id, binding, klass) ⇒ Object

A shim to convert between older-style trace hook call to newer style trace hook using RubyVM::Frame. Methods stored in @excluded are ignored.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/tracefilter.rb', line 62

def trace_hook(event, file, line, id, binding, klass)
  tf = RubyVM::Frame::current.prev

  # FIXME: Clean this mess up. And while your at it, understand
  # what's going on better.
  tf_check = tf

  while %w(IFUNC CFUNC).member?(tf_check.type) do 
    tf_check = tf_check.prev 
  end
  return unless tf_check

  begin 
    if tf_check.method && !tf_check.method.empty?
      meth_name = tf_check.method.gsub(/^.* in /, '')
      meth = eval("self.method(:#{meth_name})", tf_check.binding)
      if @excluded.member?(meth.to_s)
        # Turn off tracing for any calls from this frame.  Note
        # that Ruby turns of tracing in the thread of a hook while
        # it is running, but I don't think this is as good as
        # turning it off in the frame and frames called from that.
        # Example: if a trace hook yields to or calls a block
        # outside not derived from the frame, then tracing should
        # start again.  But either way, since RubyVM::Frame
        # allows control over tracing *any* decision is not
        # irrevocable, just possibly unhelpful.
        tf_check.trace_off = true
        return 
      end
    end
  rescue NameError
  rescue SyntaxError
  rescue ArgumentError
  end
  while %w(IFUNC).member?(tf.type) do 
    tf = tf.prev 
  end

  # There is what looks like a a bug in Ruby where self.class for C
  # functions are not set correctly. Until this is fixed in what I
  # consider a more proper way, we'll hack around this by passing
  # the binding as the optional arg parameter.
  arg = 
    if 'CFUNC' == tf.type && NilClass != klass 
      klass 
    elsif 'raise' == event
      # As a horrible hack to be able to get the raise message on a
      # 'raise' event before the event occurs, I changed RubyVM to store
      # the message in the class field.
      klass
    else
      nil
    end

  retval = @hook_proc.call(event, tf, arg)
  if retval.respond_to?(:ancestors) && retval.ancestors.include?(Exception)
    raise retval 
  end
end

#valid_meth?(fn) ⇒ Boolean

fn should be a RubyVM::Frame object or a Proc which has an instruction sequence

Returns:

  • (Boolean)


25
26
27
# File 'lib/tracefilter.rb', line 25

def valid_meth?(fn)
  fn.is_a?(Method)
end