Skip to content

Commit

Permalink
Fixed conflicting tests for yield analysis.
Browse files Browse the repository at this point in the history
Since the new 'warn about overriding block_given?' tests introduce a class which
overrides block_given?, the yield tests were noticing the existence of a block_given?
which was not Kernel#block_given?. Since the old yield tests were methods on Object,
and not on a particular class, they had to decide each method was block-optional.

By isolating each test in a class not equal to the ones introduced in the
override-warning specs, the yield tests no longer fail.
  • Loading branch information
Michael Edgar committed Aug 22, 2011
1 parent 8d46a07 commit 18f028d
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 107 deletions.
2 changes: 2 additions & 0 deletions lib/laser/analysis/bootstrap/bootstrap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -264,10 +264,12 @@ def self.load_standard_library
end
end
rescue StandardError => err
require 'pp'
puts "Loading class definitions failed:"
p err.message
pp err
pp err.backtrace
raise
end
end
# All methods from here on out will need to be used, or a warning will be issued.
Expand Down
2 changes: 1 addition & 1 deletion lib/laser/analysis/bootstrap/laser_method.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def verify_override_safety
return unless overridden
if OverrideSafetyInfo.warn_on_any_override?(overridden)
@proc.ast_node.add_error(DangerousOverrideError.new(
OverrideSafetyInfo.warning_message(overridden),
OverrideSafetyInfo.warning_message(overridden, name),
@proc.ast_node))
end
end
Expand Down
6 changes: 5 additions & 1 deletion lib/laser/analysis/control_flow/control_flow_graph.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ def initialize(formal_arguments = [])
@name_count = Hash.new { |hash, temp| hash[temp] = 0 }
@formals = formal_arguments
@yield_type = nil
@yield_arity = nil#Set.new([Arity::ANY])
@yield_arity = nil
@raise_frequency = Frequency::MAYBE
@analyzed = false
super()
end

Expand Down Expand Up @@ -210,6 +211,9 @@ def bind_block_type(new_block_type)
DEFAULT_ANALYSIS_OPTS = {optimize: true, simulate: true}
# Runs full analysis on the CFG. Puts it in SSA then searches for warnings.
def analyze(opts={})
return if @analyzed
@analyzed = true

opts = DEFAULT_ANALYSIS_OPTS.merge(opts)
# kill obvious dead code now.
perform_dead_code_discovery(true)
Expand Down
27 changes: 21 additions & 6 deletions lib/laser/analysis/override_safety_info.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,36 @@ def self.warn_on_any_override?(method)
do_not_override.include?(method)
end

def self.warning_message(method)
do_not_override[method]
# Returns the warning message when +method+ is overridden. Since
# we may actually be overriding an alias, we need to provide the
# name of the override, and substitute it into the message.
def self.warning_message(method, overridden_name)
do_not_override[method] % overridden_name
end

def self.do_not_override
return @do_not_override if @do_not_override
result = Hash[
ClassRegistry['Module'].instance_method(:public),
'Overriding Module#public breaks its zero-argument lexically-scoped behavior.',
'Overriding Module#%s breaks its zero-argument lexically-scoped behavior.',
ClassRegistry['Module'].instance_method(:private),
'Overriding Module#private breaks its zero-argument lexically-scoped behavior.',
'Overriding Module#%s breaks its zero-argument lexically-scoped behavior.',
ClassRegistry['Module'].instance_method(:protected),
'Overriding Module#protected breaks its zero-argument lexically-scoped behavior.',
'Overriding Module#%s breaks its zero-argument lexically-scoped behavior.',
ClassRegistry['Module'].instance_method(:module_function),
'Overriding Module#module_function breaks its zero-argument lexically-scoped behavior.'
'Overriding Module#%s breaks its zero-argument lexically-scoped behavior.',
ClassRegistry['Kernel'].instance_method(:block_given?),
'Overriding Kernel#%s irreparably breaks the method.',
ClassRegistry['Kernel'].instance_method(:binding),
'Overriding Kernel#%s irreparably breaks the method.',
ClassRegistry['Kernel'].instance_method(:callcc),
'Overriding Kernel#%s may give highly surprising results.',
ClassRegistry['Kernel'].instance_method(:caller),
'Overriding Kernel#%s may work, but must be done very carefully.',
ClassRegistry['Kernel'].instance_method(:__method__),
'Overriding Kernel#%s irreparably breaks the method.',
ClassRegistry['Kernel'].instance_method(:local_variables),
'Overriding Kernel#%s irreparably breaks the method.',
]
unless result[nil] # if all keys existed
@do_not_override = result
Expand Down
83 changes: 2 additions & 81 deletions lib/laser/standard_library/class_definitions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -163,93 +163,14 @@ def module_function(*args)
def alias_method(to, from)
end
end

module Kernel
# pure: true
# builtin: true
# raises: never
# returns: Boolean
def eql?(other)
end
# pure: true
# builtin: true
# raises: never
# returns: Boolean
def equal?(other)
end
# builtin: true
# klass: Module
# raises: never
def is_a?(klass)
end
alias kind_of? is_a?
# pure: true
# raises: never
# builtin: true
def singleton_class
end
# pure: true
# raises: never
# builtin: true
# returns: Class=
def class
end
# pure: true
# raises: never
def inspect
end
# builtin: true
def instance_variable_get(name)
end
# builtin: true
def instance_variable_defined?(name)
end
# builtin: true
def instance_variable_set(name, val)
end
private
# yield_usage: required
def proc
unless block_given?
raise ArgumentError.new('tried to create Proc object without a block')
end
Proc.new
end
# special: true
def require(path)
end
# raises: never
def p(*args)
args
end
def eval(string, bndg = nil, filename = nil, lineno = nil)
end
def autoload(sym, path)
end
alias fail raise
# predictable: false
# returns: String=
# raises: maybe
def gets(opt_arg_1 = :__unset__, opt_arg_2 = :__unset__)
end
# predictable: false
def puts(*to_put)
end
# returns: Boolean
# raises: never
def block_given?
end
alias iterator? block_given?
# builtin: true
# raises: never
# pure: false
# predictable: false
# overload: () -> Float
# overload: Fixnum= -> Fixnum= | Bignum=
# overload: Bignum= -> Fixnum= | Bignum=
def rand(n=nil)
end
end

require 'kernel'
require 'basic_object'
require 'nil_false_true'
require 'exceptions'
Expand Down
115 changes: 115 additions & 0 deletions lib/laser/standard_library/kernel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
module Kernel
# pure: true
# builtin: true
# raises: never
# returns: Boolean
def eql?(other)
end
# pure: true
# builtin: true
# raises: never
# returns: Boolean
def equal?(other)
end
# builtin: true
# klass: Module
# raises: never
def is_a?(klass)
end
alias kind_of? is_a?
# pure: true
# raises: never
# builtin: true
def singleton_class
end
# pure: true
# raises: never
# builtin: true
# returns: Class=
def class
end
# pure: true
# raises: never
def inspect
end
# builtin: true
def instance_variable_get(name)
end
# builtin: true
def instance_variable_defined?(name)
end
# builtin: true
def instance_variable_set(name, val)
end
private


# raises: never
# returns: Symbol= | NilClass=
# builtin: true
# special: true
def __method__
end
alias __callee__ __method__

# raises: never
# returns: Binding=
# builtin: true
# special: true
def binding
end

# raises: never
# returns: Array=
# builtin: true
# special: true
def caller
end

# raises: never
# returns: Continuation=
# builtin: true
# special: true
def callcc
end

# yield_usage: required
def proc
unless block_given?
raise ArgumentError.new('tried to create Proc object without a block')
end
Proc.new
end

# raises: never
def p(*args)
args
end
def eval(string, bndg = nil, filename = nil, lineno = nil)
end
def autoload(sym, path)
end
alias fail raise
# predictable: false
# returns: String=
# raises: maybe
def gets(opt_arg_1 = :__unset__, opt_arg_2 = :__unset__)
end
# predictable: false
def puts(*to_put)
end
# returns: Boolean
# raises: never
def block_given?
end
alias iterator? block_given?
# builtin: true
# raises: never
# pure: false
# predictable: false
# overload: () -> Float
# overload: Fixnum= -> Fixnum= | Bignum=
# overload: Bignum= -> Fixnum= | Bignum=
def rand(n=nil)
end
end
16 changes: 15 additions & 1 deletion spec/analysis_specs/control_flow_specs/improper_override_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,21 @@ def self.#{method}(*args)
end
EOF
ClassRegistry["OverI6"].singleton_class.instance_method(method).proc.ast_node.should(
have_error(DangerousOverrideError))
have_error(DangerousOverrideError).with_message(/#{method}/))
end
end

%w(block_given? iterator? binding callcc caller __method__ __callee__).each do |method|
it "should warn when the visibility method Kernel##{method} is overridden" do
g = cfg <<-EOF
class OverI7
def #{method}(*args)
super
end
end
EOF
ClassRegistry["OverI7"].instance_method(method).proc.ast_node.should(
have_error(DangerousOverrideError).with_message(/#{method}/))
end
end
end
42 changes: 25 additions & 17 deletions spec/analysis_specs/control_flow_specs/yield_properties_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,33 +52,41 @@ def one
g.yield_type.should be :required
g.yield_arity.should == Set[0]
end
['block_given?', 'defined?(yield)', 'defined?(yield($., *$*))', 'Proc.new', 'iterator?'].each do |guard|
['block_given?', 'defined?(yield)', 'defined?(yield($., *$*))', 'Proc.new', 'iterator?'].each_with_index do |guard, idx|
opt_class = "YP#{100 + idx}"
it "denotes the method optional when yield is guarded by #{guard}" do
g = cfg_method <<-EOF
def one
if #{guard}
yield 1
else
1
cfg <<-EOF
class #{opt_class}
def one
if #{guard}
yield 1
else
1
end
end
end
EOF
g.yield_type.should be :optional
g.yield_arity.should == Set[1]
method = ClassRegistry[opt_class].instance_method(:one)
method.yield_type.should be :optional
method.yield_arity.should == Set[1]
end

fool_class = "YP#{120 + idx}"
it "denotes the method foolish when yield is not guarded by #{guard}, but the block is unused when given" do
g = cfg_method <<-EOF
def one
if #{guard}
1
else
yield 1
g = cfg <<-EOF
class #{fool_class}
def one
if #{guard}
1
else
yield 1
end
end
end
EOF
g.yield_type.should be :foolish
g.yield_arity.should == Set[]
method = ClassRegistry[fool_class].instance_method(:one)
method.yield_type.should be :foolish
method.yield_arity.should == Set[]
end
end

Expand Down

0 comments on commit 18f028d

Please sign in to comment.