typeprof 0.15.0 → 0.15.1
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/Gemfile.lock +2 -2
- data/lib/typeprof/analyzer.rb +33 -12
- data/lib/typeprof/builtin.rb +8 -0
- data/lib/typeprof/iseq.rb +303 -201
- data/lib/typeprof/method.rb +2 -2
- data/lib/typeprof/type.rb +1 -1
- data/lib/typeprof/version.rb +1 -1
- data/smoke/break2.rb +1 -1
- data/smoke/identifier_keywords.rb +17 -0
- data/smoke/next2.rb +1 -1
- data/smoke/or_raise.rb +18 -0
- data/smoke/pattern-match1.rb +1 -6
- data/testbed/ao.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52c9e6c0c338fb19da0264929bce36b903cdedf7be3b6a031deef525e28be8d4
|
4
|
+
data.tar.gz: 8c17fe8549a7c44c8a11a1db9a40384b05f4025e5ec23140fee09eaf349d855c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 74d8eddbd6010287d33c9891a59112f7f9edf1aa2f3ea24d07142e1d81e1c03508b1362a3630f092138352616a8258e973784d926293882894ba933123851f88
|
7
|
+
data.tar.gz: 77bc3c7f268b11663f561829c4b1a2c4917e137c0e56c3b4e734f29862c011e7095b2d1939a352534f78003159d90f07f8de81ce1304819f4f79e5d5ac6f7242
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
typeprof (0.15.
|
4
|
+
typeprof (0.15.1)
|
5
5
|
rbs (>= 1.3.1)
|
6
6
|
|
7
7
|
GEM
|
@@ -11,7 +11,7 @@ GEM
|
|
11
11
|
docile (1.4.0)
|
12
12
|
power_assert (2.0.0)
|
13
13
|
rake (13.0.1)
|
14
|
-
rbs (1.3.
|
14
|
+
rbs (1.3.3)
|
15
15
|
simplecov (0.21.2)
|
16
16
|
docile (~> 1.1)
|
17
17
|
simplecov-html (~> 0.11)
|
data/lib/typeprof/analyzer.rb
CHANGED
@@ -286,6 +286,7 @@ module TypeProf
|
|
286
286
|
@anonymous_struct_gen_id = 0
|
287
287
|
|
288
288
|
@types_being_shown = []
|
289
|
+
@namespace = nil
|
289
290
|
end
|
290
291
|
|
291
292
|
def add_entrypoint(iseq)
|
@@ -1118,15 +1119,16 @@ module TypeProf
|
|
1118
1119
|
env = @ep2env[ep]
|
1119
1120
|
raise "nil env" unless env
|
1120
1121
|
|
1121
|
-
insn
|
1122
|
+
insn = ep.ctx.iseq.insns[ep.pc]
|
1123
|
+
operands = insn.operands
|
1122
1124
|
|
1123
1125
|
if Config.verbose >= 2
|
1124
1126
|
# XXX: more dedicated output
|
1125
1127
|
puts "DEBUG: stack=%p" % [env.stack]
|
1126
|
-
puts "DEBUG: %s (%s) PC=%d insn=%s sp=%d" % [ep.source_location, ep.ctx.iseq.name, ep.pc, insn, env.stack.size]
|
1128
|
+
puts "DEBUG: %s (%s) PC=%d insn=%s sp=%d" % [ep.source_location, ep.ctx.iseq.name, ep.pc, insn.insn, env.stack.size]
|
1127
1129
|
end
|
1128
1130
|
|
1129
|
-
case insn
|
1131
|
+
case insn.insn
|
1130
1132
|
when :_iseq_body_start
|
1131
1133
|
# XXX: reconstruct and record the method signature
|
1132
1134
|
iseq = ep.ctx.iseq
|
@@ -1641,7 +1643,7 @@ module TypeProf
|
|
1641
1643
|
return
|
1642
1644
|
end
|
1643
1645
|
|
1644
|
-
when :getlocal
|
1646
|
+
when :getlocal
|
1645
1647
|
var_idx, scope_idx, _escaped = operands
|
1646
1648
|
if scope_idx == 0
|
1647
1649
|
ty = env.get_local(-var_idx+2)
|
@@ -1682,18 +1684,12 @@ module TypeProf
|
|
1682
1684
|
getlocal_operands, _dup_operands, branch_operands = operands
|
1683
1685
|
var_idx, _scope_idx, _escaped = getlocal_operands
|
1684
1686
|
ret_ty = env.get_local(-var_idx+2)
|
1685
|
-
unless ret_ty
|
1686
|
-
p env.locals
|
1687
|
-
raise
|
1688
|
-
end
|
1689
1687
|
|
1690
1688
|
branchtype, target, = branch_operands
|
1691
1689
|
# branchtype: :if or :unless or :nil
|
1692
1690
|
ep_then = ep.next
|
1693
1691
|
ep_else = ep.jump(target)
|
1694
1692
|
|
1695
|
-
var_idx, _scope_idx, _escaped = getlocal_operands
|
1696
|
-
|
1697
1693
|
ret_ty.each_child do |ret_ty|
|
1698
1694
|
flow_env = env.local_update(-var_idx+2, ret_ty).push(ret_ty)
|
1699
1695
|
case ret_ty
|
@@ -1707,6 +1703,31 @@ module TypeProf
|
|
1707
1703
|
end
|
1708
1704
|
end
|
1709
1705
|
return
|
1706
|
+
when :dup_setlocal_branch
|
1707
|
+
_dup_operands, setlocal_operands, branch_operands = operands
|
1708
|
+
|
1709
|
+
env, (ret_ty,) = env.pop(1)
|
1710
|
+
|
1711
|
+
var_idx, _scope_idx, _escaped = setlocal_operands
|
1712
|
+
|
1713
|
+
branchtype, target, = branch_operands
|
1714
|
+
# branchtype: :if or :unless or :nil
|
1715
|
+
ep_then = ep.next
|
1716
|
+
ep_else = ep.jump(target)
|
1717
|
+
|
1718
|
+
ret_ty.each_child do |ret_ty|
|
1719
|
+
flow_env = env.local_update(-var_idx+2, ret_ty)
|
1720
|
+
case ret_ty
|
1721
|
+
when Type.any
|
1722
|
+
merge_env(ep_then, flow_env)
|
1723
|
+
merge_env(ep_else, flow_env)
|
1724
|
+
when Type::Instance.new(Type::Builtin[:false]), Type.nil
|
1725
|
+
merge_env(branchtype == :if ? ep_then : ep_else, flow_env)
|
1726
|
+
else
|
1727
|
+
merge_env(branchtype == :if ? ep_else : ep_then, flow_env)
|
1728
|
+
end
|
1729
|
+
end
|
1730
|
+
return
|
1710
1731
|
when :getlocal_checkmatch_branch
|
1711
1732
|
getlocal_operands, branch_operands = operands
|
1712
1733
|
var_idx, _scope_idx, _escaped = getlocal_operands
|
@@ -1970,7 +1991,7 @@ module TypeProf
|
|
1970
1991
|
end
|
1971
1992
|
env = env.push(ty)
|
1972
1993
|
else
|
1973
|
-
raise "Unknown insn: #{ insn }"
|
1994
|
+
raise "Unknown insn: #{ insn.insn }"
|
1974
1995
|
end
|
1975
1996
|
|
1976
1997
|
add_edge(ep, ep)
|
@@ -2204,7 +2225,7 @@ module TypeProf
|
|
2204
2225
|
if blk.is_a?(Type::Proc)
|
2205
2226
|
blk.block_body.do_call(aargs, ep, env, self, replace_recv_ty: replace_recv_ty, replace_cref: replace_cref, &ctn)
|
2206
2227
|
else
|
2207
|
-
warn(ep, "non-proc is passed as a block")
|
2228
|
+
warn(ep, "non-proc is passed as a block") if blk != Type.any
|
2208
2229
|
ctn[Type.any, ep, env]
|
2209
2230
|
end
|
2210
2231
|
end
|
data/lib/typeprof/builtin.rb
CHANGED
@@ -813,6 +813,7 @@ module TypeProf
|
|
813
813
|
Type::Builtin[:exc] = scratch.get_constant(klass_obj, :Exception)
|
814
814
|
Type::Builtin[:encoding] = scratch.get_constant(klass_obj, :Encoding)
|
815
815
|
Type::Builtin[:enumerator] = scratch.get_constant(klass_obj, :Enumerator)
|
816
|
+
Type::Builtin[:kernel] = scratch.get_constant(klass_obj, :Kernel)
|
816
817
|
|
817
818
|
klass_vmcore = Type::Builtin[:vmcore]
|
818
819
|
klass_ary = Type::Builtin[:ary]
|
@@ -825,6 +826,7 @@ module TypeProf
|
|
825
826
|
scratch.set_custom_method(klass_vmcore, :"core#undef_method", Builtin.method(:vmcore_undef_method))
|
826
827
|
scratch.set_custom_method(klass_vmcore, :"core#hash_merge_kwd", Builtin.method(:vmcore_hash_merge_kwd))
|
827
828
|
scratch.set_custom_method(klass_vmcore, :"core#raise", Builtin.method(:vmcore_raise))
|
829
|
+
|
828
830
|
scratch.set_custom_method(klass_vmcore, :lambda, Builtin.method(:lambda))
|
829
831
|
scratch.set_singleton_custom_method(klass_obj, :"new", Builtin.method(:object_s_new))
|
830
832
|
scratch.set_custom_method(klass_obj, :p, Builtin.method(:kernel_p), false)
|
@@ -880,6 +882,12 @@ module TypeProf
|
|
880
882
|
str_ty = Type::Instance.new(Type::Builtin[:str])
|
881
883
|
env_ty = Type.gen_hash {|h| h[str_ty] = Type.optional(str_ty) }
|
882
884
|
scratch.add_constant(klass_obj, :ENV, env_ty, false)
|
885
|
+
|
886
|
+
scratch.search_method(Type::Builtin[:kernel], false, :sprintf) do |mdefs,|
|
887
|
+
mdefs.each do |mdef|
|
888
|
+
scratch.add_method(klass_vmcore, :"core#sprintf", false, mdef)
|
889
|
+
end
|
890
|
+
end
|
883
891
|
end
|
884
892
|
end
|
885
893
|
end
|
data/lib/typeprof/iseq.rb
CHANGED
@@ -3,75 +3,65 @@ module TypeProf
|
|
3
3
|
# https://github.com/ruby/ruby/pull/4468
|
4
4
|
CASE_WHEN_CHECKMATCH = RubyVM::InstructionSequence.compile("case 1; when Integer; end").to_a.last.any? {|insn,| insn == :checkmatch }
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
6
|
+
class << self
|
7
|
+
def compile(file)
|
8
|
+
compile_core(nil, file)
|
9
|
+
end
|
10
|
+
|
11
|
+
def compile_str(str, path = nil)
|
12
|
+
compile_core(str, path)
|
13
|
+
end
|
14
|
+
|
15
|
+
private def compile_core(str, path)
|
16
|
+
opt = RubyVM::InstructionSequence.compile_option
|
17
|
+
opt[:inline_const_cache] = false
|
18
|
+
opt[:peephole_optimization] = false
|
19
|
+
opt[:specialized_instruction] = false
|
20
|
+
opt[:operands_unification] = false
|
21
|
+
opt[:coverage_enabled] = false
|
22
|
+
|
23
|
+
if str
|
24
|
+
iseq = RubyVM::InstructionSequence.compile(str, path, **opt)
|
25
|
+
else
|
26
|
+
iseq = RubyVM::InstructionSequence.compile_file(path, **opt)
|
27
|
+
end
|
28
|
+
|
29
|
+
return new(iseq.to_a)
|
30
|
+
end
|
16
31
|
end
|
17
32
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
opt[:operands_unification] = false
|
24
|
-
opt[:coverage_enabled] = false
|
25
|
-
new(RubyVM::InstructionSequence.compile(str, path, **opt).to_a)
|
33
|
+
Insn = Struct.new(:insn, :operands, :lineno)
|
34
|
+
class Insn
|
35
|
+
def check?(insn_cmp, operands_cmp = nil)
|
36
|
+
return insn == insn_cmp && (!operands_cmp || operands == operands_cmp)
|
37
|
+
end
|
26
38
|
end
|
27
39
|
|
28
|
-
|
40
|
+
ISEQ_FRESH_ID = [0]
|
29
41
|
|
30
42
|
def initialize(iseq)
|
31
|
-
@id =
|
32
|
-
FRESH_ID[0] += 1
|
43
|
+
@id = (ISEQ_FRESH_ID[0] += 1)
|
33
44
|
|
34
45
|
_magic, _major_version, _minor_version, _format_type, _misc,
|
35
46
|
@name, @path, @absolute_path, @start_lineno, @type,
|
36
47
|
@locals, @fargs_format, catch_table, insns = *iseq
|
37
48
|
|
38
|
-
|
39
|
-
when :method, :block
|
40
|
-
if @fargs_format[:opt]
|
41
|
-
label = @fargs_format[:opt].last
|
42
|
-
i = insns.index(label) + 1
|
43
|
-
else
|
44
|
-
i = insns.find_index {|insn| insn.is_a?(Array) }
|
45
|
-
end
|
46
|
-
# skip keyword initialization
|
47
|
-
while insns[i][0] == :checkkeyword
|
48
|
-
raise if insns[i + 1][0] != :branchif
|
49
|
-
label = insns[i + 1][1]
|
50
|
-
i = insns.index(label) + 1
|
51
|
-
end
|
52
|
-
insns[i, 0] = [[:_iseq_body_start]]
|
53
|
-
end
|
49
|
+
convert_insns(insns)
|
54
50
|
|
55
|
-
|
56
|
-
# because they requires to be virtually called.
|
57
|
-
# So, this preprocess adds "nop" to make a new insn for their return addresses
|
58
|
-
special_labels = {}
|
59
|
-
catch_table.map do |type, iseq, first, last, cont, stack_depth|
|
60
|
-
special_labels[cont] = true if type == :rescue || type == :ensure
|
61
|
-
end
|
51
|
+
add_body_start_marker(insns)
|
62
52
|
|
63
|
-
|
64
|
-
@linenos = []
|
53
|
+
add_exception_cont_marker(insns, catch_table)
|
65
54
|
|
66
|
-
labels =
|
55
|
+
labels = create_label_table(insns)
|
67
56
|
|
68
|
-
|
69
|
-
|
57
|
+
@insns = setup_insns(insns, labels)
|
58
|
+
|
59
|
+
@fargs_format[:opt] = @fargs_format[:opt].map {|l| labels[l] } if @fargs_format[:opt]
|
70
60
|
|
71
61
|
@catch_table = []
|
72
62
|
catch_table.map do |type, iseq, first, last, cont, stack_depth|
|
73
63
|
iseq = iseq ? ISeq.new(iseq) : nil
|
74
|
-
target = labels[
|
64
|
+
target = labels[cont]
|
75
65
|
entry = [type, iseq, target, stack_depth]
|
76
66
|
labels[first].upto(labels[last]) do |i|
|
77
67
|
@catch_table[i] ||= []
|
@@ -79,43 +69,143 @@ module TypeProf
|
|
79
69
|
end
|
80
70
|
end
|
81
71
|
|
82
|
-
|
72
|
+
rename_insn_types
|
73
|
+
|
74
|
+
unify_instructions
|
75
|
+
end
|
76
|
+
|
77
|
+
def source_location(pc)
|
78
|
+
"#{ @path }:#{ @insns[pc].lineno }"
|
79
|
+
end
|
83
80
|
|
84
|
-
|
81
|
+
attr_reader :name, :path, :absolute_path, :start_lineno, :type, :locals, :fargs_format, :catch_table, :insns
|
82
|
+
attr_reader :id
|
83
|
+
|
84
|
+
def pretty_print(q)
|
85
|
+
q.text "ISeq["
|
86
|
+
q.group do
|
87
|
+
q.nest(1) do
|
88
|
+
q.breakable ""
|
89
|
+
q.text "@type= #{ @type }"
|
90
|
+
q.breakable ", "
|
91
|
+
q.text "@name= #{ @name }"
|
92
|
+
q.breakable ", "
|
93
|
+
q.text "@path= #{ @path }"
|
94
|
+
q.breakable ", "
|
95
|
+
q.text "@absolute_path= #{ @absolute_path }"
|
96
|
+
q.breakable ", "
|
97
|
+
q.text "@start_lineno= #{ @start_lineno }"
|
98
|
+
q.breakable ", "
|
99
|
+
q.text "@fargs_format= #{ @fargs_format.inspect }"
|
100
|
+
q.breakable ", "
|
101
|
+
q.text "@insns="
|
102
|
+
q.group(2) do
|
103
|
+
@insns.each_with_index do |(insn, *operands), i|
|
104
|
+
q.breakable
|
105
|
+
q.group(2, "#{ i }: #{ insn.to_s }", "") do
|
106
|
+
q.pp operands
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
q.breakable
|
112
|
+
end
|
113
|
+
q.text "]"
|
85
114
|
end
|
86
115
|
|
87
116
|
def <=>(other)
|
88
117
|
@id <=> other.id
|
89
118
|
end
|
90
119
|
|
91
|
-
|
120
|
+
# Remove lineno entry and convert instructions to Insn instances
|
121
|
+
def convert_insns(insns)
|
122
|
+
ninsns = []
|
123
|
+
lineno = 0
|
124
|
+
insns.each do |e|
|
125
|
+
case e
|
126
|
+
when Integer # lineno
|
127
|
+
lineno = e
|
128
|
+
when Symbol # label or trace
|
129
|
+
ninsns << e
|
130
|
+
when Array
|
131
|
+
insn, *operands = e
|
132
|
+
ninsns << Insn.new(insn, operands, lineno)
|
133
|
+
else
|
134
|
+
raise "unknown iseq entry: #{ e }"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
insns.replace(ninsns)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Insert a dummy instruction "_iseq_body_start"
|
141
|
+
def add_body_start_marker(insns)
|
142
|
+
case @type
|
143
|
+
when :method, :block
|
144
|
+
# skip initialization code of optional arguments
|
145
|
+
if @fargs_format[:opt]
|
146
|
+
label = @fargs_format[:opt].last
|
147
|
+
i = insns.index(label) + 1
|
148
|
+
else
|
149
|
+
i = insns.find_index {|insn| insn.is_a?(Insn) }
|
150
|
+
end
|
151
|
+
|
152
|
+
# skip initialization code of keyword arguments
|
153
|
+
while insns[i][0] == :checkkeyword
|
154
|
+
raise if insns[i + 1].insn != :branchif
|
155
|
+
label = insns[i + 1].operands[0]
|
156
|
+
i = insns.index(label) + 1
|
157
|
+
end
|
158
|
+
|
159
|
+
insns.insert(i, Insn.new(:_iseq_body_start, [], @start_lineno))
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Insert "nop" instruction to continuation point of exception handlers
|
164
|
+
def add_exception_cont_marker(insns, catch_table)
|
165
|
+
# rescue/ensure clauses need to have a dedicated return addresses
|
166
|
+
# because they requires to be virtually called.
|
167
|
+
# So, this preprocess adds "nop" to make a new insn for their return addresses
|
168
|
+
exception_cont_labels = {}
|
169
|
+
catch_table.map! do |type, iseq, first, last, cont, stack_depth|
|
170
|
+
if type == :rescue || type == :ensure
|
171
|
+
exception_cont_labels[cont] = true
|
172
|
+
cont = :"#{ cont }_exception_cont"
|
173
|
+
end
|
174
|
+
[type, iseq, first, last, cont, stack_depth]
|
175
|
+
end
|
176
|
+
|
92
177
|
i = 0
|
178
|
+
while i < insns.size
|
179
|
+
e = insns[i]
|
180
|
+
if exception_cont_labels[e]
|
181
|
+
insns.insert(i, :"#{ e }_exception_cont", Insn.new(:nop, []))
|
182
|
+
i += 2
|
183
|
+
end
|
184
|
+
i += 1
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def create_label_table(insns)
|
189
|
+
pc = 0
|
93
190
|
labels = {}
|
94
|
-
ninsns = []
|
95
191
|
insns.each do |e|
|
96
|
-
if e.is_a?(Symbol)
|
97
|
-
|
98
|
-
labels[:"#{ e }_special"] = i
|
99
|
-
ninsns << [:nop]
|
100
|
-
i += 1
|
101
|
-
end
|
102
|
-
labels[e] = i
|
192
|
+
if e.is_a?(Symbol)
|
193
|
+
labels[e] = pc
|
103
194
|
else
|
104
|
-
|
105
|
-
ninsns << e
|
195
|
+
pc += 1
|
106
196
|
end
|
107
197
|
end
|
198
|
+
labels
|
199
|
+
end
|
108
200
|
|
109
|
-
|
110
|
-
ninsns
|
201
|
+
def setup_insns(insns, labels)
|
202
|
+
ninsns = []
|
203
|
+
insns.each do |e|
|
111
204
|
case e
|
112
|
-
when Integer # lineno
|
113
|
-
lineno = e
|
114
205
|
when Symbol # label or trace
|
115
206
|
nil
|
116
|
-
when
|
117
|
-
insn
|
118
|
-
operands = (INSN_TABLE[insn] || []).zip(operands).map do |type, operand|
|
207
|
+
when Insn
|
208
|
+
operands = (INSN_TABLE[e.insn] || []).zip(e.operands).map do |type, operand|
|
119
209
|
case type
|
120
210
|
when "ISEQ"
|
121
211
|
operand && ISeq.new(operand)
|
@@ -131,81 +221,64 @@ module TypeProf
|
|
131
221
|
end
|
132
222
|
end
|
133
223
|
|
134
|
-
|
135
|
-
@linenos << lineno
|
224
|
+
ninsns << Insn.new(e.insn, operands, e.lineno)
|
136
225
|
else
|
137
226
|
raise "unknown iseq entry: #{ e }"
|
138
227
|
end
|
139
228
|
end
|
140
|
-
|
141
|
-
@fargs_format[:opt] = @fargs_format[:opt].map {|l| labels[l] } if @fargs_format[:opt]
|
142
|
-
|
143
|
-
labels
|
229
|
+
ninsns
|
144
230
|
end
|
145
231
|
|
146
|
-
def
|
147
|
-
@insns.
|
148
|
-
insn
|
149
|
-
case insn
|
232
|
+
def rename_insn_types
|
233
|
+
@insns.each do |insn|
|
234
|
+
case insn.insn
|
150
235
|
when :branchif
|
151
|
-
|
236
|
+
insn.insn, insn.operands = :branch, [:if] + insn.operands
|
152
237
|
when :branchunless
|
153
|
-
|
238
|
+
insn.insn, insn.operands = :branch, [:unless] + insn.operands
|
154
239
|
when :branchnil
|
155
|
-
|
240
|
+
insn.insn, insn.operands = :branch, [:nil] + insn.operands
|
241
|
+
when :getblockparam, :getblockparamproxy
|
242
|
+
insn.insn = :getlocal
|
156
243
|
end
|
157
244
|
end
|
158
245
|
end
|
159
246
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
q.group(2) do
|
186
|
-
@insns.each_with_index do |(insn, *operands), i|
|
187
|
-
q.breakable
|
188
|
-
q.group(2, "#{ i }: #{ insn.to_s }", "") do
|
189
|
-
q.pp operands
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
194
|
-
q.breakable
|
195
|
-
end
|
196
|
-
q.text "]"
|
197
|
-
end
|
247
|
+
# Unify some instructions for flow-sensitive analysis
|
248
|
+
def unify_instructions
|
249
|
+
# This method rewrites instructions to enable flow-sensitive analysis.
|
250
|
+
#
|
251
|
+
# Consider `if x; ...; else; ... end`.
|
252
|
+
# When the variable `x` is of type "Integer | nil",
|
253
|
+
# we want to make sure that `x` is "Integer" in then clause.
|
254
|
+
# So, we need to split the environment to two ones:
|
255
|
+
# one is that `x` is of type "Integer", and the other is that
|
256
|
+
# `x` is type "nil".
|
257
|
+
#
|
258
|
+
# However, `if x` is compiled to "getlocal; branch".
|
259
|
+
# TypeProf evaluates them as follows:
|
260
|
+
#
|
261
|
+
# * "getlocal" pushes the value of `x` to the stack, amd
|
262
|
+
# * "branch" checks the value on the top of the stack
|
263
|
+
#
|
264
|
+
# TypeProf does not keep where the value comes from, so
|
265
|
+
# it is difficult to split the environment when evaluating "branch".
|
266
|
+
#
|
267
|
+
# This method rewrites "getlocal; branch" to "nop; getlocal_branch".
|
268
|
+
# The two instructions are unified to "getlocal_branch" instruction,
|
269
|
+
# so TypeProf can split the environment.
|
270
|
+
#
|
271
|
+
# This is a very fragile appoach because it highly depends on the compiler of Ruby.
|
198
272
|
|
199
|
-
def analyze_stack
|
200
273
|
# gather branch targets
|
201
274
|
# TODO: catch_table should be also considered
|
202
275
|
branch_targets = {}
|
203
|
-
@insns.each do |insn
|
204
|
-
case insn
|
276
|
+
@insns.each do |insn|
|
277
|
+
case insn.insn
|
205
278
|
when :branch
|
206
|
-
branch_targets[operands[1]] = true
|
279
|
+
branch_targets[insn.operands[1]] = true
|
207
280
|
when :jump
|
208
|
-
branch_targets[operands[0]] = true
|
281
|
+
branch_targets[insn.operands[0]] = true
|
209
282
|
end
|
210
283
|
end
|
211
284
|
|
@@ -215,25 +288,27 @@ module TypeProf
|
|
215
288
|
case_branch_list = []
|
216
289
|
if CASE_WHEN_CHECKMATCH
|
217
290
|
(@insns.size - 1).times do |i|
|
218
|
-
|
219
|
-
next unless
|
291
|
+
insn = @insns[i]
|
292
|
+
next unless insn.insn == :getlocal && insn.operands[1] == 0
|
293
|
+
getlocal_operands = insn.operands
|
220
294
|
nops = [i]
|
221
295
|
new_insns = []
|
222
296
|
j = i + 1
|
223
297
|
while true
|
224
|
-
case @insns[j]
|
225
|
-
when
|
226
|
-
break unless @insns[j + 1]
|
227
|
-
break unless @insns[j + 2]
|
228
|
-
break unless @insns[j + 3]
|
229
|
-
break unless @insns[j + 4]
|
230
|
-
break unless @insns[j + 5]
|
231
|
-
target_pc = @insns[j + 5][1]
|
232
|
-
break unless @insns[target_pc]
|
298
|
+
case @insns[j].insn
|
299
|
+
when :dup
|
300
|
+
break unless @insns[j + 1].check?(:putnil, [])
|
301
|
+
break unless @insns[j + 2].check?(:putobject, [true])
|
302
|
+
break unless @insns[j + 3].check?(:getconstant) # TODO: support A::B::C
|
303
|
+
break unless @insns[j + 4].check?(:checkmatch, [2])
|
304
|
+
break unless @insns[j + 5].check?(:branch)
|
305
|
+
target_pc = @insns[j + 5].operands[1]
|
306
|
+
break unless @insns[target_pc].check?(:pop, [])
|
233
307
|
nops << j << (j + 4) << target_pc
|
234
|
-
|
308
|
+
branch_operands = @insns[j + 5][1]
|
309
|
+
new_insns << [j + 5, Insn.new(:getlocal_checkmatch_branch, [getlocal_operands, branch_operands])]
|
235
310
|
j += 6
|
236
|
-
when
|
311
|
+
when :pop
|
237
312
|
nops << j
|
238
313
|
case_branch_list << [nops, new_insns]
|
239
314
|
break
|
@@ -244,25 +319,28 @@ module TypeProf
|
|
244
319
|
end
|
245
320
|
else
|
246
321
|
(@insns.size - 1).times do |i|
|
247
|
-
|
248
|
-
next unless
|
322
|
+
insn = @insns[i]
|
323
|
+
next unless insn.insn == :getlocal && insn.operands[1] == 0
|
324
|
+
getlocal_operands = insn.operands
|
249
325
|
nops = []
|
250
326
|
new_insns = []
|
251
327
|
j = i + 1
|
252
328
|
while true
|
253
|
-
|
254
|
-
|
255
|
-
break unless @insns[j + 1]
|
256
|
-
break unless @insns[j + 2]
|
257
|
-
break unless @insns[j + 3]
|
258
|
-
break unless @insns[j + 4]
|
259
|
-
break unless @insns[j + 5]
|
260
|
-
target_pc = @insns[j + 5][1]
|
261
|
-
break unless @insns[target_pc]
|
329
|
+
insn = @insns[j]
|
330
|
+
if insn.check?(:putnil, [])
|
331
|
+
break unless @insns[j + 1].check?(:putobject, [true])
|
332
|
+
break unless @insns[j + 2].check?(:getconstant) # TODO: support A::B::C
|
333
|
+
break unless @insns[j + 3].check?(:topn, [1])
|
334
|
+
break unless @insns[j + 4].check?(:send, [{:mid=>:===, :flag=>20, :orig_argc=>1}, nil])
|
335
|
+
break unless @insns[j + 5].check?(:branch)
|
336
|
+
target_pc = @insns[j + 5].operands[1]
|
337
|
+
break unless @insns[target_pc].check?(:pop, [])
|
262
338
|
nops << (j + 4) #<< target_pc
|
263
|
-
|
339
|
+
send_operands = @insns[j + 4][1]
|
340
|
+
branch_operands = @insns[j + 5][1]
|
341
|
+
new_insns << [j + 5, Insn.new(:arg_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands])]
|
264
342
|
j += 6
|
265
|
-
|
343
|
+
elsif insn.check?(:pop, [])
|
266
344
|
#nops << j
|
267
345
|
case_branch_list << [nops, new_insns]
|
268
346
|
break
|
@@ -273,15 +351,15 @@ module TypeProf
|
|
273
351
|
end
|
274
352
|
end
|
275
353
|
case_branch_list.each do |nops, new_insns|
|
276
|
-
nops.each {|i| @insns[i] =
|
354
|
+
nops.each {|i| @insns[i] = Insn.new(:nop, []) }
|
277
355
|
new_insns.each {|i, insn| @insns[i] = insn }
|
278
356
|
end
|
279
357
|
|
280
358
|
# find a pattern: getlocal(recv), ..., send (is_a?, respond_to?), branch
|
281
359
|
recv_getlocal_send_branch_list = []
|
282
360
|
(@insns.size - 1).times do |i|
|
283
|
-
insn
|
284
|
-
if insn == :getlocal && operands[1] == 0
|
361
|
+
insn = @insns[i]
|
362
|
+
if insn.insn == :getlocal && insn.operands[1] == 0
|
285
363
|
j = i + 1
|
286
364
|
sp = 1
|
287
365
|
while @insns[j]
|
@@ -297,94 +375,118 @@ module TypeProf
|
|
297
375
|
end
|
298
376
|
recv_getlocal_send_branch_list.each do |i, j|
|
299
377
|
next if (i + 1 .. j + 1).any? {|i| branch_targets[i] }
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
@insns[j] =
|
304
|
-
@insns[j + 1] =
|
378
|
+
getlocal_operands = @insns[i].operands
|
379
|
+
send_operands = @insns[j].operands
|
380
|
+
branch_operands = @insns[j + 1].operands
|
381
|
+
@insns[j] = Insn.new(:nop, [])
|
382
|
+
@insns[j + 1] = Insn.new(:recv_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands])
|
305
383
|
end
|
306
384
|
|
307
385
|
# find a pattern: getlocal, send (===), branch
|
308
386
|
arg_getlocal_send_branch_list = []
|
309
387
|
(@insns.size - 1).times do |i|
|
310
|
-
insn1
|
311
|
-
next unless insn1 == :getlocal &&
|
312
|
-
insn2
|
313
|
-
next unless insn2 == :send
|
314
|
-
|
315
|
-
next unless
|
316
|
-
insn3
|
317
|
-
next unless insn3 == :branch
|
388
|
+
insn1 = @insns[i]
|
389
|
+
next unless insn1.insn == :getlocal && insn1.operands[1] == 0
|
390
|
+
insn2 = @insns[i + 1]
|
391
|
+
next unless insn2.insn == :send
|
392
|
+
send_operands = insn2.operands[0]
|
393
|
+
next unless send_operands[:flag] == 16 && send_operands[:orig_argc] == 1
|
394
|
+
insn3 = @insns[i + 2]
|
395
|
+
next unless insn3.insn == :branch
|
318
396
|
arg_getlocal_send_branch_list << i
|
319
397
|
end
|
320
398
|
arg_getlocal_send_branch_list.each do |i|
|
321
399
|
next if (i .. i + 2).any? {|i| branch_targets[i] }
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
@insns[i + 1] =
|
326
|
-
@insns[i + 2] =
|
400
|
+
getlocal_operands = @insns[i].operands
|
401
|
+
send_operands = @insns[i + 1].operands
|
402
|
+
branch_operands = @insns[i + 2].operands
|
403
|
+
@insns[i + 1] = Insn.new(:nop, [])
|
404
|
+
@insns[i + 2] = Insn.new(:arg_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands])
|
327
405
|
end
|
328
406
|
|
329
407
|
# find a pattern: send (block_given?), branch
|
330
408
|
send_branch_list = []
|
331
409
|
(@insns.size - 1).times do |i|
|
332
|
-
insn
|
333
|
-
if insn == :send
|
334
|
-
insn
|
335
|
-
if insn == :branch
|
410
|
+
insn = @insns[i]
|
411
|
+
if insn.insn == :send
|
412
|
+
insn = @insns[i + 1]
|
413
|
+
if insn.insn == :branch
|
336
414
|
send_branch_list << i
|
337
415
|
end
|
338
416
|
end
|
339
417
|
end
|
340
418
|
send_branch_list.each do |i|
|
341
419
|
next if branch_targets[i + 1]
|
342
|
-
|
343
|
-
|
344
|
-
@insns[i] =
|
345
|
-
@insns[i + 1] =
|
420
|
+
send_operands = @insns[i].operands
|
421
|
+
branch_operands = @insns[i + 1].operands
|
422
|
+
@insns[i] = Insn.new(:nop, [])
|
423
|
+
@insns[i + 1] = Insn.new(:send_branch, [send_operands, branch_operands])
|
346
424
|
end
|
347
425
|
|
348
426
|
# find a pattern: getlocal, dup, branch
|
349
427
|
(@insns.size - 2).times do |i|
|
350
428
|
next if branch_targets[i + 1] || branch_targets[i + 2]
|
351
|
-
insn0
|
352
|
-
insn1
|
353
|
-
insn2
|
354
|
-
if insn0 == :getlocal && insn1 == :dup && insn2 == :branch &&
|
355
|
-
|
356
|
-
|
357
|
-
|
429
|
+
insn0 = @insns[i]
|
430
|
+
insn1 = @insns[i + 1]
|
431
|
+
insn2 = @insns[i + 2]
|
432
|
+
if insn0.insn == :getlocal && insn1.insn == :dup && insn2.insn == :branch && insn0.operands[1] == 0
|
433
|
+
getlocal_operands = insn0.operands
|
434
|
+
dup_operands = insn1.operands
|
435
|
+
branch_operands = insn2.operands
|
436
|
+
@insns[i ] = Insn.new(:nop, [])
|
437
|
+
@insns[i + 1] = Insn.new(:nop, [])
|
438
|
+
@insns[i + 2] = Insn.new(:getlocal_dup_branch, [getlocal_operands, dup_operands, branch_operands])
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
# find a pattern: dup, setlocal, branch
|
443
|
+
(@insns.size - 2).times do |i|
|
444
|
+
next if branch_targets[i + 1] || branch_targets[i + 2]
|
445
|
+
insn0 = @insns[i]
|
446
|
+
insn1 = @insns[i + 1]
|
447
|
+
insn2 = @insns[i + 2]
|
448
|
+
if insn0.insn == :dup && insn1.insn == :setlocal && insn2.insn == :branch && insn1.operands[1] == 0
|
449
|
+
dup_operands = insn0.operands
|
450
|
+
setlocal_operands = insn1.operands
|
451
|
+
branch_operands = insn2.operands
|
452
|
+
@insns[i ] = Insn.new(:nop, [])
|
453
|
+
@insns[i + 1] = Insn.new(:nop, [])
|
454
|
+
@insns[i + 2] = Insn.new(:dup_setlocal_branch, [dup_operands, setlocal_operands, branch_operands])
|
358
455
|
end
|
359
456
|
end
|
360
457
|
|
361
458
|
# find a pattern: dup, branch
|
362
459
|
(@insns.size - 1).times do |i|
|
363
460
|
next if branch_targets[i + 1]
|
364
|
-
insn0
|
365
|
-
insn1
|
366
|
-
if insn0 == :dup && insn1 == :branch
|
367
|
-
|
368
|
-
|
461
|
+
insn0 = @insns[i]
|
462
|
+
insn1 = @insns[i + 1]
|
463
|
+
if insn0.insn == :dup && insn1.insn == :branch
|
464
|
+
dup_operands = insn0.operands
|
465
|
+
branch_operands = insn1.operands
|
466
|
+
@insns[i ] = Insn.new(:nop, [])
|
467
|
+
@insns[i + 1] = Insn.new(:dup_branch, [dup_operands, branch_operands])
|
369
468
|
end
|
370
469
|
end
|
371
470
|
|
372
471
|
# find a pattern: getlocal, branch
|
373
472
|
(@insns.size - 1).times do |i|
|
374
473
|
next if branch_targets[i + 1]
|
375
|
-
insn0
|
376
|
-
insn1
|
377
|
-
if
|
378
|
-
|
379
|
-
|
474
|
+
insn0 = @insns[i]
|
475
|
+
insn1 = @insns[i + 1]
|
476
|
+
if insn0.insn == :getlocal && insn0.operands[1] == 0 && insn1.insn == :branch
|
477
|
+
getlocal_operands = insn0.operands
|
478
|
+
branch_operands = insn1.operands
|
479
|
+
@insns[i ] = Insn.new(:nop, [])
|
480
|
+
@insns[i + 1] = Insn.new(:getlocal_branch, [getlocal_operands, branch_operands])
|
380
481
|
end
|
381
482
|
end
|
382
483
|
end
|
383
484
|
|
384
485
|
def check_send_branch(sp, j)
|
385
|
-
insn
|
486
|
+
insn = @insns[j]
|
487
|
+
operands = insn.operands
|
386
488
|
|
387
|
-
case insn
|
489
|
+
case insn.insn
|
388
490
|
when :putspecialobject, :putnil, :putobject, :duparray, :putstring,
|
389
491
|
:putself
|
390
492
|
sp += 1
|
@@ -422,7 +524,7 @@ module TypeProf
|
|
422
524
|
argc += 1 # receiver
|
423
525
|
argc += kw_arg.size if kw_arg
|
424
526
|
sp -= argc
|
425
|
-
return :match if insn == :send && sp == 0 && @insns[j + 1]
|
527
|
+
return :match if insn.insn == :send && sp == 0 && @insns[j + 1].insn == :branch
|
426
528
|
sp += 1
|
427
529
|
when :arg_getlocal_send_branch
|
428
530
|
return # not implemented
|
data/lib/typeprof/method.rb
CHANGED
@@ -283,12 +283,12 @@ module TypeProf
|
|
283
283
|
# XXX: a block is passed to a method that does not accept block.
|
284
284
|
# Should we call the passed block with any arguments?
|
285
285
|
ret_ty = ret_ty.remove_type_vars
|
286
|
-
ctn[ret_ty, caller_ep, ncaller_env]
|
286
|
+
ctn[ret_ty, caller_ep, ncaller_env] if ret_ty != Type.bot
|
287
287
|
end
|
288
288
|
else
|
289
289
|
ret_ty = ret_ty.substitute(subst, Config.options[:type_depth_limit])
|
290
290
|
ret_ty = ret_ty.remove_type_vars
|
291
|
-
ctn[ret_ty, caller_ep, ncaller_env]
|
291
|
+
ctn[ret_ty, caller_ep, ncaller_env] if ret_ty != Type.bot
|
292
292
|
end
|
293
293
|
end
|
294
294
|
|
data/lib/typeprof/type.rb
CHANGED
@@ -895,7 +895,7 @@ module TypeProf
|
|
895
895
|
fargs << ("**" + all_val_ty.screen_name(scratch))
|
896
896
|
end
|
897
897
|
if Config.options[:show_parameter_names]
|
898
|
-
farg_names = farg_names.map {|name| name
|
898
|
+
farg_names = farg_names.map {|name| RBS::Parser::KEYWORDS.key?(name.to_s) ? "`#{name}`" : name }
|
899
899
|
farg_names = farg_names.map {|name| name.is_a?(Integer) ? "noname_#{ name }" : name }
|
900
900
|
fargs = fargs.zip(farg_names).map {|farg, name| name ? "#{ farg } #{ name }" : farg }
|
901
901
|
end
|
data/lib/typeprof/version.rb
CHANGED
data/smoke/break2.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
def type(type)
|
2
|
+
end
|
3
|
+
|
4
|
+
def out(*out)
|
5
|
+
end
|
6
|
+
|
7
|
+
def untyped(untyped:)
|
8
|
+
end
|
9
|
+
|
10
|
+
__END__
|
11
|
+
# Classes
|
12
|
+
class Object
|
13
|
+
private
|
14
|
+
def type: (untyped `type`) -> nil
|
15
|
+
def out: (*untyped `out`) -> nil
|
16
|
+
def untyped: (untyped: untyped) -> nil
|
17
|
+
end
|
data/smoke/next2.rb
CHANGED
data/smoke/or_raise.rb
ADDED
data/smoke/pattern-match1.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# RUBY_VERSION >= 3.0
|
2
|
+
# NO_SHOW_ERRORS
|
2
3
|
|
3
4
|
def foo
|
4
5
|
case [:a, :b, :c]
|
@@ -11,12 +12,6 @@ end
|
|
11
12
|
foo
|
12
13
|
|
13
14
|
__END__
|
14
|
-
# Errors
|
15
|
-
smoke/pattern-match1.rb:5: [error] undefined method: nil#length
|
16
|
-
smoke/pattern-match1.rb:5: [error] undefined method: nil#[]
|
17
|
-
smoke/pattern-match1.rb:5: [error] undefined method: nil#[]
|
18
|
-
smoke/pattern-match1.rb:5: [error] undefined method: nil#[]
|
19
|
-
|
20
15
|
# Classes
|
21
16
|
class Object
|
22
17
|
private
|
data/testbed/ao.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: typeprof
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.15.
|
4
|
+
version: 0.15.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yusuke Endoh
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rbs
|
@@ -217,6 +217,7 @@ files:
|
|
217
217
|
- smoke/hash4.rb
|
218
218
|
- smoke/hash5.rb
|
219
219
|
- smoke/huge_union.rb
|
220
|
+
- smoke/identifier_keywords.rb
|
220
221
|
- smoke/included.rb
|
221
222
|
- smoke/inheritance.rb
|
222
223
|
- smoke/inheritance2.rb
|
@@ -276,6 +277,7 @@ files:
|
|
276
277
|
- smoke/optional1.rb
|
277
278
|
- smoke/optional2.rb
|
278
279
|
- smoke/optional3.rb
|
280
|
+
- smoke/or_raise.rb
|
279
281
|
- smoke/parameterizedd-self.rb
|
280
282
|
- smoke/parameterizedd-self2.rb
|
281
283
|
- smoke/pathname1.rb
|