Skip to content

Commit

Permalink
reworked frames management and longjumps
Browse files Browse the repository at this point in the history
  • Loading branch information
iliabylich committed Dec 24, 2019
1 parent f83d458 commit b9cd86a
Show file tree
Hide file tree
Showing 28 changed files with 308 additions and 228 deletions.
19 changes: 17 additions & 2 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
namespace :rubyspec do
env = 'PATCH_EVAL=1 DISABLE_TRACES=1 DISABLE_BREAKPOINTS=1'

def each_spec
Dir['./rubyspec/language/**/*_spec.rb'].sort.each do |f|
yield f
rescue Exception => e
puts "#{f} failed with #{e}"
end
end

task :run_and_record_failures do
sh 'DISABLE_BREAKPOINTS=1 bin/my.rb ./mspec/bin/mspec-tag ./rubyspec/language/ -- --int-spec'
sh 'rm -rf tags'
each_spec do |f|
sh "#{env} bin/my.rb ./mspec/bin/mspec-tag #{f} -- --int-spec"
end
end

task :run_passing do
sh 'DISABLE_BREAKPOINTS=1 ./mspec/bin/mspec -t bin/my.rb ./rubyspec/language/ -- --excl-tag=fails'
each_spec do |f|
sh "#{env} ./mspec/bin/mspec -t bin/my.rb #{f} -- --excl-tag=fails"
end
end
end
96 changes: 62 additions & 34 deletions bin/my.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ def self.run_instruction(iseq)
require 'readline'
require '/Users/ilya/.rvm/scripts/irbrc.rb'

Object.send(:remove_const, :UncaughtThrowError)
class UncaughtThrowError < ArgumentError
attr_reader :tag, :value

def initialize(tag, value)
@tag = tag
@value = value
end
end

[Kernel, Kernel.singleton_class].each do |mod|
mod.module_eval do
alias original_require require
Expand Down Expand Up @@ -123,49 +133,67 @@ def require_relative(filepath)
binding.irb
end

def eval(code)
verbose = $VERBOSE
$VERBOSE = false
iseq = RubyVM::InstructionSequence.compile(code).to_a
$VERBOSE = true
iseq[9] = :eval
VM.instance.execute(iseq, _self: self)
ensure
$VERBOSE = verbose
if ENV['PATCH_EVAL']
def eval(code)
verbose = $VERBOSE
$VERBOSE = false
iseq = RubyVM::InstructionSequence.compile(code).to_a
$VERBOSE = true
iseq[9] = :eval
VM.instance.execute(iseq, _self: self)
ensure
$VERBOSE = verbose
end
end
end
end

class BasicObject
def instance_eval(code = nil, &block)
if code
iseq = ::RubyVM::InstructionSequence.compile(code).to_a
iseq[9] = :eval
::VM.instance.execute(iseq, _self: self)
else
::RubyRb::REAL_INSTANCE_EVAL.bind(self).call(&block)
def throw(tag, value = nil)
raise UncaughtThrowError.new(tag, value)
end

def catch(tag)
yield
rescue UncaughtThrowError => e
if e.tag == tag
return e.value
else
raise
end
end
end
end

class Module
def module_eval(code = nil, &block)
if code
iseq = ::RubyVM::InstructionSequence.compile(code).to_a
iseq[9] = :eval
::VM.instance.execute(iseq, _self: self)
else
::RubyRb::REAL_MODULE_EVAL.bind(self).call(&block)
if ENV['PATCH_EVAL']
class BasicObject
def instance_eval(code = nil, &block)
if code
iseq = ::RubyVM::InstructionSequence.compile(code).to_a
iseq[9] = :eval
::VM.instance.execute(iseq, _self: self)
else
::RubyRb::REAL_INSTANCE_EVAL.bind(self).call(&block)
end
end
end

def class_eval(code = nil, &block)
if code
iseq = ::RubyVM::InstructionSequence.compile(code).to_a
iseq[9] = :eval
::VM.instance.execute(iseq, _self: self)
else
::RubyRb::REAL_CLASS_EVAL.bind(self).call(&block)
class Module
def module_eval(code = nil, &block)
if code
iseq = ::RubyVM::InstructionSequence.compile(code).to_a
iseq[9] = :eval
::VM.instance.execute(iseq, _self: self)
else
::RubyRb::REAL_MODULE_EVAL.bind(self).call(&block)
end
end

def class_eval(code = nil, &block)
if code
iseq = ::RubyVM::InstructionSequence.compile(code).to_a
iseq[9] = :eval
::VM.instance.execute(iseq, _self: self)
else
::RubyRb::REAL_CLASS_EVAL.bind(self).call(&block)
end
end
end
end
Expand Down
2 changes: 2 additions & 0 deletions default.mspec
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ class MSpecScript
[%r(rubyspec/), 'tags/'],
[/_spec.rb$/, '_tags.txt']
]

set :backtrace_filter, /(lib\/mspec\|compile-rb)/
end
25 changes: 8 additions & 17 deletions executor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ def execute_send((options, _flag, block_iseq))
if block_iseq
original_block_frame = self.current_frame
proc do |*args, &block|

VM.instance.execute(
block_iseq,
block_args: args,
Expand Down Expand Up @@ -240,7 +241,7 @@ def execute_putspecialobject((type))
when 3
push(:VM_SPECIAL_OBJECT_CONST_BASE)
else
raise VM::InternalError, 'dead'
raise VM::InternalError, "unknown specialobject type #{type}"
end
end

Expand Down Expand Up @@ -1004,22 +1005,12 @@ def execute_throw((throw_state))
if state != 0
# throw start
case state
when 1
# return
when RUBY_TAG_RETURN
raise VM::ReturnError, throw_obj
when 3
# next inside rescue/ensure, inside pop_frame,
# so current_frame is about to die

frame = current_frame

until frame.can_do_next?
frame.exit!(:__unused)
frame = frame.parent_frame
end

frame.returning = throw_obj
frame.exit!(throw_obj)
when RUBY_TAG_BREAK
raise VM::BreakError, throw_obj
when RUBY_TAG_NEXT
raise VM::NextError, throw_obj
else
binding.irb
end
Expand Down Expand Up @@ -1099,8 +1090,8 @@ def execute_setspecial((key))
end

def execute_opt_or(_)
lhs = pop
rhs = pop
lhs = pop
push(lhs | rhs)
end

Expand Down
1 change: 1 addition & 0 deletions spec/vm_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ def test
ensure
p 3
end
p 4
RUBY

assert_evaluates_like_mri(<<-RUBY)
Expand Down
3 changes: 0 additions & 3 deletions tags/language/block_tags.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ fails:A block yielded a single Array when non-symbol keys are in a keyword argum
fails:A block yielded a single Object calls #to_ary on the object when taking multiple arguments
fails:A block yielded a single Object receives the object if #to_ary returns nil
fails:A block yielded a single Object raises a TypeError if #to_ary does not return an Array
fails:A block taking zero arguments may include a rescue clause
fails:A block taking || arguments may include a rescue clause
fails:A block taking |a| arguments assigns the first value yielded to the argument
fails:A block taking |a| arguments may include a rescue clause
fails:A block taking |a, b| arguments destructures a splatted Array
fails:A block taking |a, b| arguments calls #to_ary to convert a single yielded object to an Array
fails:A block taking |a, b| arguments raises a TypeError if #to_ary does not return an Array
Expand Down
14 changes: 0 additions & 14 deletions tags/language/break_tags.txt
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
fails:The break statement in a block returns nil to method invoking the method yielding to the block when not passed an argument
fails:The break statement in a block returns a value to the method invoking the method yielding to the block
fails:The break statement in a block yielded inside a while breaks out of the block
fails:The break statement in a block captured and delegated to another method repeatedly breaks out of the block
fails:The break statement in a captured block when the invocation of the scope creating the block is still active raises a LocalJumpError when invoking the block from the scope creating the block
fails:The break statement in a captured block when the invocation of the scope creating the block is still active raises a LocalJumpError when invoking the block from a method
fails:The break statement in a captured block when the invocation of the scope creating the block is still active raises a LocalJumpError when yielding to the block
fails:The break statement in a captured block from a scope that has returned raises a LocalJumpError when calling the block from a method
fails:The break statement in a captured block from a scope that has returned raises a LocalJumpError when yielding to the block
fails:The break statement in a captured block from another thread raises a LocalJumpError when getting the value from another thread
fails:The break statement in a lambda returns from the lambda
fails:The break statement in a lambda returns from the call site if the lambda is passed as a block
fails:The break statement in a lambda when the invocation of the scope creating the lambda is still active returns nil when not passed an argument
fails:The break statement in a lambda when the invocation of the scope creating the lambda is still active returns a value to the scope creating and calling the lambda
fails:The break statement in a lambda when the invocation of the scope creating the lambda is still active returns a value to the method scope below invoking the lambda
fails:The break statement in a lambda when the invocation of the scope creating the lambda is still active returns a value to a block scope invoking the lambda in a method below
fails:The break statement in a lambda when the invocation of the scope creating the lambda is still active returns from the lambda
fails:The break statement in a lambda from a scope that has returned returns a value to the method scope invoking the lambda
fails:The break statement in a lambda from a scope that has returned returns a value to the block scope invoking the lambda in a method
fails:The break statement in a lambda from a scope that has returned raises a LocalJumpError when yielding to a lambda passed as a block argument
fails:Break inside a while loop causes a call with a block to return when run
fails:Break inside a while loop with a value passes the value returned by a method with omitted parenthesis and passed block
fails:Executing break from within a block returns from the original invoking method even in case of chained calls
fails:Executing break from within a block runs ensures when continuing upward
fails:Executing break from within a block doesn't run ensures in the destination method
2 changes: 2 additions & 0 deletions tags/language/constants_tags.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ fails:Module#private_constant marked constants in Object cannot be accessed usin
fails:Module#private_constant marked constants in Object is not defined? using ::Const form
fails:Module#private_constant marked constants NameError by #private_constant has :receiver and :name attributes
fails:Module#private_constant marked constants NameError by #private_constant has the defined class as the :name attribute
fails:Module#public_constant marked constants in a module is defined? with A::B form
fails:Module#public_constant marked constants in a class is defined? with A::B form
22 changes: 20 additions & 2 deletions tags/language/defined_tags.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
fails:The defined? keyword when called with a method name having a method call as a receiver returns nil if evaluating the receiver raises an exception
fails:The defined? keyword for an expression with logical connectives does not propagate an exception raised by a method in a 'not' expression
fails:The defined? keyword for an expression with logical connectives returns nil for an expression with '!' and an unset global variable
fails:The defined? keyword for an expression with logical connectives returns nil for an expression with 'not' and an unset global variable
fails:The defined? keyword for variables returns nil for a global variable that has not been read
fails:The defined? keyword for variables returns nil for a global variable that has been read but not assigned to
fails:The defined? keyword for variables when a String matches a Regexp returns 'global-variable' for $&
Expand All @@ -12,6 +12,24 @@ fails:The defined? keyword for variables when a Regexp matches a String returns
fails:The defined? keyword for variables when a Regexp matches a String returns 'global-variable' for $'
fails:The defined? keyword for variables when a Regexp matches a String returns 'global-variable' for $+
fails:The defined? keyword for variables when a Regexp matches a String returns 'global-variable' for the capture references
fails:The defined? keyword for a scoped constant returns 'constant' when the scoped constant is defined
fails:The defined? keyword for a scoped constant returns nil when a constant is defined on top-level but not on the module
fails:The defined? keyword for a scoped constant returns 'constant' if the scoped-scoped constant is defined
fails:The defined? keyword for a top-level scoped constant returns 'constant' when the scoped constant is defined
fails:The defined? keyword for a top-level scoped constant returns 'constant' if the scoped-scoped constant is defined
fails:The defined? keyword for a self-send method call scoped constant returns 'constant' if the constant is defined in the scope of the method's value
fails:The defined? keyword for a self-send method call scoped constant returns 'constant' if all the constants in the scope chain are defined
fails:The defined? keyword for a receiver method call scoped constant returns 'constant' if the constant is defined in the scope of the method's value
fails:The defined? keyword for a receiver method call scoped constant returns 'constant' if all the constants in the scope chain are defined
fails:The defined? keyword for a module method call scoped constant returns 'constant' if the constant scoped by the method's value is defined
fails:The defined? keyword for a module method call scoped constant returns 'constant' if all the constants in the scope chain are defined
fails:The defined? keyword for a module method call scoped constant returns 'constant' if all the constants in the receiver are defined
fails:The defined? keyword for a module method call scoped constant returns 'constant' if all the constants in the receiver and scope chain are defined
fails:The defined? keyword for a variable scoped constant returns 'constant' if the constant is defined in the scope of the instance variable
fails:The defined? keyword for a variable scoped constant returns 'constant' if the constant is defined in the scope of the global variable
fails:The defined? keyword for a variable scoped constant returns 'constant' if the constant is defined in the scope of the class variable
fails:The defined? keyword for a variable scoped constant returns 'constant' if the constant is defined in the scope of the local variable
fails:The defined? keyword for a self:: scoped constant returns 'constant' for a constant explicitly scoped to self:: when set
fails:The defined? keyword for super returns nil when a superclass undef's the method
fails:The defined? keyword for super for a method taking no arguments returns 'super' from a block when a superclass method exists
fails:The defined? keyword for super for a method taking no arguments returns 'super' from a #define_method when a superclass method exists
Expand Down
7 changes: 4 additions & 3 deletions tags/language/ensure_tags.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
fails:An ensure block inside a begin block is executed when an exception is raised and rescued in it's corresponding begin block
fails:An ensure block inside a begin block is executed when an exception is raised in it's corresponding begin block
fails:An ensure block inside a begin block is executed even when a symbol is thrown in it's corresponding begin block
fails:An ensure block inside a begin block sets exception cause if raises exception in block and in ensure
fails:The value of an ensure expression, when an exception is rescued, is the value of the rescuing block
fails:An ensure block inside a method is executed when an exception is raised in the method
fails:An ensure block inside a method is executed even when a symbol is thrown in the method
fails:An ensure block inside a method has an impact on the method's explicit return value from rescue if returns explicitly
fails:An ensure block inside a method has no impact on the method's explicit return value from rescue if returns implicitly
fails:An ensure block inside a method suppresses exception raised in method if returns value explicitly
fails:An ensure block inside a method suppresses exception raised in rescue if returns value explicitly
fails:An ensure block inside a method overrides exception raised in rescue if raises exception itself
fails:An ensure block inside a method suppresses exception raised in method if raises exception itself
fails:An ensure block inside a class is executed when an exception is raised
fails:An ensure block inside a class is executed even when a symbol is thrown
fails:An ensure block inside 'do end' block is executed when an exception is raised in it's corresponding begin block
fails:An ensure block inside 'do end' block is executed even when a symbol is thrown in it's corresponding begin block
1 change: 0 additions & 1 deletion tags/language/for_tags.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
fails:The for expression breaks out of a loop upon 'break', returning nil
fails:The for expression allows 'break' to have an argument which becomes the value of the for expression
6 changes: 0 additions & 6 deletions tags/language/loop_tags.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
fails:The loop expression repeats the given block until a break is called
fails:The loop expression executes code in its own scope
fails:The loop expression returns the value passed to break if interrupted by break
fails:The loop expression returns nil if interrupted by break with no arguments
fails:The loop expression skips to end of body with next
fails:The loop expression restarts the current iteration with redo
fails:The loop expression uses a spaghetti nightmare of redo, next and break
1 change: 1 addition & 0 deletions tags/language/method_tags.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,4 @@ fails:A method assigns local variables from method parameters for definition 'de
fails:A method assigns local variables from method parameters for definition 'def m(a:, **k) [a, k] end'
fails:"A method assigns local variables from method parameters for definition \n def m(a, b=1, *c, d, e:, f: 2, g:, **k, &l)\n [a, b, c, d, e, f, g, k, l]\n end"
fails:"A method assigns local variables from method parameters for definition \n def m(a, b = nil, c = nil, d, e: nil, **f)\n [a, b, c, d, e, f]\n end"
fails:A method assigns keyword arguments from a passed Hash without modifying it for definition 'def m(a: nil); a; end'
7 changes: 0 additions & 7 deletions tags/language/next_tags.txt

This file was deleted.

1 change: 0 additions & 1 deletion tags/language/or_tags.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
fails:The || operator has a higher precedence than 'break' in 'break true || false'
fails:The || operator has a higher precedence than 'return' in 'return true || false'
3 changes: 0 additions & 3 deletions tags/language/precedence_tags.txt

This file was deleted.

Loading

0 comments on commit b9cd86a

Please sign in to comment.