Skip to content

Exceptions and Throw

datanorris edited this page Jul 3, 2016 · 9 revisions

This page describes the Exception class and some of its subclasses, and how exception objects are manipulated in Ruby, and the throw/catch feature (which does not employ Exception objects at all).

For details on how exceptions can be caught, see Ruby Expressions.

The Exception class

Exception is the root class of all exceptions in Ruby - only instances of Exception and its subclasses can be raised as exceptions. Exception objects store the following state:

  • A message, which describes the exception (e.g. "invalid value for x: y"), accessed by the message, to_s and == methods and modified by the initialize constructor method
  • A backtrace, accessed by the backtrace method and modified by the set_backtrace methods

This Exception object state (and, in general, additional state stored by Exception subclasses defined by core/extension libraries) is stored in the object as special hidden instance variables - they can't be directly accessed in Ruby code, however they do get copied in cloning/duping operations.

Ruby generally does not access the state directly, rather it uses the accessor/mutator methods - you are not required to store the state using the default native facilities, and may override these methods to access and store state how and if you wish as long as you observe the method contracts. However, note how by default Exception#exception directly modifies internal state as described below.

Message

The message method is expected to return a String instance, or an object that can be converted to one via to_str. It is used by Ruby (along with class name, the backtrace method, and perhaps other VM information) when printing out details of an unhandled exception that caused program termination.

Backtrace

The backtrace method is expected to return one of the following:

  • An array of Strings, or String-convertible objects via to_str, that each describe a level of the Ruby stack in the backtrace from the innermost level outwards
  • A single String or String-convertible object, which Ruby will treat as being wrapped in an Array.
  • nil, which means no backtrace is associated with this object

The set_backtrace method is expected to store a backtrace object which can be subsequently accessed by the backtrace method. When the Ruby VM invokes set_backtrace it passes a valid backtrace object (i.e. an array of Strings).

Ruby code can build its own backtrace via Thread#backtrace (note totally unrelated to Exception#backtrace).

Raising exceptions

Ruby implements an unusual pattern for raising exceptions. Methods for raising exceptions, such as Kernel#raise, don't directly accept an Exception object to be thrown - they accept a template object from which an exception will be provided, by calling the template's exception method. This feature is basically here to permit some syntactic conveniences as shown below.

Raise methods

Ruby methods for raising exceptions (such as Kernel#raise, Kernel#fail, Thread#raise) are composed of 2 phases:

  • Create the exception
  • Raise the exception

In the case of Thread#raise, the exception is created, but isn't raised directly - it will be raised by the targeted thread.

All of these methods may be invoked with arguments as follows:

raise
raise <string>
raise <template>, [ <argument> ], [ <backtrace> ]

When an exception object is created, it typically doesn't have a backtrace associated with it. When Ruby raises an exception object, the following occurs:

  • If instructed to attach a specific backtrace to the object, it will do so via set_backtrace
  • Otherwise, it will check for an existing backtrace on the object via the backtrace method - if not found, Ruby will construct a backtrace and associate it with the object via set_backtrace.

Any object can be an exception template, all it has to do is implement the exception method. exception is expected to return an Exception object (which may be the template object itself), and it's expected to take 1 optional argument, of any type, which is passed in directly from a Kernel#raise (or similar method) argument and is intended to serve as a template-interpreted parameter in the exception object creation process.

There are 2 default implementations of exception:

  • Exception::exception, which implements the default behaviour of Class#new
  • Exception.exception, which expects an optional single argument of self or a message, and either:
  • Returns itself, if called with no arguments, or with 1 argument which is itself - note that it is probably sensible to preserve this behaviour if you override exception
  • Creates a clone of itself (with the default implementation of Object#clone), and invokes the default implementation of Exception#initialize on the new object with exception's arguments
    • Note that initialize accepts 1 optional argument and will reset the internal backtrace state to nil, and the internal message state to the argument (or nil if not provided)

Raising exceptions

The default implementations of exception therefore (at least in the case of `Exception#exception) and allow, for example, the following constructs:

raise exc                       # Where exc is an Exception object, raises it directly
raise exc, exc, bt              # Where bt is a backtrace, sets the backtrace on the exception and raises it
raise exc, "hello"              # Raises a clone of exc with the message "hello"
raise StandardError, "hello"    # Raises a new instance of StandardError created with the argument "hello"

Note that an exception class's initialize method should be prepared to be invoked with 1 optional argument through the Exception::exception method, unless that class overrides its Exception::exception method for different behaviour.

Raising exceptions

setup_exception/rb_longjmp/rb_exc_raise nil param/rb_exc_fatal nil param/rb_raise_jump (used for exc's raised by Ruby code) In params:

  • tag (e.g. TAG_RAISE/FATAL)
  • mesg (exception object, or Qnil - meaning reraise th->errinfo, or new RuntimeError)

If mesg is nil and th->errinfo is kill, JUMP_TAG(TAG_FATAL)

Sort out backtrace (for non-sysstackerror)

  • check for presence of mesg.backtrace (must be nil, or array of strings, or string (will be wrapped in array))
  • if nil, create a backtrace and mesg.set_backtrace (will dup mesg if frozen)

th->errinfo = mesg if rb_threadptr_set_raised()

  • th->errinfo = exception_error
  • rb_threadptr_reset_raised()
  • JUMP_TAG(TAG_FATAL) if tag != TAG_FATAL
  • EXEC_EVENT_HOOK(RUBY_EVENT_RAISE) for rb_raise_jump:
  • rolls back the cfp
  • fires EXEC_EVENT_HOOK(RUBY_EVENT_C_RETURN) rb_thread_raised_clear() JUMP_TAG(tag)

make_exception/rb_f_raise/rb_exc_raise non-nil param/rb_exc_fatal non-nil param/thread.raise In params argc, argv, isstr (flag indicating args MIGHT be the string form, used for kernel.raise and thread.raise). Args either:

  • none - return nil
  • nil - raise TypeError
  • str? (convertible to string via to_str) - in param to new RuntimeError
  • exc, arg?, bt? - exc is an exception object or class typically, .exception(arg if provided) will be called on it (unless it is sysstack_error, in which case it will be taken as is)
    • for Exception classes executes default implementation of Class.new
    • for Exception objects returns itself with no args or with arg == itself, otherwise it clones itself and runs ".initialize(arg)"

Returns Qnil (meaning propagate existing exception, or else revert to default exception type), or an exception object

Raises TypeError if it can't call ".exception" or if the object it ends up with is not an Exception

if bt is provided, set_backtrace(mesg, bt)

raised flag special exceptions fatal exceptions throw/catch accessing errinfo from ruby code

  • Exception - The root exception class - all objects to be raised as exceptions must inherit from this class.
  • NoMemoryError - Raised when memory allocation failed (out of memory)
  • SystemStackError - Raised when ran out of C stack or Ruby VM stack, e.g. method or proc calls nested too deep
  • SecurityError - Raised when Ruby security features are enabled, and a disallowed insecure operation was attempted
  • SystemExit - Raised to instruct the Ruby VM to terminate, typically by the Kernel#exit or Kernel#abort methods
  • SignalException - Raised when the Ruby VM receives an OS signal (other than the SIGINT signal) that it has been configured to raise as an exception
    • Interrupt - Like SignalException, for the SIGINT signal
  • ScriptError - A container for exceptions due to problems with Ruby code
    • LoadError - Raised when the Ruby VM is unable to open a Ruby sourcefile or extension library that it is attempting to load
    • SyntaxError - Raised when the Ruby VM encounters invalid code when trying to compile Ruby code, e.g. a Ruby sourcefile or eval string.
    • NotImplementedError - Raised when functionality is invoked which is not available or not yet supported on the current platform
  • StandardError - A container for "regular" exceptions, i.e. all exceptions other than the special circumstance exceptions described above. The rescue clause catches StandardError by default. Application-defined exceptions should inherit from this class.
    • ArgumentError - Raised when invalid arguments were provided to a method (and there is not a more specific exception type that could be thrown instead)
    • EncodingError - A container for exceptions related to problems with translating between different String character set encodings. Note that a failure due to invalid byte sequences when interpreting against a single encoding is raised as an ArgumentError.
      • Encoding::CompatibilityError - Raised when an operation requires strings to be bytecode-compatible with different character sets, and they are not
      • Encoding::ConverterNotFoundError - Raised when Ruby can't transcode between character encodings because the necessary converters are not available
      • Encoding::InvalidByteSequenceError - Raised when Ruby can't transcode between character encodings due to invalid byte sequences in the source string
      • Encoding::UndefinedConversionError - Raised when Ruby can't transcode between character encodings because the target encoding does not support a character in the source string
    • FiberError - Raised for errors associated with creating and manipulating Fibers
    • IOError - Raised for general errors associated with I/O operations, other than OS-raised errors which are raised as SystemCallErrors
      • EOFError - Raised for end-of-file errors associated with I/O operations
    • IndexError - Raised when an invalid index was specified, e.g. into an array
      • KeyError - Raised when an invalid key was specified, e.g. into a hash table
      • StopIteration - Raised when using external (pull-style) iteration with an Enumerator object when the iteration is complete. Rescued by Kernel#loop so this method can be used as a convenience to convert from pull-style to push-style enumeration.
    • LocalJumpError - Raised when a yield or control transfer instruction fails to resolve
    • Math::DomainError - Raised when a math function is invoked with arguments outside its valid domain
    • NameError - Raised for errors associated with identifiers specified in Ruby code - e.g. methods, variables or constants which are invalid, don't exist or are disallowed in the context (excluding those raised for NoMethodError)
      • NoMethodError - Raised when attempting to call a method on an object and the call is not resolved, either directly or via method_missing. method_missing is required to raise this exception when invoked with methods that it doesn't implement.
    • RangeError - Raised for general errors where a numerical argument is outside the valid range for the operation
      • FloatDomainError - Raised when attempting to convert special floating-point values (such as Infininty and NaN) to other numeric types which don't support them
    • RegexpError - Raised for invalid regular expressions
    • RuntimeError - A generic exception class for errors not falling in other categories. This is the default exception class raised by e.g. Kernel#raise if no other exception class is specified.
    • SystemCallError
      • Errno::*
      • Errno::NOERROR
    • ThreadError - Raised for errors associated with creating and manipulating Threads and Mutexes
    • TypeError - Raised for general errors where an argument is not of the expected class
    • ZeroDivisionError - Raised when attempting to divide by 0

Clone this wiki locally