core/exception.rb
class Exception
attr_accessor :locations
attr_accessor :cause
attr_accessor :custom_backtrace
def initialize(message = nil)
@reason_message = message
@locations = nil
@backtrace = nil
@custom_backtrace = nil
end
private :initialize
def capture_backtrace!(offset=1)
@locations = Rubinius::VM.backtrace offset
end
def ==(other)
other.instance_of?(__class__) &&
message == other.message &&
backtrace == other.backtrace
end
def to_s
if @reason_message
@reason_message.to_s
else
self.class.to_s
end
end
# This is here rather than in yaml.rb because it contains "private"
# information, ie, the list of ivars. Putting it over in the yaml
# source means it's easy to forget about.
def to_yaml_properties
list = super
list.delete :@backtrace
list.delete :@custom_backtrace
return list
end
def message
@reason_message
end
# Needed to properly implement #exception, which must clone and call
# #initialize again, BUT not a subclasses initialize.
alias_method :__initialize__, :initialize
def backtrace
return @custom_backtrace if @custom_backtrace
if backtrace?
awesome_backtrace.to_mri
else
nil
end
end
# Indicates if the Exception has a backtrace set
def backtrace?
(@backtrace || @locations) ? true : false
end
def awesome_backtrace
@backtrace ||= Rubinius::Backtrace.backtrace(@locations)
end
def render(header="An exception occurred", io=STDERR, color=true)
exception = self
exceptions = []
while exception
exceptions.unshift exception
exception = exception.cause
end
while exception = exceptions.shift
if custom_trace = exception.custom_backtrace
io.puts "User-defined backtrace:", "\n"
custom_trace.reverse_each { |line| io.puts line }
io.puts "\nBacktrace:", "\n"
end
io.puts exception.awesome_backtrace.show("\n", color)
io.puts "\n#{exception.message} (#{exception.class})"
io.puts "\nCausing:", "\n" unless exceptions.empty?
end
message_lines = message.to_s.split("\n")
io.puts header
io.puts
io.puts " #{message_lines.shift} (#{self.class})"
message_lines.each do |line|
io.puts " #{line}"
end
end
def set_backtrace(bt)
if bt.kind_of? Rubinius::Backtrace
@backtrace = bt
else
# See if we stashed a Backtrace object away, and use it.
if hidden_bt = Rubinius::Backtrace.detect_backtrace(bt)
@backtrace = hidden_bt
else
type_error = TypeError.new "backtrace must be Array of String"
case bt
when Array
if bt.all? { |s| s.kind_of? String }
@custom_backtrace = bt
else
raise type_error
end
when String
@custom_backtrace = [bt]
when nil
@custom_backtrace = nil
else
raise type_error
end
end
end
end
# This is important, because I subclass can just override #to_s and calling
# #message will call it. Using an alias doesn't achieve that.
def message
to_s
end
def inspect
s = self.to_s
if s.empty?
self.class.name
else
"#<#{self.class.name}: #{s}>"
end
end
class << self
alias_method :exception, :new
end
def exception(message=nil)
if message
unless message.equal? self
# As strange as this might seem, this IS actually the protocol
# that MRI implements for this. The explicit call to
# Exception#initialize (via __initialize__) is exactly what MRI
# does.
e = clone
Rubinius.privately { e.__initialize__(message) }
return e
end
end
self
end
def location
[context.file.to_s, context.line]
end
end
class PrimitiveFailure < Exception
end
class ScriptError < Exception
end
class StandardError < Exception
end
class SignalException < Exception
end
class NoMemoryError < Exception
end
class ZeroDivisionError < StandardError
end
class ArgumentError < StandardError
def to_s
if @given and @expected
if @method_name
"method '#{@method_name}': given #{@given}, expected #{@expected}"
else
"given #{@given}, expected #{@expected}"
end
else
super
end
end
end
class UncaughtThrowError < ArgumentError
attr_accessor :tag
end
class IndexError < StandardError
end
class StopIteration < IndexError
end
class RangeError < StandardError
end
class FloatDomainError < RangeError
end
class LocalJumpError < StandardError
end
class NameError < StandardError
attr_reader :name
def initialize(*args, receiver: nil)
super(args.shift)
@name = args.shift
@receiver = receiver
end
private :initialize
def receiver
if @receiver
@receiver
else
raise ArgumentError, 'no receiver is available'
end
end
end
class NoMethodError < NameError
attr_reader :name
attr_reader :args
def initialize(*arguments, **options)
super(arguments.shift, **options)
@name = arguments.shift
@args = arguments.shift
end
private :initialize
end
class RuntimeError < StandardError
end
class SecurityError < StandardError
end
class ThreadError < StandardError
end
class FiberError < StandardError
end
class TypeError < StandardError
end
class FloatDomainError < RangeError
end
class RegexpError < StandardError
end
class LoadError < ScriptError
attr_accessor :path
def initialize(message=nil, path: nil)
super message
@path = path
end
private :initialize
class InvalidExtensionError < LoadError
end
class MRIExtensionError < InvalidExtensionError
end
end
class NotImplementedError < ScriptError
end
class Interrupt < SignalException
def initialize(*args)
super(args.shift)
@name = args.shift
end
private :initialize
end
class IOError < StandardError
end
class EOFError < IOError
end
class LocalJumpError < StandardError
end
class SyntaxError < ScriptError
attr_accessor :column
attr_accessor :line
attr_accessor :file
attr_accessor :code
def self.from(message, column, line, code, file)
exc = new message
exc.file = file
exc.line = line
exc.column = column
exc.code = code
exc
end
def reason
@reason_message
end
end
class SystemExit < Exception
##
# Process exit status if this exception is raised
attr_reader :status
##
# Creates a SystemExit exception with optional status and message. If the
# status is omitted, Process::EXIT_SUCCESS is used.
#--
# *args is used to simulate optional prepended argument like MRI
def initialize(first=nil, *args)
if first.kind_of?(Fixnum)
status = first
super(*args)
else
status = Process::EXIT_SUCCESS
super
end
@status = status
end
private :initialize
##
# Returns true is exiting successfully, false if not. A successful exit is
# one with a status equal to 0 (zero). Any other status is considered a
# unsuccessful exit.
def success?
status == Process::EXIT_SUCCESS
end
end
class SystemCallError < StandardError
attr_reader :errno
def self.errno_error(message, errno, location)
Rubinius.primitive :exception_errno_error
raise PrimitiveFailure, "SystemCallError.errno_error failed"
end
# We use .new here because when errno is set, we attempt to
# lookup and return a subclass of SystemCallError, specificly,
# one of the Errno subclasses.
def self.new(*args)
# This method is used 2 completely different ways. One is when it's called
# on SystemCallError, in which case it tries to construct a Errno subclass
# or makes a generic instead of itself.
#
# Otherwise it's called on a Errno subclass and just helps setup
# a instance of the subclass
if self.equal? SystemCallError
case args.size
when 1
if args.first.kind_of?(Fixnum)
errno = args.first
message = nil
else
errno = nil
message = StringValue(args.first)
end
location = nil
when 2
message, errno = args
location = nil
when 3
message, errno, location = args
else
raise ArgumentError, "wrong number of arguments (#{args.size} for 1..3)"
end
# If it corresponds to a known Errno class, create and return it now
if errno && error = SystemCallError.errno_error(message, errno, location)
return error
else
return super(message, errno, location)
end
else
case args.size
when 0
message = nil
location = nil
when 1
message = StringValue(args.first)
location = nil
when 2
message, location = args
else
raise ArgumentError, "wrong number of arguments (#{args.size} for 0..2)"
end
if defined?(self::Errno) && self::Errno.kind_of?(Fixnum)
errno = self::Errno
error = SystemCallError.errno_error(message, self::Errno, location)
if error && error.class.equal?(self)
return error
end
end
error = allocate
Rubinius::Unsafe.set_class error, self
Rubinius.privately { error.initialize(*args) }
return error
end
end
# Must do this here because we have a unique new and otherwise .exception will
# call Exception.new because of the alias in Exception.
class << self
alias_method :exception, :new
end
# Use splat args here so that arity returns -1 to match MRI.
def initialize(*args)
kls = self.class
message, errno, location = args
@errno = errno
msg = "unknown error"
msg << " @ #{StringValue(location)}" if location
msg << " - #{StringValue(message)}" if message
super(msg)
end
private :initialize
end
class KeyError < IndexError
end
class SignalException < Exception
attr_reader :signo
attr_reader :signm
def initialize(signo = nil, signm = nil)
# MRI overrides this behavior just for SignalException itself
# but not for anything that inherits from it, therefore we
# need this ugly check to make sure it works as intented.
return super(signo) unless self.class == SignalException
if signo.is_a? Integer
unless @signm = Signal::Numbers[signo]
raise ArgumentError, "invalid signal number #{signo}"
end
@signo = signo
@signm = signm || "SIG#{@signm}"
elsif signo
if signm
raise ArgumentError, "wrong number of arguments (2 for 1)"
end
signm = signo
if signo.kind_of?(Symbol)
signm = signm.to_s
else
signm = StringValue(signm)
end
signm = signm[3..-1] if signm.prefix? "SIG"
unless @signo = Signal::Names[signm]
raise ArgumentError, "invalid signal name #{signm}"
end
@signm = "SIG#{signm}"
end
super(@signm)
end
private :initialize
end
class StopIteration
attr_accessor :result
private :result=
end
##
# Base class for various exceptions raised in the VM.
class Rubinius::MachineException < Exception
end
class Rubinius::ConcurrentUpdateError < Rubinius::MachineException
end
class Rubinius::VMException < Exception
end
##
# Raised in the VM when an assertion fails.
class Rubinius::AssertionError < Rubinius::VMException
end
##
# Raised in the VM when attempting to read/write outside
# the bounds of an object.
class Rubinius::ObjectBoundsExceededError < Rubinius::VMException
end
# Defined by the VM itself
class Rubinius::InvalidBytecode < Rubinius::Internal
attr_reader :compiled_code
attr_reader :ip
def message
if @compiled_code
if @ip and @ip >= 0
"#{super} - at #{@compiled_code.name}+#{@ip}"
else
"#{super} - method #{@compiled_code.name}"
end
else
super
end
end
end
class InterpreterError < Exception
end
class DeadlockError < Exception
end
# MRI has an Exception class named "fatal" that is raised
# by the rb_fatal function. The class is not accessible from
# ruby because the name is begins with a lower-case letter.
# Also, the exception cannot be rescued.
#
# To support rb_fatal in the C-API, Rubinius provides the
# following FatalError class. If it clashes with code in
# the wild, we can rename it.
class FatalError < Exception
end