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 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 'rake', '~> 0.9.0'
12
- gem 'rspec', '~> 2.3.0'
12
+ gem 'rspec', '~> 2.4.0'
13
13
  gem 'cucumber', '>= 0.10.0'
14
14
  end
@@ -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
@@ -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.pre1"
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}
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.add_development_dependency(%q<rake>, ["~> 0.9.0"])
357
- s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
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.3.0"])
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.3.0"])
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
@@ -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
- stub_custom_method(kernel_module, SpecialMethods::SendMethod, 'send', :any, special: true)
201
- stub_custom_method(kernel_module, SpecialMethods::SendMethod, 'public_send', :public, special: true)
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
- Types::UnionType.new(arg_types[0].possible_classes.map do |arg_class|
207
- if arg_class <= ClassRegistry['String']
208
- ClassRegistry['RuntimeError'].as_type
209
- elsif LaserSingletonClass === arg_class && arg_class < ClassRegistry['Class']
210
- arg_class.get_instance.as_type
211
- elsif arg_class <= ClassRegistry['Exception']
212
- arg_class.as_type
213
- elsif arg_class.instance_method_defined?('exception')
214
- arg_class.instance_method(:exception).return_type_for_types(arg_class.as_type)
215
- end
216
- end)
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] = @instance_methods[oldsym]
353
- @visibility_table[newsym] = @visibility_table[oldsym]
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, file_name, line_number,
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(dispatches, args, block)
339
- result = cpa_for_templates(dispatches, cartesian)
340
- raise_result, raise_type = raisability_for_templates(dispatches, cartesian, ignore_privacy)
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(possible_dispatches, args, block)
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
- # Calculates the CPA-based return type of a dynamic call.
388
- def cpa_for_templates(possible_dispatches, cartesian)
389
- result = Set.new
390
- possible_dispatches.each do |self_type, methods|
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
- [raise_freq, raise_type]
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
- insn[1].bind! result if insn[1]
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 each_target_method(self_type, arg_type)
21
+ def all_target_methods(self_type, arg_type)
22
+ collection = Set[]
22
23
  arg_type.possible_classes.each do |target_klass|
23
- if LaserSingletonClass === target_klass
24
- target_method_name = target_klass.get_instance.to_s
25
- self_type.possible_classes.each do |self_class|
26
- if passes_visibility?(self_class, target_method_name)
27
- method = self_class.instance_method(target_method_name)
28
- method.been_used!
29
- yield(method)
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
- end
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 collect_type_from_targets(to_call, self_type, arg_types, block_type)
42
- result_type = Types::UnionType.new([])
43
- each_target_method(self_type, arg_types[0]) do |method|
44
- result_type |= method.send(to_call, self_type, arg_types[1..-1], block_type)
45
- end
46
- result_type
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
- collect_type_from_targets(:return_type_for_types, self_type, arg_types, block_type)
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
- collect_type_from_targets(:raise_type_for_types, self_type, arg_types, block_type)
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
- all_frequencies = []
59
- each_target_method(self_type, arg_types[0]) do |method|
60
- all_frequencies << method.raise_frequency_for_types(self_type, arg_types[1..-1], block_type)
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
@@ -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.index(only_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]
@@ -3,7 +3,7 @@ module Laser
3
3
  MAJOR = 0
4
4
  MINOR = 7
5
5
  PATCH = 0
6
- BUILD = 'pre1'
6
+ BUILD = 'pre2'
7
7
 
8
8
  if BUILD.empty?
9
9
  STRING = [MAJOR, MINOR, PATCH].compact.join('.')
@@ -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
- $all_types ||= Hash.new {|h,k| h[k] = []}
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
@@ -256,10 +256,11 @@ rescue RuntimeError
256
256
  2
257
257
  end
258
258
  EOF
259
+
259
260
  g.yield_type.should be :required
260
261
  g.yield_arity.should == Set[1]
261
262
  end
262
-
263
+
263
264
  it 'infers yield likelihood with to_proc block syntax' do
264
265
  cfg <<-EOF
265
266
  class YP1
@@ -955,7 +955,7 @@ end
955
955
  }
956
956
  end
957
957
 
958
- it 'correctly resolves many bindings, creates new modules and classes, and defines methods' do
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.pre1
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-12 00:00:00 Z
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: :development
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.3.0
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