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 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