gun_dog 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|