Class: GunDog::TraceMaker

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(klass, suppress: [], &exec_block) ⇒ TraceMaker

Returns a new instance of TraceMaker.



7
8
9
10
11
12
13
# File 'lib/gun_dog/trace_maker.rb', line 7

def initialize(klass, suppress: [], &exec_block)
  @klass = klass
  @trace_report = TraceReport.new(klass, suppression_set: build_suppression_set(suppress))
  @trace_report.stack << MethodOwnerStackFrame.new(GunDog, :trace)
  @ancestor_cache = {}
  @exec_block = exec_block
end

Instance Attribute Details

#call_traceObject (readonly)

Returns the value of attribute call_trace.



5
6
7
# File 'lib/gun_dog/trace_maker.rb', line 5

def call_trace
  @call_trace
end

#complete_call_listObject (readonly)

Returns the value of attribute complete_call_list.



5
6
7
# File 'lib/gun_dog/trace_maker.rb', line 5

def complete_call_list
  @complete_call_list
end

#klassObject (readonly)

Returns the value of attribute klass.



5
6
7
# File 'lib/gun_dog/trace_maker.rb', line 5

def klass
  @klass
end

#return_traceObject (readonly)

Returns the value of attribute return_trace.



5
6
7
# File 'lib/gun_dog/trace_maker.rb', line 5

def return_trace
  @return_trace
end

#trace_reportObject (readonly)

Returns the value of attribute trace_report.



5
6
7
# File 'lib/gun_dog/trace_maker.rb', line 5

def trace_report
  @trace_report
end

Instance Method Details

#after_super?(defined_class) ⇒ Boolean

Returns:

  • (Boolean)


93
94
95
96
97
98
99
100
101
102
# File 'lib/gun_dog/trace_maker.rb', line 93

def after_super?(defined_class)
  return true unless klass.superclass != Object

  if @ancestor_cache.has_key?(defined_class)
    @ancestor_cache[defined_class]
  else
    ancestors = klass.ancestors
    @ancestor_cache[defined_class] = ancestors.index(klass.superclass) > ancestors.index(defined_class)
  end
end

#build_suppression_set(suppression_list) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/gun_dog/trace_maker.rb', line 129

def build_suppression_set(suppression_list)
  suppression_list.map { |method_id|
    begin
      if class_method = method_id[/self\.(.*)/,1]
        klass.method(class_method).unbind
      else
        klass.instance_method(method_id)
      end
    rescue NameError
      nil
    end
  }.uniq.to_set
end

#execObject



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/gun_dog/trace_maker.rb', line 15

def exec
  set_trace

  call_trace.enable do
    return_trace.enable do
      @exec_block.call
    end
  end

  trace_report.finalize_report
  trace_report
ensure
  call_trace.disable
  return_trace.disable
end

#set_call_traceObject



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/gun_dog/trace_maker.rb', line 42

def set_call_trace
  @call_trace ||= TracePoint.new(:call) do |tp|
    trace_report.stack << MethodOwnerStackFrame.new(tp.defined_class, tp.method_id)

    tp.disable

    binding_class = tp.binding.eval('self').class
    trace_type = trace_method(binding_class, tp.defined_class)

    unless trace_type
      tp.enable
      next
    end

    method_id = tp.binding.eval('__callee__') || tp.method_id

    called_method = if trace_type == :meta
      tp.binding.eval('self').method(method_id)
    else
      tp.self.method(tp.method_id)
    end

    # next if trace_report.suppression_set.include?(called_method.unbind)

    call_record = CallRecord.new(
      klass,
      called_method.name,
      class_method: trace_type == :eigen,
      generated: trace_type == :meta
    )

    call_record.args = called_method.parameters.each.with_object({}) do |p, memo|
      memo[p.last] = tp.binding.local_variable_get(p.last)
    end

    trace_report.call_records << call_record

    set_method_return_trace(call_record).enable

    tp.enable
  end
end

#set_method_return_trace(call_record) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/gun_dog/trace_maker.rb', line 104

def set_method_return_trace(call_record)
  # instantiate a new return tracepoint to watch for the return of this
  # method only
  #
  TracePoint.new(:return) do |mrt|
    method_id = mrt.binding.eval('__callee__') || mrt.method_id
    next if method_id != call_record.method_name
    mrt.disable
    call_record.return_value = mrt.return_value

    if trace_report.stack.internal_stack?
      call_record.internal = true
      call_record.stack = trace_report.stack.since_first_klass_entry
    elsif trace_report.stack.cyclical_stack?
      call_record.cyclical = true
      call_record.stack = trace_report.stack.since_first_klass_entry
    end

    if trace_report.stack.dynamic_stack?
      call_record.dynamic = true
      call_record.stack = trace_report.stack.since_first_klass_entry
    end
  end
end

#set_return_traceObject



36
37
38
39
40
# File 'lib/gun_dog/trace_maker.rb', line 36

def set_return_trace
  @return_trace ||= TracePoint.new(:return) do |tp|
    trace_report.stack.pop
  end
end

#set_traceObject



31
32
33
34
# File 'lib/gun_dog/trace_maker.rb', line 31

def set_trace
  set_return_trace
  set_call_trace
end

#trace_method(binding_class, defined_class) ⇒ Object



85
86
87
88
89
90
91
# File 'lib/gun_dog/trace_maker.rb', line 85

def trace_method(binding_class, defined_class)
  return :eigen if defined_class == klass.singleton_class
  return false if binding_class != klass
  return :meta if klass < defined_class && (defined_class.anonymous? || after_super?(defined_class))
  return :instance if defined_class == klass
  false
end