argtrace 0.1.1 → 0.2.0

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
  SHA256:
3
- metadata.gz: 7398f53bb9b94105fc990221979803eaceebffa073e7487b16b558fc65755af6
4
- data.tar.gz: 0744ce1ba76244daf8d442465e6ac7828cdf07b51f486b4b661c619f333419cb
3
+ metadata.gz: 954782bc5b1e4572bb27a4bb008d81dee8a8406b0a55b630d479913cf19dc977
4
+ data.tar.gz: 12e7d2fa9af906a4ff9d7f431a2c54a6e5140beb3b397de9dc1bac3f9c27fa87
5
5
  SHA512:
6
- metadata.gz: 274187675f07b8141b2ddbf05be0d344d5ce885cfc945c0d76912b2b77acfcba447eedaa1cc0dba0b671d1dee8b1dbe79fc4772b1747a8f946952a98749ce5f4
7
- data.tar.gz: 3e05fa0e9067ec243aca02b4e94a016e9e193cd26d0bd321295dd1c8159926f7a48274eaee52d85121ca60eeb9d5b3dcfef7505f4ddc13fba74c6cd894c0f991
6
+ metadata.gz: 28085197c7f058c828730753e2bb40bca75646eb88d57515c6c7803b63a06e6a49109ffa914df6667efe6f62a2a33b04d5d3e94184a9eaeedd2c81862eb23147
7
+ data.tar.gz: 93197b5f6530199b8a6b9e38e93e141ec096da0a83328f15607500ad7057ca5badcb8dba143fd96a94c2a87782a46689f385ea01fa03bfc02d7241bbabe1407a
data/README.md CHANGED
@@ -39,10 +39,33 @@ $ ruby -r argtrace/autorun YOUR_PROGRAM_HERE.rb
39
39
  ```
40
40
  RBS file is saved as "sig.rbs" in current directory.
41
41
 
42
- ### Restriction
43
- Argtrace cannot work with C-extension,
44
- because TracePoint doesn't provide feature to access arguments of calls into C-extension.
42
+ ### 3. Rakefile
43
+ Modify Rakefile to load Argtrace.
44
+ ```ruby
45
+ RSpec::Core::RakeTask.new(:spec) do |t|
46
+ t.ruby_opts = %w[-r argtrace/autorun]
47
+ end
48
+ ```
49
+ then run as usual like `rake test`.
50
+ RBS file is saved as "sig.rbs" in current directory.
45
51
 
52
+ ### 4. Minitest
53
+ Modify global helper under test/ to load Argtrace. For example:
54
+ ```ruby
55
+ require "argtrace/autorun"
56
+ require "minitest/autorun"
57
+ ```
58
+ And add Argtrace to Gemfile
59
+ ```ruby
60
+ gem "argtrace"
61
+ ```
62
+ then run as usual like `rake test`.
63
+ RBS file is saved as "sig.rbs" in current directory.
64
+
65
+ ### Restriction
66
+ - Argtrace cannot work with C-extension,
67
+ because TracePoint doesn't provide feature to access arguments of calls into C-extension.
68
+ - Only work with single-thread programs.
46
69
 
47
70
  ## Contributing
48
71
 
@@ -8,8 +8,11 @@ module Argtrace
8
8
 
9
9
  tracer.set_filter do |tp|
10
10
  if [:call, :return].include?(tp.event)
11
- tracer.user_source?(tp.defined_class, tp.method_id)
11
+ ret = tracer.user_source?(tp.defined_class, tp.method_id)
12
+ # $stderr.puts [tp.event, tp.defined_class, tp.method_id].inspect if ret
13
+ ret
12
14
  else
15
+ # $stderr.puts [tp.event, tp.defined_class, tp.method_id].inspect if ret
13
16
  true
14
17
  end
15
18
  end
@@ -17,6 +20,7 @@ module Argtrace
17
20
  tracer.set_notify do |ev, callinfo|
18
21
  if ev == :return
19
22
  typelib.learn(callinfo.signature)
23
+ # $stderr.puts callinfo.signature.inspect
20
24
  end
21
25
  end
22
26
 
@@ -1,4 +1,3 @@
1
-
2
1
  module Argtrace
3
2
 
4
3
  # signature of method/block
@@ -10,6 +9,10 @@ module Argtrace
10
9
  @params = []
11
10
  end
12
11
 
12
+ def signature_for_block?
13
+ @method_id == nil
14
+ end
15
+
13
16
  def merge(all_params, ret)
14
17
  unless @params
15
18
  @params = []
@@ -19,8 +22,10 @@ module Argtrace
19
22
  if i == @params.size
20
23
  @params << all_params[i] # TODO: dup
21
24
  else
22
- if @params[i].mode == all_params[i].mode &&
23
- @params[i].name == all_params[i].name
25
+ same_mode = @params[i].mode == all_params[i].mode
26
+ same_name = @params[i].name == all_params[i].name
27
+ # allow name changing only for block call
28
+ if same_mode && (signature_for_block? || same_name)
24
29
  if all_params[i].mode == :block
25
30
  # TODO: buggy
26
31
  # merging of block parameter type is quite tricky...
@@ -207,6 +212,7 @@ module Argtrace
207
212
  return false
208
213
  end
209
214
  else
215
+ # both data should be Class, just compare them
210
216
  return other.data < @data
211
217
  end
212
218
  end
@@ -41,20 +41,21 @@ module Argtrace
41
41
  #{@stack[id].map{|x| x.signature.to_s}.join("\n ")}
42
42
  EOF
43
43
  end
44
- type = TypeUnion.new
45
- type.add(Type.new_with_value(tp.return_value))
46
- ent.signature.return_type = type
47
44
  end
48
45
  return ent
49
46
  end
50
47
 
51
48
  # find callinfo which use specific block
52
- def find_by_block_location(path, lineno)
49
+ def find_by_block_location(tp)
53
50
  id = Thread.current.object_id
54
51
  ret = []
55
52
  @stack[id].each do |info|
56
- if info.block_proc && info.block_proc.source_location == [path, lineno]
57
- ret << info
53
+ if info.block_proc && info.block_proc.source_location == [tp.path, tp.lineno]
54
+ # Warning: sometimes different block has same location.
55
+ # I cannot handle such case strictly, but use heuristics by comparing parameter names.
56
+ if tp.parameters.map{|x| x[1]} == info.block_proc.parameters.map{|x| x[1]}
57
+ ret << info
58
+ end
58
59
  end
59
60
  end
60
61
  return ret
@@ -107,10 +108,13 @@ module Argtrace
107
108
 
108
109
  # process block call/return event
109
110
  def trace_block_event(tp)
111
+ return if tp.event != :b_call
112
+
110
113
  # I cannot determine the called block instance directly, so use block's location.
111
- callinfos_with_block = @callstack.find_by_block_location(tp.path, tp.lineno)
114
+ callinfos_with_block = @callstack.find_by_block_location(tp)
112
115
  callinfos_with_block.each do |callinfo|
113
116
  block_param = callinfo.signature.get_block_param
117
+ # $stderr.puts [tp.event, tp.path, tp.lineno, callinfo.block_proc.parameters, tp.parameters].inspect
114
118
  block_param_types = get_param_types(callinfo.block_proc.parameters, tp)
115
119
  # TODO: return type (but maybe, there is no demand)
116
120
  block_param.type.merge(block_param_types, nil)
@@ -158,6 +162,10 @@ module Argtrace
158
162
 
159
163
  callinfo = @callstack.pop_callstack(tp)
160
164
  if callinfo
165
+ rettype = TypeUnion.new
166
+ rettype.add(Type.new_with_value(tp.return_value))
167
+ callinfo.signature.return_type = rettype
168
+
161
169
  if !skip_flag && @prune_event_count == 0
162
170
  @notify_block.call(tp.event, callinfo) if @notify_block
163
171
  end
@@ -462,6 +470,12 @@ module Argtrace
462
470
  @@running_trace.delete(trace)
463
471
  end
464
472
 
473
+ def self.force_stop_all
474
+ @@running_trace.each do |t|
475
+ t.disable
476
+ end
477
+ end
478
+
465
479
  end
466
480
 
467
481
  end
@@ -15,9 +15,48 @@ module Argtrace
15
15
  hmethod[method_id] = [nil, nil]
16
16
  }
17
17
  }
18
+ @api_class_cache = {}
19
+ @api_symbol_cache = {}
20
+ end
21
+
22
+ CLASS_NAME_PATTERN = '[A-Z][A-Za-z0-9_]*'
23
+ def api_class?(klass)
24
+ unless @api_class_cache.key?(klass)
25
+ if /\A(#{CLASS_NAME_PATTERN})(::#{CLASS_NAME_PATTERN})*\z/ =~ klass.to_s
26
+ @api_class_cache[klass] = true
27
+ else
28
+ # this must not be interface class
29
+ @api_class_cache[klass] = false
30
+ end
31
+ end
32
+ return @api_class_cache[klass]
33
+ end
34
+
35
+ NORMAL_METHOD_NAME_PATTERN = '[A-Za-z0-9_]+[=?!]?'
36
+ OPERATOR_METHOD_NAME_PATTERN = '[!%&=\-~^|\[+*\]<>\/]+'
37
+ def api_method?(method_id)
38
+ unless @api_symbol_cache.key?(method_id)
39
+ if /\A((#{NORMAL_METHOD_NAME_PATTERN})|(#{OPERATOR_METHOD_NAME_PATTERN}))\z/ =~ method_id.to_s
40
+ @api_symbol_cache[method_id] = true
41
+ else
42
+ # this must not be interface method
43
+ @api_symbol_cache[method_id] = false
44
+ end
45
+ end
46
+ return @api_symbol_cache[method_id]
18
47
  end
19
48
 
20
49
  def ready_signature(signature)
50
+ return nil unless api_class?(signature.defined_class)
51
+ return nil unless api_method?(signature.method_id)
52
+
53
+ # DEBUG:
54
+ # if not @lib.key?(signature.defined_class)
55
+ # p [signature.defined_class, signature.method_id, signature.defined_class.to_s, signature.method_id.to_s]
56
+ # elsif not @lib[signature.defined_class].key?(signature.method_id)
57
+ # p [signature.defined_class, signature.method_id, signature.defined_class.to_s, signature.method_id.to_s]
58
+ # end
59
+
21
60
  pair = @lib[signature.defined_class][signature.method_id]
22
61
  index = signature.is_singleton_method ? 1 : 0
23
62
  unless pair[index]
@@ -31,8 +70,49 @@ module Argtrace
31
70
  return pair[index]
32
71
  end
33
72
 
73
+ # remove non-api class from type signature
74
+ def discard_noise_from_signature(signature)
75
+ signature.params.each do |param|
76
+ if param.mode == :block
77
+ discard_noise_from_signature(param.type)
78
+ else
79
+ discard_noise_from_typeunion(param.type)
80
+ end
81
+ end
82
+ discard_noise_from_typeunion(signature.return_type)
83
+ end
84
+
85
+ def discard_noise_from_typeunion(typeunion)
86
+ return unless typeunion
87
+ typeunion.union.delete_if{|type| noise_type?(type)}
88
+ end
89
+
90
+ def noise_type?(type)
91
+ if type.data.is_a?(Symbol)
92
+ return false
93
+ end
94
+ if type.data.is_a?(Array)
95
+ if type.subdata == nil
96
+ return false
97
+ end
98
+ return !api_class?(type.subdata)
99
+ end
100
+ if type.data.is_a?(Class)
101
+ return !api_class?(type.data)
102
+ end
103
+ raise "Unexpected type data : #{type}"
104
+ end
105
+
106
+ # add signature into type library
34
107
  def learn(signature)
35
- ready_signature(signature).merge(signature.params, signature.return_type)
108
+ sig = ready_signature(signature)
109
+ if sig
110
+ discard_noise_from_signature(signature)
111
+ sig.merge(signature.params, signature.return_type)
112
+ else
113
+ # skip
114
+ # $stderr.puts [:skip, signature].inspect
115
+ end
36
116
  end
37
117
 
38
118
  def to_rbs
@@ -41,6 +121,9 @@ module Argtrace
41
121
  # TODO: attr_reader/attr_writer/attr_accessor
42
122
  mod_root = OutputModule.new
43
123
 
124
+ # DEBUG:
125
+ # $stderr.puts @lib.inspect
126
+
44
127
  @lib.keys.sort_by{|x| x.to_s}.each do |klass|
45
128
  klass_methods = @lib[klass]
46
129
 
@@ -50,7 +133,15 @@ module Argtrace
50
133
  sig = klass_methods[method_id][instance_or_singleton]
51
134
  next unless sig
52
135
 
53
- mod_root.add_signature(sig)
136
+ begin
137
+ mod_root.add_signature(sig)
138
+ rescue => e
139
+ $stderr.puts "----- argtrace bug -----"
140
+ $stderr.puts "#{klass}::#{method_id} (#{sig})"
141
+ $stderr.puts e.full_message
142
+ $stderr.puts "------------------------"
143
+ raise
144
+ end
54
145
  end
55
146
  end
56
147
  end
@@ -73,33 +164,13 @@ module Argtrace
73
164
  @actual_module = Kernel
74
165
 
75
166
  constname = class_const_name(signature.defined_class)
76
- unless constname
77
- # cannot handle this
78
- return
79
- end
80
-
81
167
  add_signature_inner(constname, signature)
82
168
  end
83
169
 
84
170
  # split class name into consts (e.g. Argtrace::TypeLib to ["Argtrace", "TypeLib"])
171
+ # bad name class is already sanitized, just split.
85
172
  def class_const_name(klass)
86
- if /^[A-Za-z0-9_:]+$/ =~ klass.to_s
87
- # this should be normal name
88
- consts = klass.to_s.split("::")
89
-
90
- # assertion
91
- resolved_class = consts.inject(Kernel){|mod, const| mod.const_get(const)}
92
- if klass != resolved_class
93
- $stderr.puts "----- argtrace bug -----"
94
- $stderr.puts "#{klass} => #{consts} => #{resolved_class}"
95
- $stderr.puts "------------------------"
96
- raise "Failed to resolve class by constant"
97
- end
98
-
99
- return consts
100
- else
101
- return nil
102
- end
173
+ klass.to_s.split("::")
103
174
  end
104
175
 
105
176
  def add_signature_inner(name_consts, signature)
@@ -191,6 +262,11 @@ module Argtrace
191
262
  # TODO: I can't distinguish nil and untyped.
192
263
  return "untyped"
193
264
  end
265
+ if typeunion.union.count{|x| x.data.is_a?(Symbol)} >= 16
266
+ # too much symbols, this should not be enum.
267
+ typeunion.union.delete_if{|x| x.data.is_a?(Symbol)}
268
+ typeunion.add(Type.new_with_type(Symbol))
269
+ end
194
270
  if typeunion.union.size == 2 and typeunion.union.any?{|x| NilClass == x.data}
195
271
  # type is nil and sometype, so represent it as "sometype?"
196
272
  sometype = typeunion.union.find{|x| NilClass != x.data}
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Argtrace
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: argtrace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Riki Ishikawa
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-14 00:00:00.000000000 Z
11
+ date: 2021-03-17 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: library to trace arguments of method calls or block calls with TracePoint
14
14
  and do callback.