NAME

docs/pdds/pdd23_exceptions.pod - Parrot Exceptions

ABSTRACT

This document defines the requirements and implementation strategy for Parrot's exception system.

VERSION

$Revision: 18563 $

DESCRIPTION

Exceptions are indications by running code that something unusual -- an "exception" to the normal processing -- has occurred. When code detects an exceptional condition, it throws an exception object. Before this occurs, code can register exception handlers, which are functions (or closures) which may (but are not obligated to) handle the exception. Some exceptions permit continued execution immediately after the throw; some don't.

Exceptions transfer control to a piece of code outside the normal flow of control. They are mainly used for error reporting or cleanup tasks.

(A digression on terminology: In a system analysis sense, the word "exception" usually refers to the exceptional event that requires out-of-band handling. However, in Parrot, "exception" also refers to the object that holds all the information describing the exceptional condition: the nature of the exception, the error message describing it, and other ancillary information. The specific type (class) of an exception object indicates its category.)

Exception Opcodes

These are the opcodes relevant to exceptions and exception handlers:

push_eh SUB_LABEL
push_eh INVOCABLE_PMC

Push an invocable PMC -- usually a closure or, in rarer cases, a subroutine -- onto the exception handler stack.

If a SUB_LABEL is provided, Parrot automatically performs the equivalent of a newclosure operation on the given subroutine, and pushes the resulting closure.

When an exception is thrown, Parrot walks up the stack of active exception handlers, invoking each one in turn, but still in the dynamic context of the exception (i.e. the call stack is not unwound first). See below for more detail.

pop_eh

Pop the most recently pushed exception handler off the exception handler stack.

{{ TODO: Provide exception handler stack introspection. }}

throw EXCEPTION

Throw an exception consisting of the given EXCEPTION PMC. Active exception handlers (if any) will be invoked with EXCEPTION as the only parameter.

Throwing an exception with throw is a one-way trip (unless you have made other arrangements) because Parrot does not take a continuation after this opcode. (But see throwcc below.)

Any type of PMC can be thrown as an exception. However, if there's any chance of cross-language calls -- and in a Parrot environment, cross-language operations are kind of the point -- then you should be prepared to catch exception classes you would never have thrown yourself.

That said, it is VERY STRONGLY RECOMMENDED that any thrown PMC that can possibly escape your private sandbox should meet the minimal interface requirements of the parrot;exception class, described below.

throwcc EXCEPTION [ , CONTINUATION ]

Throw an exception consisting of the given EXCEPTION PMC after taking a continuation at the next opcode. When a CONTINUATION is passed in, it will use that instead. Active exception handlers (if any) will be invoked with EXCEPTION and the given continuation as parameters.

Except for the continuation which is passed to exception handlers, throwcc is just like throw. This opcode is useful for exceptions that are more like warnings or notices than errors.

Exception handlers can resume execution immediately after the throwcc opcode by executing the handled opcode, and then invoking the given continuation which they receive as a parameter. That continuation must be invoked with no parameters; in other words, throwcc never returns a value.

die [ MESSAGE ]

The die opcode throws an exception of type exception;death with a payload of MESSAGE. If MESSAGE is a string register, the exception payload is a String PMC containing MESSAGE; if MESSAGE is a PMC, it is used directly as the exception payload.

The default when no MESSAGE is given is "Fatal exception at LINE in FILE." followed by a backtrace.

If this exception is not handled, it results in Parrot returning an error indication and the stringification of MESSAGE to its embedding environment. When running standalone, this means writing the stringification of MESSAGE to standard error and executing the standard C function exit(1).

exit [ EXITCODE ]

Throw an exception of type exception;exit with a payload of EXITCODE, which defaults to zero, as an Integer PMC.

If not handled, this exception results in Parrot returning EXITCODE as a status to its embedded environment, or when running standalone, to execute the C function exit(EXITCODE).

handled EXCEPTION

While handling an exception, tell Parrot that the exception has been handled and should be removed from the stack of active exceptions. This opcode is an exception handler's way of telling Parrot that it has handled the exception.

Order of Operations in Exception Handling

When throw or throwcc is called, for all active exception handlers, in LIFO order:

1 Find the topmost exception handler.
2 Push an exception record somewhere, presumably on the exception handler stack. The exception record contains a pointer to an exception handler block, an exception PMC, and (optionally) a continuation.
3 Invoke the handler (note: this is still in the thrower's dynamic context).

If the handler returns without calling handled:

1 Find the "exception handling in progress" record.
2 Find the next exception handler.
3 If the handler is found, invoke it.
4 If no handler is found, and the exception is non-fatal (such as a warning), and there is a continuation in the exception record (because the throwing opcode was throwcc), invoke the continuation (resume execution). Whether to resume or die when an exception isn't handled is determined by the severity of the exception.
5 Otherwise terminate program a la die.

When running an embedded Parrot interpreter, the interpreter does not immediately terminate on an unhandled exception, it merely returns control to the embedding program and stores the unhandled exception so that it may be queried by the embedding program. The embedding program may choose to handle the exception and continue execution by invoking the exception's continuation.

When the handled opcode is called:

1 Pop and destroy the exception record.
2 If there was a continuation in the exception record, invoke the continuation.

STANDARD EXCEPTIONS

Universal Exception Object Interface [Advisory]

All of Parrot's standard exceptions provide at least the following interface. It is STRONGLY RECOMMENDED that all classes intended for throwing also provide at least this interface as well.

PMC *get_message()

Get an exception's human-readable self-description. Note that the type of the returned PMC may not be String, but you should still be able to stringify and print it.

PMC *get_payload()

Get the datum that more specifically identifies the detailed cause/nature of the exception. Each exception class will have its own specific payload type(s). See the table of standard exception classes for examples.

PMC *get_inner_exception()

If an exception is a consequence of a previous exception, the get_inner_exception() method returns that previous exception, else it returns null.

Interface of Standard Parrot Exceptions

Parrot's standard exceptions provide some additional methods beyond the three universal exception methods shown above. The additional methods are:

init_pmc(PMC *payload)

Initialize the exception PMC with the given payload. Note that the payload will be interpreted differently depending on the specific type of the exception. For example, the payload of exception;errno is an integer. In addition, some exceptions don't require payloads, thus:

init()

Initialize the exception PMC without a payload. Some exceptions are adequately self-explanatory without payloads.

void set_inner_exception(PMC *inner)

If an exception is a consequence of a previous exception, use the set_inner_exception() method to store that previous exception as part of the exception object.

Standard Parrot Exceptions

Parrot comes with a small hierarchy of classes designed for use as exceptions. Parrot throws them when internal Parrot errors occur, but any user code can throw them too.

exception

Base class of all standard exceptions. Provides no special functionality. Exists for the purpose of isa testing.

exception;errno

A system error as reported in the C variable errno. Payload is an integer. Message is the return value of the standard C function strerror().

exception;math

Generic base class for math errors.

exception;math;division_by_zero

Division by zero (integer or float). No payload.

exception;domain

Generic base class for miscellaneous domain (input value) errors. Payload is an array, the first element of which is the operation that failed (e.g. the opcode name); subsequent elements depend on the value of the first element.

(Note: There is not a separate exception class for every operation that might throw a domain exception. Class proliferation is expensive, both to Parrot and to the humans working with it who have to memorize a class hierarchy. But I understand the temptation.)

exception;lexical

An find_lex or store_lex operation failed because a given lexical variable was not found. Payload is an array: [0] the name of the lexical variable that was not found, [1] the LexPad in which it was not found.

Opcodes that Throw Exceptions

Exceptions have been incorporated into built-in opcodes in a limited way. For the most part, they're used when the return value is either impractical to check (perhaps because we don't want to add that many error checks in line), or where the output type is unable to represent an error state (e.g. the output I register of the ord opcode).

The div, fdiv, and cmod opcodes throw exception;math;division_by_zero.

The ord opcode throws exception;domain when it's passed an empty argument or a string index that's outside the length of the string. Payload is an array, first element being the string 'ord'.

The classoffset opcode throws exception;domain when it's asked to retrieve the attribute offset for a class that isn't in the object's inheritance hierarchy. Payload is an array: [0] string 'classoffset', [1] object in question, [2] ID of class not found.

The find_charset opcode throws exception;domain if the charset name it's looking up doesn't exist. Payload is an array: [0] string 'find_charset', [1] charset name that was not found.

The trans_charset opcode throws exception;domain on "information loss" (presumably, this means when one charset doesn't have a one-to-one correspondence in the other charset). Payload is an array: [0] string 'trans_charset', [1] source charset name, [2] destination charset name, [3] untranslatable code point.

The find_encoding opcode throws exception;domain if the encoding name it's looking up doesn't exist. Payload is an array: [0] string 'find_encoding', [1] encoding name that was not found.

The trans_encoding opcode throws exception;domain on "information loss" (presumably, this means when one encoding doesn't have a one-to-one correspondence in the other encoding). Payload is an array: [0] string 'trans_encoding', [1] source encoding name, [2] destination encoding name, [3] untranslatable code point.

Parrot's default version of the LexPad PMC throws exception;lexical for some error conditions, though other implementations can choose to return error values instead.

By default, the find_lex and store_lex opcodes throw an exception (exception;lexical) when the given name can't be found in any visible lexical pads. However, this behavior is only a default, as provided by the default Parrot lexical pad PMC LexPad. If a given HLL has its own lexical pad PMC, its behavior may be very different. (For example, in Tcl, store_lex is likely to succeed every time, as creating new lexicals at runtime is OK in Tcl.)

{{ TODO: List any other opcodes that currently throw exceptions and general categories of opcodes that should throw exceptions. }}

Other opcodes respond to an errorson setting to decide whether to throw an exception or return an error value. find_global throws an exception (or returns a Null PMC) if the global name requested doesn't exist. find_name throws an exception (or returns a Null PMC) if the name requested doesn't exist in a lexical, current, global, or built-in namespace.

{{ TODO: "errorson" as specified is dynamically rather than lexically scoped; is this good? Probably not good. Let's revisit it when we get the basic exceptions functionality implemented. }}

It's a little odd that so few opcodes throw exceptions (these are the ones that are documented, but a few others throw exceptions internally even though they aren't documented as doing so). It's worth considering either expanding the use of exceptions consistently throughout the opcode set, or eliminating exceptions from the opcode set entirely. The strategy for error handling should be consistent, whatever it is. [I like the way LexPads and the errorson settings provide the option for exception-based or non-exception-based implementations, rather than forcing one or the other.]

{{ NOTE: There are a couple of different factors here. One is the ability to globally define the severity of certain exceptions or categories of exceptions without needing to define a handler for each one. (e.g. Perl 6 may have pragmas to set how severe type-checking errors are. A simple "incompatible type" error may be fatal under one pragma, a resumable warning under another pragma, and completely silent under a third pragma.) Another is the ability to "defang" opcodes so they return error codes instead of throwing exceptions. We might provide a very simple interface to catch an exception and capture its payload without the full complexity of manually defining exception handlers (though it would still be implemented as an exception handler internally). Something like:

.local pmc error_code
.capture_start error_code
$P1 = find_lex 'foo'
.capture_end

# error_code contains what would have been the "error" return value

This could eliminate the need for "defanging" because it would be almost as easy to use as error codes. It could be implemented once for all exceptional opcodes, instead of needing to be defined for each one. And, it still keeps the error information out-of-band, instead of mixing the error in with normal return values. }}

Resuming after Exceptions

Exceptions thrown by standard Parrot opcodes (like the one thrown by find_global above or by the throw opcode) are always resumable, so when the exception handler function returns normally it continues execution at the opcode immediately after the one that threw the exception. Other exceptions at the run-loop level are also generally resumable.

$P0 = new String
$P0 = "something bad happened"
$P1 = new ['parrot';'exception'], $P0  # create new exception object
throw $P1                              # throw it

ATTACHMENTS

None.

FOOTNOTES

None.

REFERENCES

src/ops/core.ops
src/exceptions.c