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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8553b5cae7081c781faffb51279789d50c10b352
4
- data.tar.gz: f1dae43fce48c756e6bc004a7708879d3f1e7d94
3
+ metadata.gz: efabc10a53ec099193771fc99c5f2b81f058c4d6
4
+ data.tar.gz: fa3ddf6614c1ba10ab5d7edaf96fc86e118592cc
5
5
  SHA512:
6
- metadata.gz: bab212ec57b6f8d3b10b8fbb4adda795a932aa6b539ecd53b35b3977d628040696fe9544d49508f24be2c4827195b86100269694a8a05cb1ffd8f421c955cc70
7
- data.tar.gz: 53117f89da46453a9e567265ab8bb781c2c6431b859b713fdefa57c502b5f62355ef9e1456001fe5c88439505bfa0704bcd7f432cd7ca4610f0155e6d71107b8
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"
@@ -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
- class_method: json['class_method'],
15
- generated: json['generated']
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, class_method: false, generated: false)
29
+ def initialize(klass, method_name, origin: false, extra_module: nil)
30
30
  @klass = klass
31
31
  @method_name = method_name
32
- @class_method = class_method
33
- @generated = generated
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
- !!@class_method
53
+ @origin == :eigen
54
54
  end
55
55
 
56
- def generated?
57
- !!@generated
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
- "#{generated? ? "[generated] " : nil } \
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
- "class_method" => class_method?,
81
- "generated" => generated?,
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
@@ -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
- attr_reader :trace_report, :return_trace, :call_trace, :klass, :complete_call_list
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
- trace_report.stack << MethodOwnerStackFrame.new(tp.defined_class, tp.method_id)
82
+ begin
83
+ tp.disable
45
84
 
46
- tp.disable
85
+ trace_report.stack << MethodOwnerStackFrame.new(tp.defined_class, tp.method_id)
47
86
 
48
- binding_class = tp.binding.eval('self').class
49
- trace_type = trace_method(binding_class, tp.defined_class)
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
- unless trace_type
52
- tp.enable
53
- next
54
- end
91
+ unless trace_type
92
+ tp.enable
93
+ next
94
+ end
55
95
 
56
- method_id = tp.binding.eval('__callee__') || tp.method_id
96
+ call_record = nil
57
97
 
58
- called_method = if trace_type == :meta
59
- tp.binding.eval('self').method(method_id)
60
- else
61
- tp.self.method(tp.method_id)
62
- end
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
- # next if trace_report.suppression_set.include?(called_method.unbind)
124
+ trace_report.call_records << call_record
65
125
 
66
- call_record = CallRecord.new(
67
- klass,
68
- called_method.name,
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
- call_record.args = called_method.parameters.each.with_object({}) do |p, memo|
74
- memo[p.last] = tp.binding.local_variable_get(p.last)
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 :meta if klass < defined_class && (defined_class.anonymous? || after_super?(defined_class))
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
@@ -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(&:klass).keys.to_set
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
- frame.klass == klass || frame.klass.singleton_class == klass.singleton_class
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module GunDog
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
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.2
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-10-19 00:00:00.000000000 Z
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