Exceptions

Exceptions provide a way of subverting the normal flow of control. Their main use is error reporting and cleanup tasks, but sometimes exceptions are just a funny way to jump from one code location to another one. Parrot uses a robust exception mechanism and makes it available to PIR.

Exceptions are objects that hold essential information about an exceptional situation: the error message, the severity and type of the error, the location of the error, and backtrace information about the chain of calls that led to the error. Exception handlers are ordinary subroutines, but user code never calls them directly from within user code. Instead, Parrot invokes an appropriate exception handler to catch a thrown exception.

Throwing Exceptions

The throw opcode throws an exception object. This example creates a new Exception object in $P0 and throws it:

  $P0 = new 'Exception'
  throw $P0

Setting the string value of an exception object sets its error message:

  $P0 = new 'Exception'
  $P0 = "I really had my heart set on halibut."
  throw $P0

Other parts of Parrot throw their own exceptions. The die opcode throws a fatal (that is, uncatchable) exception. Many opcodes throw exceptions to indicate error conditions. The / operator (the div opcode), for example, throws an exception on attempted division by zero.

When no appropriate handlers are available to catch an exception, Parrot treats it as a fatal error and exits, printing the exception message followed by a backtrace showing the location of the thrown exception:

  I really had my heart set on halibut.
  current instr.: 'main' pc 6 (pet_store.pir:4)

Catching Exceptions

Exception handlers catch exceptions, making it possible to recover from errors in a controlled way, instead of terminating the process entirely.

The push_eh opcode creates an exception handler object and stores it in the list of currently active exception handlers. The body of the exception handler is a labeled section of code inside the same subroutine as the call to push_eh. The opcode takes one argument, the name of the label:

  push_eh my_handler
    $P0 = new 'Exception'
    throw $P0

    say 'never printed'

  my_handler:
    say 'caught an exception'

This example creates an exception handler with a destination address of the my_handler label, then creates a new exception and throws it. At this point, Parrot checks to see if there are any appropriate exception handlers in the currently active list. It finds my_handler and runs it, printing "caught an exception". The "never printed" line never runs, because the exceptional control flow skips right over it.

Because Parrot scans the list of active handlers from newest to oldest, you don't want to leave exception handlers lying around when you're done with them. The pop_eh opcode removes an exception handler from the list of currently active handlers:

  push_eh my_handler
    $I0 = $I1 / $I2
    pop_eh

    say 'maybe printed'

    goto skip_handler

  my_handler:
    say 'caught an exception'
    pop_eh

  skip_handler:

This example creates an exception handler my_handler and then runs a division operation that will throw a "division by zero" exception if $I2 is 0. When $I2 is 0, div throws an exception. The exception handler catches it, prints "caught an exception", and then clears itself with pop_eh. When $I2 is a non-zero value, there is no exception. The code clears the exception handler with pop_eh, then prints "maybe printed". The goto skips over the code of the exception handler, as it's just a labeled unit of code within the subroutine.

The exception object provides access to various attributes of the exception for additional information about what kind of error it was, and what might have caused it. The directive .get_results retrieves the Exception object from inside the handler:

  my_handler:
    .get_results($P0)

Not all handlers are able to handle all kinds of exceptions. If a handler determines that it's caught an exception it can't handle, it can rethrow the exception to the next handler in the list of active handlers:

  my_handler:
    .get_results($P0)
    rethrow $P0

If none of the active handlers can handle the exception, the exception becomes a fatal error. Parrot will exit, just as if it could find no handlers.

An exception handler creates a return continuation with a snapshot of the current interpreter context. If the handler is successful, it can resume running at the instruction immediately after the one that threw the exception. This resume continuation is available from the resume attribute of the exception object. To resume after the exception handler is complete, call the resume handler like an ordinary subroutine:

  my_handler:
    .get_results($P0)
    $P1 = $P0['resume']
    $P1()

Exception PMC

Exception objects contain several useful pieces of information about the exception. To set and retrieve the exception message, use the message key on the exception object:

  $P0            = new 'Exception'
  $P0['message'] = "this is an error message for the exception"

... or set and retrieve the string value of the exception object directly:

  $S0 = $P0

The severity and type of the exception are both integer values:

  $P0['severity'] = 1
  $P0['type']     = 2

The payload holds any user-defined data attached to the exception object:

  $P0['payload'] = $P2

The attributes of the exception are useful in the handler for making decisions about how and whether to handle an exception and report its results:

  my_handler:
    .get_results($P2)
    $S0 = $P2['message']
    print 'caught exception: "'
    print $S0
    $I0 = $P2['type']
    print '", of type '
    say $I0

ExceptionHandler PMC

Exception handlers are subroutine-like PMC objects, derived from Parrot's Continuation type. When you use push_eh with a label to create an exception handler, Parrot creates the handler PMC for you. You can also create it directly by creating a new ExceptionHandler object, and setting its destination address to the label of the handler using the set_addr opcode:

    $P0 = new 'ExceptionHandler'
    set_addr $P0, my_handler
    push_eh $P0
    # ...

  my_handler:
    # ...

ExceptionHandler PMCs have several methods for setting or checking handler attributes. The can_handle method reports whether the handler is willing or able to handle a particular exception. It takes one argument, the exception object to test:

  $I0 = $P0.'can_handle'($P1)

The min_severity and max_severity methods set and retrieve the severity attributes of the handler, allowing it to refuse to handle any exceptions whose severity is too high or too low. Both take a single optional integer argument to set the severity; both return the current value of the attribute as a result:

  $P0.'min_severity'(5)
  $I0 = $P0.'max_severity'()

The handle_types and handle_types_except methods tell the exception handler what types of exceptions it should or shouldn't handle. Both take a list of integer types, which correspond to the type attribute set on an exception object:

  $P0.'handle_types'(5, 78, 42)

The following example creates an exception handler that only handles exception types 1 and 2. Instead of having push_eh create the exception handler object, it creates a new ExceptionHandler object manually. It then calls handle_types to identify the exception types it will handle:

    $P0 = new 'ExceptionHandler'
    set_addr $P0, my_handler
    $P0.'handle_types'(1, 2)
    push_eh $P0
    # ...
  my_handler:
    # ...

This handler can only handle exception objects with a type of 1 or 2. Parrot will skip over this handler for all other exception types.

  $P1         = new 'Exception'
  $P1['type'] = 2
  throw $P1                     # caught

  $P1         = new 'Exception'
  $P1['type'] = 3
  throw $P1                     # uncaught

Annotations

Annotations are pieces of metadata code stored in a bytecode file. This is especially important when dealing with high-level languages, where annotations contain information about the HLL's source code such as the current line number and file name.

Create an annotation with the .annotate directive. Annotations consist of a key/value pair, where the key is a string and the value is an integer, or a string. Bytecode stores annotations as constants in the compiled bytecode. Consequently, you may not store PMCs.

  .annotate 'file', 'mysource.lang'
  .annotate 'line', 42
  .annotate 'compiletime', '0.3456'

Annotations exist, or are "in force" throughout the entire subroutine or until their redefinition. Creating a new annotation with the same name as an old one overwrites it with the new value. The annotations opcode retrieves the current hash of annotations:

  .annotate 'line', 1
  $P0 = annotations # {'line' => 1}

  .annotate 'line', 2
  $P0 = annotations # {'line' => 2}

To retrieve a single annotation by name, use the name with annotations:

  $P0 = annotations 'line'

Exception objects contain information about the annotations that were in force when the exception was thrown. Retrieve them with the annotations method on the exception PMC object:

  $I0 = $P0.'annotations'('line')  # only the 'line' annotation
  $P1 = $P0.'annotations'()        # hash of all annotations

Exceptions can also include a backtrace to display the program flow to the point of the throw:

  $P1 = $P0.'backtrace'()

The backtrace PMC is an array of hashes. Each element in the array corresponds to a function in the current call chain. Each hash has two elements: annotation (the hash of annotations in effect at that point) and sub (the Sub PMC of that function).