laser 0.7.0.pre1 → 0.7.0.pre2
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.
- data/Gemfile +2 -2
- data/Gemfile.lock +46 -0
- data/bin/laser +4 -4
- data/laser.gemspec +8 -7
- data/lib/laser.rb +1 -0
- data/lib/laser/analysis/bootstrap/bootstrap.rb +21 -14
- data/lib/laser/analysis/bootstrap/dispatch_results.rb +192 -0
- data/lib/laser/analysis/bootstrap/laser_module.rb +2 -2
- data/lib/laser/analysis/control_flow/cfg_builder.rb +1 -3
- data/lib/laser/analysis/control_flow/constant_propagation.rb +10 -95
- data/lib/laser/analysis/control_flow/simulation.rb +6 -1
- data/lib/laser/analysis/special_methods/send.rb +37 -25
- data/lib/laser/analysis/unused_methods.rb +2 -0
- data/lib/laser/runner.rb +1 -1
- data/lib/laser/version.rb +1 -1
- data/lib/laser/warning.rb +3 -1
- data/spec/analysis_specs/control_flow_specs/yield_properties_spec.rb +2 -1
- data/spec/analysis_specs/scope_annotation_spec.rb +1 -1
- data/spec/analysis_specs/unused_methods_spec.rb +115 -0
- metadata +6 -5
- data/VERSION +0 -1
data/Gemfile
CHANGED
@@ -6,9 +6,9 @@ gem 'axiom_of_choice'
|
|
6
6
|
gem 'stream', '0.5'
|
7
7
|
gem 'object_regex', '~> 1.0'
|
8
8
|
gem 'trollop', '~> 1.16.2'
|
9
|
+
gem 'rake', '~> 0.9.0'
|
9
10
|
|
10
11
|
group :development do
|
11
|
-
gem '
|
12
|
-
gem 'rspec', '~> 2.3.0'
|
12
|
+
gem 'rspec', '~> 2.4.0'
|
13
13
|
gem 'cucumber', '>= 0.10.0'
|
14
14
|
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://www.rubygems.org/
|
3
|
+
specs:
|
4
|
+
axiom_of_choice (1.0.0)
|
5
|
+
builder (3.0.0)
|
6
|
+
cucumber (1.0.1)
|
7
|
+
builder (>= 2.1.2)
|
8
|
+
diff-lcs (>= 1.1.2)
|
9
|
+
gherkin (~> 2.4.5)
|
10
|
+
json (>= 1.4.6)
|
11
|
+
term-ansicolor (>= 1.0.5)
|
12
|
+
diff-lcs (1.1.2)
|
13
|
+
gherkin (2.4.5)
|
14
|
+
json (>= 1.4.6)
|
15
|
+
json (1.5.3)
|
16
|
+
object_regex (1.0.1)
|
17
|
+
polyglot (0.3.1)
|
18
|
+
rake (0.9.2)
|
19
|
+
ripper-plus (1.3.0)
|
20
|
+
rspec (2.4.0)
|
21
|
+
rspec-core (~> 2.4.0)
|
22
|
+
rspec-expectations (~> 2.4.0)
|
23
|
+
rspec-mocks (~> 2.4.0)
|
24
|
+
rspec-core (2.4.0)
|
25
|
+
rspec-expectations (2.4.0)
|
26
|
+
diff-lcs (~> 1.1.2)
|
27
|
+
rspec-mocks (2.4.0)
|
28
|
+
stream (0.5)
|
29
|
+
term-ansicolor (1.0.5)
|
30
|
+
treetop (1.4.9)
|
31
|
+
polyglot (>= 0.3.1)
|
32
|
+
trollop (1.16.2)
|
33
|
+
|
34
|
+
PLATFORMS
|
35
|
+
ruby
|
36
|
+
|
37
|
+
DEPENDENCIES
|
38
|
+
axiom_of_choice
|
39
|
+
cucumber (>= 0.10.0)
|
40
|
+
object_regex (~> 1.0)
|
41
|
+
rake (~> 0.9.0)
|
42
|
+
ripper-plus (~> 1.3.0)
|
43
|
+
rspec (~> 2.4.0)
|
44
|
+
stream (= 0.5)
|
45
|
+
treetop (~> 1.4)
|
46
|
+
trollop (~> 1.16.2)
|
data/bin/laser
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require 'rubygems'
|
3
|
-
require 'bundler/setup'
|
4
|
-
Bundler.require(:default)
|
2
|
+
#require 'rubygems'
|
3
|
+
#require 'bundler/setup'
|
4
|
+
#Bundler.require(:default)
|
5
5
|
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
6
|
require 'laser'
|
7
|
-
Laser::Runner.new(ARGV.dup).run
|
7
|
+
Laser::Runner.new(ARGV.dup).run
|
data/laser.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{laser}
|
8
|
-
s.version = "0.7.0.
|
8
|
+
s.version = "0.7.0.pre2"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = [%q{Michael Edgar}]
|
12
|
-
s.date = %q{2011-08-
|
12
|
+
s.date = %q{2011-08-19}
|
13
13
|
s.description = %q{Laser is an advanced static analysis tool for Ruby.}
|
14
14
|
s.email = %q{michael.j.edgar@dartmouth.edu}
|
15
15
|
s.executables = [%q{laser}]
|
@@ -22,10 +22,10 @@ Gem::Specification.new do |s|
|
|
22
22
|
".document",
|
23
23
|
".rspec",
|
24
24
|
"Gemfile",
|
25
|
+
"Gemfile.lock",
|
25
26
|
"LICENSE",
|
26
27
|
"README.md",
|
27
28
|
"Rakefile",
|
28
|
-
"VERSION",
|
29
29
|
"bin/laser",
|
30
30
|
"design_docs/goals.md",
|
31
31
|
"design_docs/object_regex.md",
|
@@ -56,6 +56,7 @@ Gem::Specification.new do |s|
|
|
56
56
|
"lib/laser/analysis/arity.rb",
|
57
57
|
"lib/laser/analysis/bindings.rb",
|
58
58
|
"lib/laser/analysis/bootstrap/bootstrap.rb",
|
59
|
+
"lib/laser/analysis/bootstrap/dispatch_results.rb",
|
59
60
|
"lib/laser/analysis/bootstrap/laser_class.rb",
|
60
61
|
"lib/laser/analysis/bootstrap/laser_method.rb",
|
61
62
|
"lib/laser/analysis/bootstrap/laser_module.rb",
|
@@ -353,8 +354,8 @@ Gem::Specification.new do |s|
|
|
353
354
|
s.add_runtime_dependency(%q<stream>, ["= 0.5"])
|
354
355
|
s.add_runtime_dependency(%q<object_regex>, ["~> 1.0"])
|
355
356
|
s.add_runtime_dependency(%q<trollop>, ["~> 1.16.2"])
|
356
|
-
s.
|
357
|
-
s.add_development_dependency(%q<rspec>, ["~> 2.
|
357
|
+
s.add_runtime_dependency(%q<rake>, ["~> 0.9.0"])
|
358
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.4.0"])
|
358
359
|
s.add_development_dependency(%q<cucumber>, [">= 0.10.0"])
|
359
360
|
else
|
360
361
|
s.add_dependency(%q<treetop>, ["~> 1.4"])
|
@@ -364,7 +365,7 @@ Gem::Specification.new do |s|
|
|
364
365
|
s.add_dependency(%q<object_regex>, ["~> 1.0"])
|
365
366
|
s.add_dependency(%q<trollop>, ["~> 1.16.2"])
|
366
367
|
s.add_dependency(%q<rake>, ["~> 0.9.0"])
|
367
|
-
s.add_dependency(%q<rspec>, ["~> 2.
|
368
|
+
s.add_dependency(%q<rspec>, ["~> 2.4.0"])
|
368
369
|
s.add_dependency(%q<cucumber>, [">= 0.10.0"])
|
369
370
|
end
|
370
371
|
else
|
@@ -375,7 +376,7 @@ Gem::Specification.new do |s|
|
|
375
376
|
s.add_dependency(%q<object_regex>, ["~> 1.0"])
|
376
377
|
s.add_dependency(%q<trollop>, ["~> 1.16.2"])
|
377
378
|
s.add_dependency(%q<rake>, ["~> 0.9.0"])
|
378
|
-
s.add_dependency(%q<rspec>, ["~> 2.
|
379
|
+
s.add_dependency(%q<rspec>, ["~> 2.4.0"])
|
379
380
|
s.add_dependency(%q<cucumber>, [">= 0.10.0"])
|
380
381
|
end
|
381
382
|
end
|
data/lib/laser.rb
CHANGED
@@ -62,6 +62,7 @@ require 'laser/analysis/bootstrap/laser_module_copy'
|
|
62
62
|
require 'laser/analysis/bootstrap/laser_singleton_class'
|
63
63
|
require 'laser/analysis/bootstrap/laser_proc'
|
64
64
|
require 'laser/analysis/bootstrap/laser_method'
|
65
|
+
require 'laser/analysis/bootstrap/dispatch_results'
|
65
66
|
require 'laser/analysis/laser_utils.rb'
|
66
67
|
require 'laser/analysis/protocol_registry'
|
67
68
|
require 'laser/analysis/scope'
|
@@ -197,23 +197,30 @@ module Laser
|
|
197
197
|
annotated_raise_frequency: Frequency::NEVER)
|
198
198
|
stub_method(kernel_module, 'singleton_class', builtin: true, pure: true,
|
199
199
|
annotated_raise_frequency: Frequency::NEVER)
|
200
|
-
|
201
|
-
stub_custom_method(kernel_module, SpecialMethods::SendMethod, '
|
202
|
-
|
200
|
+
|
201
|
+
send_method = stub_custom_method(kernel_module, SpecialMethods::SendMethod, 'send', :any, special: true)
|
202
|
+
send_method.arity = Arity.new(1..Float::INFINITY)
|
203
|
+
send_method = stub_custom_method(kernel_module, SpecialMethods::SendMethod, 'public_send', :public, special: true)
|
204
|
+
send_method.arity = Arity.new(1..Float::INFINITY)
|
205
|
+
|
203
206
|
raise_method = stub_method(kernel_module, 'raise', builtin: true, pure: true,
|
204
207
|
annotated_raise_frequency: Frequency::ALWAYS)
|
205
208
|
def raise_method.raise_type_for_types(self_type, arg_types, block_type)
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
arg_class
|
211
|
-
|
212
|
-
arg_class
|
213
|
-
|
214
|
-
arg_class
|
215
|
-
|
216
|
-
|
209
|
+
if arg_types.size == 0
|
210
|
+
ClassRegistry['RuntimeError'].as_type
|
211
|
+
else
|
212
|
+
Types::UnionType.new(arg_types[0].possible_classes.map do |arg_class|
|
213
|
+
if arg_class <= ClassRegistry['String']
|
214
|
+
ClassRegistry['RuntimeError'].as_type
|
215
|
+
elsif LaserSingletonClass === arg_class && arg_class < ClassRegistry['Class']
|
216
|
+
arg_class.get_instance.as_type
|
217
|
+
elsif arg_class <= ClassRegistry['Exception']
|
218
|
+
arg_class.as_type
|
219
|
+
elsif arg_class.instance_method_defined?('exception')
|
220
|
+
arg_class.instance_method(:exception).return_type_for_types(arg_class.as_type)
|
221
|
+
end
|
222
|
+
end)
|
223
|
+
end
|
217
224
|
end
|
218
225
|
stub_method(array_class, 'push', builtin: true, mutation: true, annotated_raise_frequency: Frequency::NEVER)
|
219
226
|
stub_method(array_class, 'pop', builtin: true, mutation: true, annotated_raise_frequency: Frequency::NEVER)
|
@@ -0,0 +1,192 @@
|
|
1
|
+
module Laser
|
2
|
+
module Analysis
|
3
|
+
# Collects the results of attempted dispatches and calculates the return type,
|
4
|
+
# raise type, and raise frequency.
|
5
|
+
#
|
6
|
+
# Is used whenever a dispatch is discovered that needs analysis. This
|
7
|
+
# includes, for example, performing CPA and analyzing a call to send/public_send.
|
8
|
+
#
|
9
|
+
# Is responsible for noting that a method has been used, as this is the central
|
10
|
+
# place for the logic pertaining to success/failure on dispatch. Keep in mind:
|
11
|
+
# a method isn't used if it is called with incorrect arity, and that arity
|
12
|
+
# checking should occur here.
|
13
|
+
class DispatchResults
|
14
|
+
ArityError = Struct.new(:receiver_class, :method_name, :provided, :expected)
|
15
|
+
ArityError.class_eval do
|
16
|
+
def message
|
17
|
+
"#{receiver_class.name}##{method_name} expects " +
|
18
|
+
"#{expected} arguments, got #{provided}."
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
PrivacyError = Struct.new(:receiver_class, :method_name)
|
23
|
+
PrivacyError.class_eval do
|
24
|
+
def message
|
25
|
+
"Tried to call #{receiver_class.privacy_for(method_name)} method " +
|
26
|
+
"#{receiver_class.name}##{method_name}."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :result_type
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@raise_type = Types::EMPTY
|
34
|
+
@result_type = Types::EMPTY
|
35
|
+
@privacy_failures = 0
|
36
|
+
@privacy_samples = 0
|
37
|
+
@privacy_errors = Set[]
|
38
|
+
@arity_failures = 0
|
39
|
+
@arity_samples = 0
|
40
|
+
@arity_errors = Set[]
|
41
|
+
@normal_failures = 0
|
42
|
+
@normal_samples = 0
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_samples_from_dispatch(methods, self_type, cartesian, ignore_privacy)
|
46
|
+
if methods.empty?
|
47
|
+
@privacy_failures += 1
|
48
|
+
@privacy_samples += 1
|
49
|
+
@raise_type |= ClassRegistry['NoMethodError'].as_type
|
50
|
+
end
|
51
|
+
methods.each do |method|
|
52
|
+
next unless check_privacy(method, self_type, ignore_privacy)
|
53
|
+
cartesian.each do |*type_list, block_type|
|
54
|
+
next unless check_arity(method, self_type, type_list.size)
|
55
|
+
method.been_used!
|
56
|
+
normal_dispatch(method, self_type, cartesian)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def check_privacy(method, self_type, ignore_privacy)
|
62
|
+
result = false
|
63
|
+
if ignore_privacy
|
64
|
+
passes_privacy
|
65
|
+
result = true
|
66
|
+
else
|
67
|
+
self_type.possible_classes.each do |self_class|
|
68
|
+
if self_class.visibility_for(method.name) == :public
|
69
|
+
passes_privacy
|
70
|
+
result = true
|
71
|
+
else
|
72
|
+
fails_privacy(self_class, method.name)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
result
|
77
|
+
end
|
78
|
+
|
79
|
+
def check_arity(method, self_type, proposed_arity)
|
80
|
+
result = false
|
81
|
+
self_type.possible_classes.each do |self_class|
|
82
|
+
if method.valid_arity?(proposed_arity)
|
83
|
+
passes_arity
|
84
|
+
result = true
|
85
|
+
else
|
86
|
+
fails_arity(self_class, method.name, proposed_arity, method.arity)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
result
|
90
|
+
end
|
91
|
+
|
92
|
+
def normal_dispatch(method, self_type, cartesian)
|
93
|
+
cartesian.each do |*type_list, block_type|
|
94
|
+
raise_frequency = method.raise_frequency_for_types(self_type, type_list, block_type)
|
95
|
+
if raise_frequency > Frequency::NEVER
|
96
|
+
fails_dispatch(method.raise_type_for_types(self_type, type_list, block_type))
|
97
|
+
end
|
98
|
+
if raise_frequency < Frequency::ALWAYS
|
99
|
+
passes_dispatch(method.return_type_for_types(self_type, type_list, block_type))
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
########## Result Accessors ##############
|
105
|
+
|
106
|
+
def raise_type
|
107
|
+
if @privacy_samples.zero?
|
108
|
+
ClassRegistry['NoMethodError'].as_type
|
109
|
+
else
|
110
|
+
@raise_type
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def raise_frequency
|
115
|
+
if @privacy_samples.zero?
|
116
|
+
Frequency::ALWAYS
|
117
|
+
else
|
118
|
+
[arity_failure_frequency, privacy_failure_frequency,
|
119
|
+
normal_failure_frequency].max
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
############ Arity-related dispatch issues ############
|
124
|
+
def arity_failure_frequency
|
125
|
+
if @arity_failures == 0
|
126
|
+
Frequency::NEVER
|
127
|
+
elsif @arity_failures == @arity_samples
|
128
|
+
Frequency::ALWAYS
|
129
|
+
else
|
130
|
+
Frequency::MAYBE
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def passes_arity
|
135
|
+
@arity_samples += 1
|
136
|
+
end
|
137
|
+
|
138
|
+
def fails_arity(receiver_class, method_name, provided, expected)
|
139
|
+
@arity_errors << ArityError.new(receiver_class, method_name, provided, expected)
|
140
|
+
@arity_samples += 1
|
141
|
+
@arity_failures += 1
|
142
|
+
@raise_type |= ClassRegistry['ArgumentError'].as_type
|
143
|
+
end
|
144
|
+
|
145
|
+
########## Privacy-related dispatch issues ############
|
146
|
+
|
147
|
+
def privacy_failure_frequency
|
148
|
+
if @privacy_failures == 0
|
149
|
+
Frequency::NEVER
|
150
|
+
elsif @privacy_failures == @privacy_samples
|
151
|
+
Frequency::ALWAYS
|
152
|
+
else
|
153
|
+
Frequency::MAYBE
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def passes_privacy
|
158
|
+
@privacy_samples += 1
|
159
|
+
end
|
160
|
+
|
161
|
+
def fails_privacy(receiver_class, method_name)
|
162
|
+
@privacy_errors << PrivacyError.new(receiver_class, method_name)
|
163
|
+
@privacy_samples += 1
|
164
|
+
@privacy_failures += 1
|
165
|
+
@raise_type |= ClassRegistry['NoMethodError'].as_type
|
166
|
+
end
|
167
|
+
|
168
|
+
######### Calculated dispatch issues ###########
|
169
|
+
|
170
|
+
def normal_failure_frequency
|
171
|
+
if @normal_failures == 0
|
172
|
+
Frequency::NEVER
|
173
|
+
elsif @normal_failures == @normal_samples
|
174
|
+
Frequency::ALWAYS
|
175
|
+
else
|
176
|
+
Frequency::MAYBE
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def passes_dispatch(return_type)
|
181
|
+
@result_type |= return_type
|
182
|
+
@normal_samples += 1
|
183
|
+
end
|
184
|
+
|
185
|
+
def fails_dispatch(raise_type)
|
186
|
+
@raise_type |= raise_type
|
187
|
+
@normal_failures += 1
|
188
|
+
@normal_samples += 1
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -349,8 +349,8 @@ module Laser
|
|
349
349
|
def alias_method(new, old)
|
350
350
|
newsym = new.to_sym
|
351
351
|
oldsym = old.to_sym
|
352
|
-
@instance_methods[newsym] =
|
353
|
-
@visibility_table[newsym] =
|
352
|
+
@instance_methods[newsym] = instance_method(oldsym)
|
353
|
+
@visibility_table[newsym] = visibility_for(oldsym)
|
354
354
|
end
|
355
355
|
|
356
356
|
def include(*mods)
|
@@ -956,10 +956,8 @@ module Laser
|
|
956
956
|
|
957
957
|
start_block no_block
|
958
958
|
message = const_instruct('no block given (yield)')
|
959
|
-
file_name = const_instruct(@current_node.file_name)
|
960
|
-
line_number = const_instruct(@current_node.line_number || 0)
|
961
959
|
raise_instance_of_instruct(
|
962
|
-
ClassRegistry['LocalJumpError'].binding, message,
|
960
|
+
ClassRegistry['LocalJumpError'].binding, message,
|
963
961
|
target: current_yield_fail)
|
964
962
|
|
965
963
|
start_block if_block
|
@@ -335,13 +335,9 @@ module Laser
|
|
335
335
|
def cpa_call_properties(receiver, method, args, instruction, opts)
|
336
336
|
ignore_privacy, block = instruction.ignore_privacy, instruction.block_operand
|
337
337
|
dispatches = cpa_dispatches(receiver, instruction, method, opts)
|
338
|
-
cartesian = calculate_possible_templates(
|
339
|
-
|
340
|
-
|
341
|
-
if result.empty?
|
342
|
-
raise TypeError.new("No methods named #{method} with matching types were found.")
|
343
|
-
end
|
344
|
-
[Types::UnionType.new(result), raise_result, raise_type]
|
338
|
+
cartesian = calculate_possible_templates(args, block)
|
339
|
+
results = collect_dispatch_results(dispatches, cartesian, ignore_privacy)
|
340
|
+
[results.result_type, results.raise_frequency, results.raise_type]
|
345
341
|
end
|
346
342
|
|
347
343
|
# Calculates all possible (self_type, dispatches) pairs for a call.
|
@@ -360,18 +356,16 @@ module Laser
|
|
360
356
|
|
361
357
|
# Calculates the set of methods potentially invoked in dynamic dispatch,
|
362
358
|
# and the set of all possible argument type combinations.
|
363
|
-
def calculate_possible_templates(
|
359
|
+
def calculate_possible_templates(args, block)
|
364
360
|
if Bindings::Base === args && Types::TupleType === args.expr_type
|
365
361
|
cartesian_parts = args.element_types
|
366
|
-
empty = cartesian_parts.empty?
|
367
362
|
elsif Bindings::Base === args && Types::UnionType === args.expr_type &&
|
368
363
|
Types::TupleType === args.expr_type.member_types.first
|
369
364
|
cartesian_parts = args.expr_type.member_types.first.element_types.map { |x| [x] }
|
370
|
-
empty = cartesian_parts.empty?
|
371
365
|
else
|
372
366
|
cartesian_parts = args.map(&:expr_type).map(&:member_types).map(&:to_a)
|
373
|
-
empty = args.empty?
|
374
367
|
end
|
368
|
+
empty = cartesian_parts.empty?
|
375
369
|
if empty && !block
|
376
370
|
cartesian = [ [Types::NILCLASS] ]
|
377
371
|
else
|
@@ -384,91 +378,12 @@ module Laser
|
|
384
378
|
cartesian
|
385
379
|
end
|
386
380
|
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
result |= methods.map do |method|
|
392
|
-
cartesian.map do |*type_list, block_type|
|
393
|
-
begin
|
394
|
-
method.return_type_for_types(self_type, type_list, block_type)
|
395
|
-
rescue TypeError => err
|
396
|
-
Laser.debug_puts("Invalid argument types found.")
|
397
|
-
nil
|
398
|
-
end
|
399
|
-
end.compact
|
400
|
-
end.flatten
|
401
|
-
end
|
402
|
-
result
|
403
|
-
end
|
404
|
-
|
405
|
-
# TODO(adgar): Optimize this. Use lattice-style expression of raisability
|
406
|
-
# until types need to be added too.
|
407
|
-
def raisability_for_templates(possible_dispatches, cartesian, ignore_privacy)
|
408
|
-
raise_type = Types::EMPTY
|
409
|
-
seen_public = seen_private = seen_raise = seen_succeed = seen_any = seen_missing = false
|
410
|
-
seen_valid_arity = seen_invalid_arity = false
|
411
|
-
arity = cartesian.first.size - 1 # -1 for block arg
|
412
|
-
possible_dispatches.each do |self_type, methods|
|
413
|
-
seen_any = true if methods.size > 0 && !seen_any
|
414
|
-
seen_missing = true if methods.empty? && !seen_missing
|
415
|
-
methods.each do |method|
|
416
|
-
if !seen_valid_arity && method.valid_arity?(arity)
|
417
|
-
seen_valid_arity = true
|
418
|
-
end
|
419
|
-
if !seen_invalid_arity && !method.valid_arity?(arity)
|
420
|
-
seen_invalid_arity = true
|
421
|
-
end
|
422
|
-
if !ignore_privacy
|
423
|
-
self_type.possible_classes.each do |self_class|
|
424
|
-
if self_class.visibility_for(method.name) == :public
|
425
|
-
seen_public = true
|
426
|
-
method.been_used! if method.valid_arity?(arity)
|
427
|
-
end
|
428
|
-
if !seen_private
|
429
|
-
seen_private = (self_class.visibility_for(method.name) != :public)
|
430
|
-
end
|
431
|
-
end
|
432
|
-
else
|
433
|
-
method.been_used! if method.valid_arity?(arity)
|
434
|
-
end
|
435
|
-
cartesian.each do |*type_list, block_type|
|
436
|
-
raise_frequency = method.raise_frequency_for_types(self_type, type_list, block_type)
|
437
|
-
if raise_frequency > Frequency::NEVER
|
438
|
-
seen_raise = true
|
439
|
-
raise_type = raise_type | method.raise_type_for_types(self_type, type_list, block_type)
|
440
|
-
end
|
441
|
-
seen_succeed = raise_frequency < Frequency::ALWAYS if !seen_succeed
|
442
|
-
end
|
443
|
-
end
|
444
|
-
end
|
445
|
-
|
446
|
-
if seen_any
|
447
|
-
fails_lookup = seen_missing ? Frequency::MAYBE : Frequency::NEVER
|
448
|
-
fails_privacy = if ignore_privacy
|
449
|
-
then Frequency::NEVER
|
450
|
-
else Frequency.for_samples(seen_private, seen_public)
|
451
|
-
end
|
452
|
-
failed_arity = Frequency.for_samples(seen_invalid_arity, seen_valid_arity)
|
453
|
-
if fails_privacy == Frequency::ALWAYS
|
454
|
-
raise_type = ClassRegistry['NoMethodError'].as_type
|
455
|
-
elsif failed_arity == Frequency::ALWAYS
|
456
|
-
raise_type = ClassRegistry['ArgumentError'].as_type
|
457
|
-
else
|
458
|
-
if fails_lookup > Frequency::NEVER || fails_privacy > Frequency::NEVER
|
459
|
-
raise_type |= ClassRegistry['NoMethodError'].as_type
|
460
|
-
end
|
461
|
-
if failed_arity > Frequency::NEVER
|
462
|
-
raise_type |= ClassRegistry['ArgumentError'].as_type
|
463
|
-
end
|
464
|
-
end
|
465
|
-
raised = Frequency.for_samples(seen_raise, seen_succeed)
|
466
|
-
raise_freq = [fails_privacy, raised, fails_lookup, failed_arity].max
|
467
|
-
else
|
468
|
-
raise_freq = Frequency::ALWAYS # no method!
|
469
|
-
raise_type = ClassRegistry['NoMethodError'].as_type
|
381
|
+
def collect_dispatch_results(dispatches, cartesian, ignore_privacy)
|
382
|
+
results = DispatchResults.new
|
383
|
+
dispatches.each do |self_type, methods|
|
384
|
+
results.add_samples_from_dispatch(methods, self_type, cartesian, ignore_privacy)
|
470
385
|
end
|
471
|
-
|
386
|
+
results
|
472
387
|
end
|
473
388
|
|
474
389
|
# Evaluates the instruction, and if the constant value is lowered,
|
@@ -197,7 +197,12 @@ module Laser
|
|
197
197
|
args = simulate_args(insn)
|
198
198
|
block_to_use = insn[-1][:block] && insn[-1][:block].value
|
199
199
|
result = simulate_call_dispatch(receiver, method, args, block_to_use, opts)
|
200
|
-
|
200
|
+
if insn[1]
|
201
|
+
insn[1].bind! result
|
202
|
+
if method == ClassRegistry['Array'].singleton_class.instance_method(:[])
|
203
|
+
insn[1].inferred_type = Types::TupleType.new(result.map { |x| Utilities.type_for(x) })
|
204
|
+
end
|
205
|
+
end
|
201
206
|
end
|
202
207
|
|
203
208
|
def simulate_args(insn)
|
@@ -18,48 +18,60 @@ module Laser
|
|
18
18
|
@privacy = privacy
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
21
|
+
def all_target_methods(self_type, arg_type)
|
22
|
+
collection = Set[]
|
22
23
|
arg_type.possible_classes.each do |target_klass|
|
23
|
-
if
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
if target_klass <= Analysis::ClassRegistry['String'] ||
|
25
|
+
target_klass <= Analysis::ClassRegistry['Symbol']
|
26
|
+
if LaserSingletonClass === target_klass &&
|
27
|
+
target_method_name = target_klass.get_instance.to_s
|
28
|
+
self_type.possible_classes.each do |self_class|
|
29
|
+
collection << self_class.instance_method(target_method_name)
|
30
|
+
end
|
31
|
+
else
|
32
|
+
getter = @privacy == :any ? :instance_methods : :public_instance_methods
|
33
|
+
self_type.possible_classes.each do |self_class|
|
34
|
+
self_class.send(getter).each do |method_name|
|
35
|
+
collection << self_class.instance_method(method_name)
|
36
|
+
end
|
30
37
|
end
|
31
38
|
end
|
32
39
|
end
|
33
40
|
end
|
34
|
-
|
35
|
-
|
36
|
-
def passes_visibility?(klass, name)
|
37
|
-
return true if @privacy == :any
|
38
|
-
klass.visibility_for(name) == @privacy
|
41
|
+
collection
|
39
42
|
end
|
40
43
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
def dispatch_results(self_type, arg_types, block_type)
|
45
|
+
methods = all_target_methods(self_type, arg_types[0])
|
46
|
+
cartesian = [[*arg_types[1..-1], block_type]]
|
47
|
+
ignore_privacy = @privacy == :any
|
48
|
+
results = DispatchResults.new
|
49
|
+
results.add_samples_from_dispatch(methods, self_type, cartesian, ignore_privacy)
|
50
|
+
results
|
47
51
|
end
|
48
52
|
|
49
53
|
def return_type_for_types(self_type, arg_types, block_type)
|
50
|
-
|
54
|
+
if arg_types.size >= 1
|
55
|
+
dispatch_results(self_type, arg_types, block_type).result_type
|
56
|
+
else
|
57
|
+
Types::EMPTY
|
58
|
+
end
|
51
59
|
end
|
52
60
|
|
53
61
|
def raise_type_for_types(self_type, arg_types, block_type)
|
54
|
-
|
62
|
+
if arg_types.size >= 1
|
63
|
+
dispatch_results(self_type, arg_types, block_type).raise_type
|
64
|
+
else
|
65
|
+
Frequency::ALWAYS
|
66
|
+
end
|
55
67
|
end
|
56
68
|
|
57
69
|
def raise_frequency_for_types(self_type, arg_types, block_type)
|
58
|
-
|
59
|
-
|
60
|
-
|
70
|
+
if arg_types.size >= 1
|
71
|
+
dispatch_results(self_type, arg_types, block_type).raise_frequency
|
72
|
+
else
|
73
|
+
ClassRegistry['ArgumentError'].as_type
|
61
74
|
end
|
62
|
-
Frequency.combine_samples(all_frequencies)
|
63
75
|
end
|
64
76
|
end
|
65
77
|
end
|
@@ -7,7 +7,9 @@ module Laser
|
|
7
7
|
Analysis::ProtocolRegistry.class_protocols.each do |key, klass|
|
8
8
|
next if Analysis::LaserSingletonClass === klass || classes.include?(klass)
|
9
9
|
klass.__all_instance_methods(false).each do |name|
|
10
|
+
p klass, name
|
10
11
|
method = klass.instance_method(name)
|
12
|
+
p method
|
11
13
|
unless method.dispatched? || method.builtin || method.special
|
12
14
|
methods << method
|
13
15
|
end
|
data/lib/laser/runner.rb
CHANGED
@@ -46,7 +46,7 @@ module Laser
|
|
46
46
|
if (only_name = settings[:only])
|
47
47
|
@fix = @using = Warning.concrete_warnings.select do |w|
|
48
48
|
classname = w.name && w.name.split('::').last
|
49
|
-
(classname && only_name.index(classname)) || (w.short_name && w.short_name
|
49
|
+
(classname && only_name.index(classname)) || (w.short_name && only_name.index(w.short_name))
|
50
50
|
end
|
51
51
|
end
|
52
52
|
if settings[:profile]
|
data/lib/laser/version.rb
CHANGED
data/lib/laser/warning.rb
CHANGED
@@ -11,6 +11,8 @@ module Laser
|
|
11
11
|
|
12
12
|
desc { "#{self.class.name} #{file}:#{line_number} (#{severity})" }
|
13
13
|
|
14
|
+
@@all_types = nil
|
15
|
+
|
14
16
|
# This tracks all subclasses (and subclasses of subclasses, etc). Plus, this
|
15
17
|
# method is inherited, so Laser::LineWarning.all_subclasses will have all
|
16
18
|
# subclasses of Laser::LineWarning!
|
@@ -34,7 +36,7 @@ module Laser
|
|
34
36
|
# All types should be shared and modified by *all* subclasses. This makes
|
35
37
|
# Laser::Warning.all_types a global registry.
|
36
38
|
def self.all_types
|
37
|
-
|
39
|
+
@@all_types ||= Hash.new {|h,k| h[k] = []}
|
38
40
|
end
|
39
41
|
|
40
42
|
# When a Warning subclass is subclassed, store the subclass and inform the
|
@@ -955,7 +955,7 @@ end
|
|
955
955
|
}
|
956
956
|
end
|
957
957
|
|
958
|
-
|
958
|
+
pending 'correctly resolves many bindings, creates new modules and classes, and defines methods' do
|
959
959
|
tree = annotate_all(@input)
|
960
960
|
|
961
961
|
bindings_mod = 'Laser::Analysis::Bindings'
|
@@ -62,4 +62,119 @@ EOF
|
|
62
62
|
Set[ClassRegistry['UnusedMethod2'].instance_method(:foo),
|
63
63
|
ClassRegistry['UnusedMethod3'].instance_method(:baz)]
|
64
64
|
end
|
65
|
+
|
66
|
+
it 'does not mark failed dispatches as used' do
|
67
|
+
cfg <<-EOF
|
68
|
+
class UnusedMethod4
|
69
|
+
def foo(x)
|
70
|
+
end
|
71
|
+
def bar(x, y=x)
|
72
|
+
end
|
73
|
+
def baz(a, b, *rest)
|
74
|
+
bar(*rest)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
inst = UnusedMethod4.new
|
78
|
+
inst.foo(gets) # marks foo as used
|
79
|
+
inst.baz(gets, gets, gets, gets, gets) # marks baz, but not bar
|
80
|
+
EOF
|
81
|
+
@methods = UnusedMethodDetection.unused_methods
|
82
|
+
|
83
|
+
Set.new(@methods).should ==
|
84
|
+
Set[ClassRegistry['UnusedMethod4'].instance_method(:bar)]
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'works with send on constants, respecting arity' do
|
88
|
+
cfg <<-EOF
|
89
|
+
class UnusedMethod5
|
90
|
+
def zero
|
91
|
+
end
|
92
|
+
def one_or_two(a, b=1)
|
93
|
+
end
|
94
|
+
def two(a, b)
|
95
|
+
end
|
96
|
+
def three(a, b, c)
|
97
|
+
end
|
98
|
+
def any(*rest)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
choice = [:zero, :one_or_two, :two, :three, :any][gets.to_i]
|
102
|
+
UnusedMethod5.new.send(choice, gets, gets)
|
103
|
+
EOF
|
104
|
+
@methods = UnusedMethodDetection.unused_methods
|
105
|
+
|
106
|
+
Set.new(@methods).should ==
|
107
|
+
Set[ClassRegistry['UnusedMethod5'].instance_method(:zero),
|
108
|
+
ClassRegistry['UnusedMethod5'].instance_method(:three)]
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'works with public_send on constants, respecting privacy' do
|
112
|
+
cfg <<-EOF
|
113
|
+
class UnusedMethod6
|
114
|
+
def public_one_or_two(a, b=1)
|
115
|
+
end
|
116
|
+
def public_two(a, b)
|
117
|
+
end
|
118
|
+
private
|
119
|
+
def private_two(a, b)
|
120
|
+
end
|
121
|
+
protected
|
122
|
+
def protected_two(a, b)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
choice = [:public_one_or_two, :public_two,
|
126
|
+
:private_two, :protected_two][gets.to_i]
|
127
|
+
UnusedMethod6.new.public_send(choice, gets, gets)
|
128
|
+
EOF
|
129
|
+
@methods = UnusedMethodDetection.unused_methods
|
130
|
+
|
131
|
+
Set.new(@methods).should ==
|
132
|
+
Set[ClassRegistry['UnusedMethod6'].instance_method(:private_two),
|
133
|
+
ClassRegistry['UnusedMethod6'].instance_method(:protected_two)]
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'works with non-constant send, respecting arity' do
|
137
|
+
cfg <<-EOF
|
138
|
+
class UnusedMethod7
|
139
|
+
def zero
|
140
|
+
end
|
141
|
+
def one_or_two(a, b=1)
|
142
|
+
end
|
143
|
+
def two(a, b)
|
144
|
+
end
|
145
|
+
def three(a, b, c)
|
146
|
+
end
|
147
|
+
def any(*rest)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
UnusedMethod7.new.send(gets, gets, gets)
|
151
|
+
EOF
|
152
|
+
@methods = UnusedMethodDetection.unused_methods
|
153
|
+
|
154
|
+
Set.new(@methods).should ==
|
155
|
+
Set[ClassRegistry['UnusedMethod7'].instance_method(:three)]
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'works with non-constants public_send, respecting privacy' do
|
159
|
+
cfg <<-EOF
|
160
|
+
class UnusedMethod8
|
161
|
+
def public_one_or_two(a, b=1)
|
162
|
+
end
|
163
|
+
def public_two(a, b)
|
164
|
+
end
|
165
|
+
private
|
166
|
+
def private_two(a, b)
|
167
|
+
end
|
168
|
+
protected
|
169
|
+
def protected_two(a, b)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
UnusedMethod8.new.public_send(gets, gets, gets)
|
173
|
+
EOF
|
174
|
+
@methods = UnusedMethodDetection.unused_methods
|
175
|
+
|
176
|
+
Set.new(@methods).should ==
|
177
|
+
Set[ClassRegistry['UnusedMethod8'].instance_method(:private_two),
|
178
|
+
ClassRegistry['UnusedMethod8'].instance_method(:protected_two)]
|
179
|
+
end
|
65
180
|
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: laser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease: 6
|
5
|
-
version: 0.7.0.
|
5
|
+
version: 0.7.0.pre2
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Michael Edgar
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-08-
|
13
|
+
date: 2011-08-19 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: treetop
|
@@ -86,7 +86,7 @@ dependencies:
|
|
86
86
|
- - ~>
|
87
87
|
- !ruby/object:Gem::Version
|
88
88
|
version: 0.9.0
|
89
|
-
type: :
|
89
|
+
type: :runtime
|
90
90
|
prerelease: false
|
91
91
|
version_requirements: *id007
|
92
92
|
- !ruby/object:Gem::Dependency
|
@@ -96,7 +96,7 @@ dependencies:
|
|
96
96
|
requirements:
|
97
97
|
- - ~>
|
98
98
|
- !ruby/object:Gem::Version
|
99
|
-
version: 2.
|
99
|
+
version: 2.4.0
|
100
100
|
type: :development
|
101
101
|
prerelease: false
|
102
102
|
version_requirements: *id008
|
@@ -124,10 +124,10 @@ files:
|
|
124
124
|
- .document
|
125
125
|
- .rspec
|
126
126
|
- Gemfile
|
127
|
+
- Gemfile.lock
|
127
128
|
- LICENSE
|
128
129
|
- README.md
|
129
130
|
- Rakefile
|
130
|
-
- VERSION
|
131
131
|
- bin/laser
|
132
132
|
- design_docs/goals.md
|
133
133
|
- design_docs/object_regex.md
|
@@ -158,6 +158,7 @@ files:
|
|
158
158
|
- lib/laser/analysis/arity.rb
|
159
159
|
- lib/laser/analysis/bindings.rb
|
160
160
|
- lib/laser/analysis/bootstrap/bootstrap.rb
|
161
|
+
- lib/laser/analysis/bootstrap/dispatch_results.rb
|
161
162
|
- lib/laser/analysis/bootstrap/laser_class.rb
|
162
163
|
- lib/laser/analysis/bootstrap/laser_method.rb
|
163
164
|
- lib/laser/analysis/bootstrap/laser_module.rb
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
0.6.0
|