laser 0.7.0.pre1 → 0.7.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|