argtrace 0.1.1 → 0.2.0

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 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.