gun_dog 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/gun_dog.gemspec +1 -0
- data/lib/gun_dog/call_record.rb +35 -12
- data/lib/gun_dog/trace_maker.rb +112 -33
- data/lib/gun_dog/trace_stack.rb +13 -2
- data/lib/gun_dog/utilities.rb +17 -1
- data/lib/gun_dog/version.rb +1 -1
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: efabc10a53ec099193771fc99c5f2b81f058c4d6
|
|
4
|
+
data.tar.gz: fa3ddf6614c1ba10ab5d7edaf96fc86e118592cc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 56fbd2df15fef26b10fcd08d6e38698e00bb82016f98584ccc031dfd71bfb62a55eaf3c64f8718a2b4d71316bee315dc5bd7f8c29ee6c34e7dd30786dbae34e6
|
|
7
|
+
data.tar.gz: b822005d9050925dce5979fb35d89237daea139422ab22dd45c6803b9b4e2e1051b69325a69cf01d85ecccb118680c39e6183850917253cd2afeff78f7a656ae
|
data/gun_dog.gemspec
CHANGED
|
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
|
|
|
22
22
|
|
|
23
23
|
spec.add_dependency "multi_json", "~> 1.12"
|
|
24
24
|
spec.add_dependency "activesupport", ">= 4.2.9"
|
|
25
|
+
spec.add_dependency "method_source"
|
|
25
26
|
|
|
26
27
|
spec.add_development_dependency "bundler", "~> 1.15"
|
|
27
28
|
spec.add_development_dependency "rake", "~> 10.0"
|
data/lib/gun_dog/call_record.rb
CHANGED
|
@@ -2,7 +2,7 @@ module GunDog
|
|
|
2
2
|
class CallRecord
|
|
3
3
|
using ClassEncoding
|
|
4
4
|
|
|
5
|
-
attr_accessor :args, :return_value, :method_name
|
|
5
|
+
attr_accessor :args, :return_value, :method_name, :extra_module
|
|
6
6
|
attr_accessor :stack
|
|
7
7
|
|
|
8
8
|
attr_writer :internal, :cyclical, :dynamic
|
|
@@ -11,8 +11,8 @@ module GunDog
|
|
|
11
11
|
cr = new(
|
|
12
12
|
Utilities.get_class(json['klass']),
|
|
13
13
|
json['method_name'],
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
origin: json['origin'],
|
|
15
|
+
extra_module: ( Utilities.get_class(json['extra_module']) if json['extra_module'] )
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
cr.instance_eval do
|
|
@@ -26,11 +26,11 @@ module GunDog
|
|
|
26
26
|
cr
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def initialize(klass, method_name,
|
|
29
|
+
def initialize(klass, method_name, origin: false, extra_module: nil)
|
|
30
30
|
@klass = klass
|
|
31
31
|
@method_name = method_name
|
|
32
|
-
@
|
|
33
|
-
@
|
|
32
|
+
@origin = origin
|
|
33
|
+
@extra_module = extra_module
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def method_location
|
|
@@ -50,11 +50,32 @@ module GunDog
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def class_method?
|
|
53
|
-
|
|
53
|
+
@origin == :eigen
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
def
|
|
57
|
-
|
|
56
|
+
def origin
|
|
57
|
+
@origin
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def special_origin?
|
|
61
|
+
[:meta, :included, :refinement].include?(@origin)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def origin_string
|
|
65
|
+
case @origin
|
|
66
|
+
when :meta
|
|
67
|
+
'[generated]'
|
|
68
|
+
when :included
|
|
69
|
+
"[#{unbound_method.owner.name.to_s}]"
|
|
70
|
+
when :extended
|
|
71
|
+
"[#{extra_module.name.to_s} (extended)]"
|
|
72
|
+
when :prepended
|
|
73
|
+
"[#{unbound_method.owner.name.to_s} (prepended)]"
|
|
74
|
+
when :refinement
|
|
75
|
+
"[using #{extra_module.split('@')[1][0..-2]}]"
|
|
76
|
+
else
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
58
79
|
end
|
|
59
80
|
|
|
60
81
|
def unbound_method
|
|
@@ -66,7 +87,7 @@ module GunDog
|
|
|
66
87
|
end
|
|
67
88
|
|
|
68
89
|
def call_record_signature
|
|
69
|
-
"#{
|
|
90
|
+
"#{origin_string} \
|
|
70
91
|
def #{class_method? ? "self." : nil}#{method_name}(#{type_signatures(args)}) : #{return_value.class} \
|
|
71
92
|
#{ internal? ? " (internal)" : nil } \
|
|
72
93
|
#{ cyclical? ? " (cyclical)" : nil } \
|
|
@@ -77,8 +98,8 @@ module GunDog
|
|
|
77
98
|
{
|
|
78
99
|
"klass" => @klass.json_encoded,
|
|
79
100
|
"method_name" => method_name.to_s,
|
|
80
|
-
"
|
|
81
|
-
"
|
|
101
|
+
"origin" => origin,
|
|
102
|
+
"extra_module" => @extra_module,
|
|
82
103
|
"internal" => internal?,
|
|
83
104
|
"cyclical" => cyclical?,
|
|
84
105
|
"dynamic" => dynamic?,
|
|
@@ -95,6 +116,8 @@ module GunDog
|
|
|
95
116
|
#TODO pass v here back through this method to show the type signatures for each method rather than
|
|
96
117
|
# individual arguments
|
|
97
118
|
args.each_pair.map { |k,v| "#{k} : #{v ? v.to_s : v.class}" }.join(', ')
|
|
119
|
+
elsif origin == :refinement
|
|
120
|
+
'?'
|
|
98
121
|
else
|
|
99
122
|
args.each_pair.map { |k,v| "#{k} : #{v.class}" }.join(', ')
|
|
100
123
|
end
|
data/lib/gun_dog/trace_maker.rb
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
require 'active_support/core_ext/module/anonymous'
|
|
2
|
+
require 'method_source'
|
|
2
3
|
|
|
3
4
|
module GunDog
|
|
4
5
|
class TraceMaker
|
|
5
|
-
|
|
6
|
+
using GunDog::Utilities::RefinementIntrospection
|
|
7
|
+
|
|
8
|
+
attr_reader :trace_report, :return_trace, :call_trace, :klass, :complete_call_list, :c_call_trace
|
|
6
9
|
|
|
7
10
|
def initialize(klass, suppress: [], &exec_block)
|
|
8
11
|
@klass = klass
|
|
9
12
|
@trace_report = TraceReport.new(klass, suppression_set: build_suppression_set(suppress))
|
|
10
13
|
@trace_report.stack << MethodOwnerStackFrame.new(GunDog, :trace)
|
|
11
14
|
@ancestor_cache = {}
|
|
15
|
+
@included_cache = {}
|
|
12
16
|
@exec_block = exec_block
|
|
17
|
+
@mrt_tracers = []
|
|
13
18
|
end
|
|
14
19
|
|
|
15
20
|
def exec
|
|
16
21
|
set_trace
|
|
22
|
+
wrap_accessor_functions
|
|
17
23
|
|
|
18
24
|
call_trace.enable do
|
|
19
25
|
return_trace.enable do
|
|
@@ -24,6 +30,8 @@ module GunDog
|
|
|
24
30
|
trace_report.finalize_report
|
|
25
31
|
trace_report
|
|
26
32
|
ensure
|
|
33
|
+
@mrt_tracers.each(&:disable)
|
|
34
|
+
unwrap_accessor_functions
|
|
27
35
|
call_trace.disable
|
|
28
36
|
return_trace.disable
|
|
29
37
|
end
|
|
@@ -33,6 +41,36 @@ module GunDog
|
|
|
33
41
|
set_call_trace
|
|
34
42
|
end
|
|
35
43
|
|
|
44
|
+
def unwrap_accessor_functions
|
|
45
|
+
accessor_methods.map do |m|
|
|
46
|
+
klass.send(:remove_method, m)
|
|
47
|
+
|
|
48
|
+
if m.to_s[-1] == '='
|
|
49
|
+
klass.send :attr_writer, m[0..-2]
|
|
50
|
+
else
|
|
51
|
+
klass.send :attr_reader, m
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def wrap_accessor_functions
|
|
57
|
+
accessor_methods.map do |m|
|
|
58
|
+
if m.to_s[-1] == '='
|
|
59
|
+
klass.module_eval <<~RUBY, __FILE__, __LINE__
|
|
60
|
+
def #{m.to_s}(arg1)
|
|
61
|
+
@#{m.to_s[0..-2]} = arg1
|
|
62
|
+
end
|
|
63
|
+
RUBY
|
|
64
|
+
else
|
|
65
|
+
klass.module_eval <<~RUBY, __FILE__, __LINE__
|
|
66
|
+
def #{m.to_s}
|
|
67
|
+
@#{m.to_s}
|
|
68
|
+
end
|
|
69
|
+
RUBY
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
36
74
|
def set_return_trace
|
|
37
75
|
@return_trace ||= TracePoint.new(:return) do |tp|
|
|
38
76
|
trace_report.stack.pop
|
|
@@ -41,52 +79,75 @@ module GunDog
|
|
|
41
79
|
|
|
42
80
|
def set_call_trace
|
|
43
81
|
@call_trace ||= TracePoint.new(:call) do |tp|
|
|
44
|
-
|
|
82
|
+
begin
|
|
83
|
+
tp.disable
|
|
45
84
|
|
|
46
|
-
|
|
85
|
+
trace_report.stack << MethodOwnerStackFrame.new(tp.defined_class, tp.method_id)
|
|
47
86
|
|
|
48
|
-
|
|
49
|
-
|
|
87
|
+
binding_class = tp.binding.receiver.class
|
|
88
|
+
trace_type = trace_method(binding_class, tp.defined_class, tp.self)
|
|
89
|
+
method_id = tp.binding.eval('__callee__') || tp.method_id
|
|
50
90
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
91
|
+
unless trace_type
|
|
92
|
+
tp.enable
|
|
93
|
+
next
|
|
94
|
+
end
|
|
55
95
|
|
|
56
|
-
|
|
96
|
+
call_record = nil
|
|
57
97
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
98
|
+
if trace_type == :refinement
|
|
99
|
+
call_record = CallRecord.new(
|
|
100
|
+
klass,
|
|
101
|
+
method_id,
|
|
102
|
+
origin: trace_type,
|
|
103
|
+
extra_module: tp.defined_class.to_s
|
|
104
|
+
)
|
|
105
|
+
else
|
|
106
|
+
called_method = if trace_type == :meta || trace_type == :included
|
|
107
|
+
tp.binding.eval('self').method(method_id)
|
|
108
|
+
else
|
|
109
|
+
tp.self.method(tp.method_id)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
call_record = CallRecord.new(
|
|
113
|
+
klass,
|
|
114
|
+
called_method.name,
|
|
115
|
+
origin: trace_type,
|
|
116
|
+
extra_module: (tp.defined_class if trace_type == :extended)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
call_record.args = called_method.parameters.each.with_object({}) do |p, memo|
|
|
120
|
+
memo[p.last] = tp.binding.local_variable_get(p.last)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
63
123
|
|
|
64
|
-
|
|
124
|
+
trace_report.call_records << call_record
|
|
65
125
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
class_method: trace_type == :eigen,
|
|
70
|
-
generated: trace_type == :meta
|
|
71
|
-
)
|
|
126
|
+
mrt = set_method_return_trace(call_record)
|
|
127
|
+
@mrt_tracers << mrt
|
|
128
|
+
mrt.enable
|
|
72
129
|
|
|
73
|
-
|
|
74
|
-
|
|
130
|
+
tp.enable
|
|
131
|
+
rescue => e
|
|
132
|
+
STDOUT.puts "#{tp.method_id} => #{trace_type} (#{tp.defined_class})"
|
|
133
|
+
puts "#{e.class} => #{e.message}"
|
|
134
|
+
puts "=============================================================================="
|
|
135
|
+
puts e.backtrace
|
|
136
|
+
puts "=============================================================================="
|
|
137
|
+
tp.disable
|
|
75
138
|
end
|
|
76
|
-
|
|
77
|
-
trace_report.call_records << call_record
|
|
78
|
-
|
|
79
|
-
set_method_return_trace(call_record).enable
|
|
80
|
-
|
|
81
|
-
tp.enable
|
|
82
139
|
end
|
|
83
140
|
end
|
|
84
141
|
|
|
85
|
-
def trace_method(binding_class, defined_class)
|
|
142
|
+
def trace_method(binding_class, defined_class, obj)
|
|
86
143
|
return :eigen if defined_class == klass.singleton_class
|
|
87
144
|
return false if binding_class != klass
|
|
88
|
-
return :
|
|
145
|
+
return :refinement if defined_class.is_refinement?
|
|
146
|
+
return :meta if klass < defined_class && defined_class.anonymous?
|
|
147
|
+
return :prepended if klass < defined_class && prepended?(defined_class)
|
|
148
|
+
return :included if klass < defined_class && after_super?(defined_class)
|
|
89
149
|
return :instance if defined_class == klass
|
|
150
|
+
return :extended if obj.singleton_class < defined_class && after_super?(defined_class)
|
|
90
151
|
false
|
|
91
152
|
end
|
|
92
153
|
|
|
@@ -101,16 +162,25 @@ module GunDog
|
|
|
101
162
|
end
|
|
102
163
|
end
|
|
103
164
|
|
|
165
|
+
def prepended?(defined_class)
|
|
166
|
+
if @included_cache.has_key?(defined_class)
|
|
167
|
+
@included_cache[defined_class]
|
|
168
|
+
else
|
|
169
|
+
ancestors = klass.ancestors
|
|
170
|
+
@included_cache[defined_class] = ancestors.index(klass) > ancestors.index(defined_class)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
104
174
|
def set_method_return_trace(call_record)
|
|
105
175
|
# instantiate a new return tracepoint to watch for the return of this
|
|
106
176
|
# method only
|
|
107
|
-
#
|
|
108
177
|
TracePoint.new(:return) do |mrt|
|
|
109
178
|
method_id = mrt.binding.eval('__callee__') || mrt.method_id
|
|
110
179
|
next if method_id != call_record.method_name
|
|
111
180
|
mrt.disable
|
|
112
181
|
call_record.return_value = mrt.return_value
|
|
113
182
|
|
|
183
|
+
|
|
114
184
|
if trace_report.stack.internal_stack?
|
|
115
185
|
call_record.internal = true
|
|
116
186
|
call_record.stack = trace_report.stack.since_first_klass_entry
|
|
@@ -126,6 +196,15 @@ module GunDog
|
|
|
126
196
|
end
|
|
127
197
|
end
|
|
128
198
|
|
|
199
|
+
private
|
|
200
|
+
|
|
201
|
+
def accessor_methods
|
|
202
|
+
@accessor_methods ||= klass.instance_methods.map { |m| klass.instance_method(m) }
|
|
203
|
+
.select { |m| m.owner == klass }
|
|
204
|
+
.select { |m| m.source =~ /attr/ }
|
|
205
|
+
.map { |m| m.name }
|
|
206
|
+
end
|
|
207
|
+
|
|
129
208
|
def build_suppression_set(suppression_list)
|
|
130
209
|
suppression_list.map { |method_id|
|
|
131
210
|
begin
|
data/lib/gun_dog/trace_stack.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
module GunDog
|
|
2
2
|
class TraceStack < Array
|
|
3
|
+
using Utilities::RefinementIntrospection
|
|
4
|
+
|
|
3
5
|
attr_reader :collaborating_classes, :klass
|
|
4
6
|
|
|
5
7
|
def self.from_json(json)
|
|
@@ -22,7 +24,13 @@ module GunDog
|
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
def classes_in_stack
|
|
25
|
-
self.group_by
|
|
27
|
+
self.group_by { |frame|
|
|
28
|
+
if refined_class = frame.klass.to_s[/#<refinement:(.*?)@/,1]
|
|
29
|
+
Kernel.const_get(refined_class)
|
|
30
|
+
else
|
|
31
|
+
frame.klass
|
|
32
|
+
end
|
|
33
|
+
}.keys.to_set
|
|
26
34
|
end
|
|
27
35
|
|
|
28
36
|
def initialize(klass)
|
|
@@ -51,6 +59,7 @@ module GunDog
|
|
|
51
59
|
end
|
|
52
60
|
|
|
53
61
|
def preceded_by_traced_klass?
|
|
62
|
+
# FIXME this method is terribly ineffcient
|
|
54
63
|
traced_klasses = self.map(&:klass)
|
|
55
64
|
traced_klasses.include?(klass) || traced_klasses.include?(klass.singleton_class)
|
|
56
65
|
end
|
|
@@ -85,7 +94,9 @@ module GunDog
|
|
|
85
94
|
end
|
|
86
95
|
|
|
87
96
|
def frame_owned_by_traced_klass?(frame)
|
|
88
|
-
|
|
97
|
+
return true if frame.klass == klass
|
|
98
|
+
return true if frame.klass.singleton_class == klass.singleton_class
|
|
99
|
+
return true if frame.klass.refined_class == klass
|
|
89
100
|
end
|
|
90
101
|
|
|
91
102
|
def as_json
|
data/lib/gun_dog/utilities.rb
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
module GunDog
|
|
2
2
|
module Utilities
|
|
3
|
+
module RefinementIntrospection
|
|
4
|
+
refine Module do
|
|
5
|
+
def is_refinement?
|
|
6
|
+
to_s.present? && to_s =~ /refinement/
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def refined_class
|
|
10
|
+
target = to_s[/#<refinement:(.*?)@/,1]
|
|
11
|
+
return nil unless target
|
|
12
|
+
const_get(target)
|
|
13
|
+
rescue NoMethodError => e
|
|
14
|
+
# hack - this is raised on AR objects if you call to_s before the
|
|
15
|
+
# the inherited hooks are finished running
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
3
20
|
class UnencodableClass; end
|
|
4
21
|
|
|
5
22
|
def self.get_class(str)
|
|
@@ -13,7 +30,6 @@ module GunDog
|
|
|
13
30
|
begin
|
|
14
31
|
eval(str)
|
|
15
32
|
rescue => e
|
|
16
|
-
require 'pry'; binding.pry
|
|
17
33
|
e.message
|
|
18
34
|
end
|
|
19
35
|
end
|
data/lib/gun_dog/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gun_dog
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stephen Prater
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2017-
|
|
11
|
+
date: 2017-11-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: multi_json
|
|
@@ -38,6 +38,20 @@ dependencies:
|
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: 4.2.9
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: method_source
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
41
55
|
- !ruby/object:Gem::Dependency
|
|
42
56
|
name: bundler
|
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|