typeprof 0.15.0 → 0.15.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|