The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

    Exception - structured exception handling for Perl

SYNOPSIS

    use Exception qw(:all);

    Exception->debugLevel(DEBUG_STACK);
    my $err=new Exception 'id';

    try {
      $err->raise('error text');
      die 'dead';
    }
    when $err, except {
        my $error=shift;
        $error->confess;
      }
    when 'die', reraise
    except {
        shift->croak;
      }
    finally {
        print STDERR "Tidying up\n";
      };

DESCRIPTION

This module fulfils two needs; it converts all errors raised by die to exception objects which may contain stack trace information and it implements a structured exception handling syntax as summarised above.

What You Get Just by Loading the Module

Exception installs a $SIG{__DIE__} handler that converts text passed to die into an exception object. Stringification for the object is mapped onto the stringify method so, by default, Perl will simply print the error text on to STDERR on termination.

Structured Exception Handling

Exception allows you to structure your exception handling; code that can directly or indirectly raise an exception is enclosed in a try block, followed by except blocks that can handle specific exceptions or act as a catch-all handler for any exceptions not otherwise dealt with. Exceptions may be propagated back to outer contexts with the possibility of adding extra information to the exception, and a finally block can also be specified, containing tidy-up code that will be called whether or not an exception is raised.

Exception handling blocks can be tied to specific exceptions by id, by exception object or by regexp match against error text. The default exception display code can be augmented or replaced by user code.

Stack Tracing

Exception can be persuaded to capture and display a stack trace globally, by exception object or explicitly when an exception is raised. You can capture just the context at which the exception is raised, a full stack trace or an absolutely full stack trace including calls within the Exception module itself (and, if appropriate, within mod_perl).

EXCEPTION OBJECTS

Exception will create an exception object when it traps a die. More flexibly, user-created exception objects can be raised with the raise method.

Each exception object has an id; a text string that is set when the object is created (and that can be changed using the id method thereafter). die exceptions have the id 'die', anonymous exceptions created at raise time have an empty id. The exception id is set initially by a parameter to the exception constructor:

  my $err=new Exception 'id';

Exceptions are raised by the raise method:

  $err->raise('error text');

or:

  Exception->raise('text');

for an anonymous exception.

STRUCTURED EXCEPTION HANDLING

Code to be protected by Exception is enclosed in a try {} block. Any die or raise event is captured; what happens next is up to you. In any case, you need to import the routines that implement the exception structuring:

  use Exception qw(:try);

is the incantation. Either that or one of qw(:stack), qw(:debug) or qw(:all) if you need stack frame, debug or both facilities as well.

Default Behaviour

If no exception handling code is present, the exception is reraised and thus passed to the calling block; this is, of course, exactly what would happen if try wasn't present at all. More usefully, the same will happen for any exceptions raised that aren't matched by any of the supplied exception blocks.

If no user-supplied exception handler gets called at all, Perl will display the exception by stringifying it and terminate the program.

Trapping Exceptions

except blocks match all or some exceptions. You can define as many as you like; all blocks that specifically match an exception are called (unless an earlier except block raises an exception itself), default blocks are only executed for otherwise unmatched exceptions.

In either case, the except block is passed two parameters: the exception object and the current return value for the entire try construct if it has been set.

Use the when clause to match exceptions against except blocks:

  try {<code>} when <condition>, except {<handler>};

Conditions may be text strings, matching the id of an exception, regexp refs, matching the text of an exception, or exception objects, matching the given exception object or clones thereof. Multiple conditions may be specified in an array ref; the except block will apply if any of the conditions match.

For example:

  my $err=new Exception 'foo';

  try {
    $err->raise('bar');
  }
  when ['foo', qr/bar/, $err], except {
      shift->croak;
    };

will match on all three conditions.

Reraising Exceptions

Exceptions can be passed to a calling context by reraising them using the reraise clause. reraise can be tied to specific exceptions using when exactly as for except.

For example:

  try {
    <code>
  }
  when 'die', reraise
  except {
      <other exceptions>
    };

would pass exceptions raised by die to the calling routine.

Transforming Exceptions

It is sometimes useful to change the id of an exception. For example, a module might want to identify all exceptions raised within it as its own, even if they were originally raised in another module that it called. The as method performs this function:

  my $myErr=new Exception 'myModule';

  try {
    <calls to other code that might raise exceptions>
    <local code that might raise $myErr exceptions>
  }
  when $myErr, reraise
  except {
      shift->as($myErr)->raise('extra text');
    };

This will pass locally raised exception straight on; other exceptions will be converted to $myErr exceptions first. The error text parameter to the raise can be omitted: if so, the original error text is passed on unchanged. Adding extra text can however be useful in providing extra contextual information for the exception.

Using an exception object as the parameter to as in this way replaces the id, debugLevel and confessor properties of the original exception. as can also be passed a text string if only the id of the exception needs changing.

Finalisation Blocks

One or more finally blocks can be included. These will all be executed always regardless of exceptions raised, trapped or reraised and can contain any tidy-up code required - any exception raised in an except block, reraised or not handled at all will be raised after all finally blocks have been executed:

  try {
    <code>
  }
  except {
      <exception handling>
    }
  finally {
      <housekeeping code>
    }

The finally blocks are passed two parameters, the exception (if any) and the current return value (if any) in the same way as for except blocks.

Return Values

try constructs can return a (scalar) value; this is the value returned by either the try block itself or by the last executed except block if any exception occurs, passed though any finally blocks present.

For example:

  my $value=try {
    <code>
    return 1;
  }
  except {
      <code>
      return 0;
    }
  finally {
      my ($error, $retval)=@_;
      <code>
      return $retval;
    }

will set $value to 1 or 0 depending on whether an exception has occured. Note the way that the return value is passed through the finally block.

STACK TRACING

Exception can be persuaded to capture and display a stack trace by any one of four methods:

  1. by setting the environment variable _DEBUG_LEVEL before starting your Perl script.

  2. by setting the package default with Exception->debugLevel(DEBUG_STACK).

  3. by setting the debug level explicitly in an error object when you create it:

      my $err=new Exception 'foo';
      $err->debugLevel(DEBUG_CONTEXT);
  4. by setting the debug level when you raise the exception:

      $err->raise("failed: $!", {DEBUGLEVEL=>DEBUG_ALL});

Each of these will override preceding methods. The default default is no stack capture at all.

The debug level can be set to:

DEBUG_NONE:

no stack trace is stored.

DEBUG_CONTEXT:

only the location at which the exception was raised is stored.

DEBUG_STACK:

a full stack trace, excluding calls within Exception, is stored.

DEBUG_ALL:

a full stack trace, including calls within Exception, is stored.

You need to import these constants to use them:

  use Exception qw(:debug);
  use Exception qw(:all);

will do the trick.

Note that these controls apply to when the exception is raised - the display routines will always print or return whatever stack information is available to them.

EXCEPTION OBJECT METHODS

new

  my $err=new Exception 'id', 'error text';
  my $new=$err->new('id2', 'error text');

This method either creates a new exception from scratch or clones an existing exception.

The first parameter is an exception id that can be used to identify either individual exceptions or classes of exceptions. The optional second parameter sets the text of the exception, this can be added to when the exception is raised. The default is no text.

raise

  open FH, "<filename"
    or $err->raise("can't read filename: $!");

Raise an exception. That's it really. If raise is applied to an existing exception object as above, the text supplied is added to any pre-existing text in the object. Anonymous exceptions can also be raised:

  Exception->raise('bang');

but the use of predeclared exception objects is encouraged.

as

  $err1->as($err2);
  $err1->as('new id');

Transform an exception object either from another template exception, which will change the object's id, debug level and confessor, or by name, which will just change the id of the exception.

as returns the exception object, so a further method (typically raise) may be applied in the same statement:

  $err1->as('foo')->raise;

stringify

  my $text=$err->stringify;
  my $text=$err->stringify(1);

Return the text and any saved stack trace of an exception object. the optional parameter is a bitmask,

stack

  my $stack=$err->stack;

Return the stack trace data (if any) for an exception. The stack is returned as a reference to an array of stack frames; each stack frame being a reference to an array of data as returned by caller. The stack frame elements can be indexed symbolically as FRAME_PACKAGE, FRAME_FILE, FRAME_LINE, FRAME_SUBNAME, FRAME_HASARGS and FRAME_WANTARRAY. FRAME_LAST is defined as the index of the last element of the frame array for convenience.

To use these names, you need to import their definitions:

  use Exception qw(:stack);
  use Exception qw(:all);

will do what you want.

text

  my $text=$err->text;
  my $defaultText=Exception->text;
  my $old=$err->text($new);
  my $oldDefault=Exception->text($new);

Get or set the text of an exception. The routines all return a reference to the array of error text strings held in the Exception object before the call. If text is passed a text string, that text is added to the end of the array; if text is passed a reference to an array of strings, the array is replaced by the one given.

An exception also gains a line every time it is raised with a text parameter. Actually, to be precise, raise creates a new exception object with the extra line, but that's the sort of implementation detail you don't need to know, unless of course you want to...

debugLevel

  my $level=$err->debugLevel;
  my $defaultLevel=Exception->debugLevel;
  my $old=$err->debugLevel($new);
  my $oldDefault=Exception->debugLevel($newDefault);

Get or set the stack trace level for an exception of object or the package default. See the section above.

confessor

  my $code=$err->confessor;
  my $defaultCode=Exception->confessor;
  my $old=$err->confessor($new);
  my $oldDefault=Exception->confessor($new);

Get or set code to display an exception. The routines all return a reference to an array of coderefs; the routines are called in sequence when an exception's confess or croak methods are invoked.

If confessor is passed a coderef, the code is added to the end of the array (the routines are actually called last to first); if confessor is passed a reference to an array of coderefs, the array is replaced by the one given. As a special case, if the array given is empty, the set of confessor routines is reset to the default.

A useful example of a confessor would be code that printed an exception on STDOUT instead of STDERR which, used in conjunction with a stringifier that generated HTML, could be used within CGI scripts.

A confessor routine is passed two parameters when called: the exception object and a quiet flag; if this is non-zero, the routine is expected not to produce any output. The routine should return the new value of the flag: 0, 1 or -1, the last telling Exception to not call any further display routines at all.

As a trivial example, here's the default routine provided:

  sub _confess($$) {
    my ($error, $quiet)=@_;
    print STDERR $error->stringify unless $quiet;
    $quiet
  }

stringifier

  my $code=$err->stringifier;
  my $defaultCode=Exception->stringifier;
  my $old=$err->stringifier($new);
  my $oldDefault=Exception->stringifier($new);

Get or set the code to stringify an exception object. This code will be called by the stringification overloading and by the stringify and default confess methods (the latter is also called by the <croak method).

Your stringifier routine takes two parameters: the exception object and the option parameter passed to the stringify method; import :stringify to get the symbolic bit names into your code:

STRINGIFY_NOSTACK

return just the text even if a stack trace is available.

STRINGIFY_EXITCATCH

this bit shouldn't be set in user code; it will be set for you for mod_perl scripts (and CGI scripts if you've set checkCGI) if your script is exiting with an uncaught exception.

id

  my $id=$err->id;
  my $defaultId=Exception->id;
  my $old=$err->id($new);
  my $oldDefault=Exception->id($new);

Get or set the id of an exception, or of the package default used for anonymous exceptions. Exception ids can be of any scalar type - Exception uses text strings for those it generates internally ('die' for exceptions raised from die and, by default, '' for anonymous exceptions) - but you can even use object references if you can think of something useful to do with them, with the proviso that when uses a simple eq test to match them; you'll need to overload eq for your objects if you want anything clever to happen.

exitcode

  my $exitcode=$err->exitcode;
  my $defaultExitcode=Exception->exitcode;
  my $old=$err->exitcode($new);
  my $oldDefault=Exception->exitcode($new);

Get or set the exit code returned to the OS by croak. This defaults to 1.

confess

  $err->confess;

Display the exception using the list of confessor routines it contains. By default, this will print the stringified exception on STDERR.

croak

  $err->croak;
  $err->croak($exitCode);

Call the exception's confess method and terminate. If no exit code is supplied, exit with the exception's exit code as set by the exitcode method.

registerDefault

  package MyError;
  @ISA=qw(Exception);
  use Exception qw(:all);
  BEGIN {MyError->registerDefault}

This package method reblesses the default and die exception objects as being members of a subclass. This is intended for subclasses that reimplement the stringify method for a particular environment (typically a CGI script) so that the default handling, in the absence of a caught try block, for the inbuilt anonymous and die exception objects uses the subclassed stringify to render the exception.

Clearly, the last package that invokes this method gets the objects.

COMPATIBILTY

Code written prior to version 1.5 that calls the text method will need rewriting:

 1.4 and earlier                         1.5 and later

 scalar $err->text or $err->text(0)      => $err->stringify(1)
        $err->text(1)                    => join "\n", @{$err->text}
        $err->text(2)                    => $err->stringify or "$err"

 list   $err->text or $err->text(0 or 1) => @{$err->text}
        $err->text(2)                    => (@{$err->text}, $err->stack)

BUGS

The module can interact in unpredictable ways with other code that messes with $SIG{__DIE__}. It does its best to cope by keeping and propagating to any die handler that is defined when the module is initialised, but no guarantees of sane operation are given.

finally blocks are always executed, even if an exception is reraised or an exception is raised in an except block. No problem there, but this raises the question of what to do if another exception is raised in the finally block. At present Exception merges the the second exception into the first before reraising it, which is probably the best it can do, so this probably isn't a bug after all. Whatever.

Need More Tests.

AUTHOR

Pete Jordan <pete@skydancer.org.uk> http://www.skydancer.org.uk/