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 +4 -4
- data/README.md +26 -3
- data/lib/argtrace/default.rb +5 -1
- data/lib/argtrace/signature.rb +9 -3
- data/lib/argtrace/tracer.rb +21 -7
- data/lib/argtrace/typelib.rb +100 -24
- data/lib/argtrace/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 954782bc5b1e4572bb27a4bb008d81dee8a8406b0a55b630d479913cf19dc977
|
4
|
+
data.tar.gz: 12e7d2fa9af906a4ff9d7f431a2c54a6e5140beb3b397de9dc1bac3f9c27fa87
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
###
|
43
|
-
|
44
|
-
|
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
|
|
data/lib/argtrace/default.rb
CHANGED
@@ -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
|
|
data/lib/argtrace/signature.rb
CHANGED
@@ -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
|
-
|
23
|
-
|
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
|
data/lib/argtrace/tracer.rb
CHANGED
@@ -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(
|
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
|
-
|
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
|
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
|
data/lib/argtrace/typelib.rb
CHANGED
@@ -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)
|
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
|
-
|
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
|
-
|
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}
|
data/lib/argtrace/version.rb
CHANGED
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.
|
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-
|
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.
|