Exception handling

Exceptions are any unusual events (not necessarily errors) that is detectable by hardware or software and that may require special handling.

The exception is raised when the event occurs, the special processing is exception handling, and the routine which carries this out is the exception handler.

There are a wide variety of exception types, from hardware-detectable errors such as divide-by-zero, to file handling exceptions such as end-of-file, to subscript-checking exceptions (e.g. out-of-bounds array accesses).

Different exception handling facilities are provided in different languages - where a particular ability is not directly provided by the language it is the user's responsibility to devise their own handling for the event in question.

For example, the compiler for a particular language might automatically insert checks for array bounds or divide-by-zero cases: this results in a simpler and more readable user program since the user source code does not need to explicitly include the checks and handlers.

There are a number of key design decisions:


Exception handling in C++

Exception handling in C++ consists of try blocks which define the scope of exceptions, and a subsequent sequence of catch blocks which handle exceptions generated in the preceding try block. I.e.
try {
   ...
   throw(actual parameters); // generate exception
   ...
}

catch (formal parameter type list 1) {
// handler 1, for exceptions thrown with matching
//    parameter list
   throw; // re-throw unhandled exceptions
}

catch (formal parameter type list 2) {
// handler 2
}
...
When an exception is generated (thrown) the try block it occurs in is immediately terminated, and the "catch" statements following the block are sequentially searched.

The first catch whose parameter types match the thrown exception, is used for exception handling.

If a block is unable to handle the exception it can throw it again, hopefully to be caught by a subsequent catch block.

For instance, if a called function generates an exception it cannot handle, then the caller's catch blocks are explored - if those still cannot handle it then search the catch blocks from its caller, etc.

If a class method throws an exception it cannot handle then the scope of the instance is used to determine which try blocks to use. This is illustrated in the program below, for both heap-dynamic and stack-dynamic instances of classes.

#include <iostream>
using namespace std;

class testclass {
    public:
        void thrower() 
        {
           try {
              throw(1);
           } 
           catch (int e) {
              cout << "caught " << e << " in thrower, throwing " << (e+1) << endl;
              throw (e+1);
           }
        }
};

void container1()
{
   try {
       testclass t;
       cout << "In container1, calling stack-dynamic thrower" << endl;
       t.thrower();
   }
   catch (int e) {
       cout << "caught " << e << " in container1, throwing " << (e+1) << endl;
       throw (e+1);
   }
}

void container2()
{
   try {
       testclass *t = new testclass;
       cout << "In container2, calling heap-dynamic thrower" << endl;
       t->thrower();
   }
   catch (int e) {
       cout << "caught " << e << " in container2, throwing " << (e+1) << endl;
       throw (e+1);
   }
}

int main()
{
   try { container1(); }
   catch (int e) { cout << "caught " << e << " in main" << endl; }

   try { container2(); }
   catch (int e) { cout << "caught " << e << " in main" << endl; }

   return 0;
}
The program generates the following output

      In container1, calling stack-dynamic thrower
      caught 1 in thrower, throwing 2
      caught 2 in container1, throwing 3
      caught 3 in main
      In container2, calling heap-dynamic thrower
      caught 1 in thrower, throwing 2
      caught 2 in container2, throwing 3
      caught 3 in main

In general:

Best practice is to create classes for each desired exception type, usually based on a single general exception class.

Such a class, exception, is defined in the exception library, and has a variety of dynamically bound methods associated with it.

One of the most useful is the what method, which returns a character string describing the nature of the exception that was detected.

A short example is shown below, with one user-defined class derived from the base class, and also making use of a pre-built exception bad_alloc, dealing with failures of memory allocation in new.

Note: C++ does not allow you to catch errors that are not exception based (things like stack overflow, segmentation faults, divide by zero, etc). These are errors with undefined behaviour and do still result in program crashes.

#include <iostream>
#include <exception>
#include <cstdlib>
using namespace std;

class myExcept: public exception {
   public:
      virtual const char* what() const throw() {
         return "Hey, it was mine!";
      }
};

void f0();  // custom exception
void f1();  // new allocation failure
void f2();  // throwing non-derived exception

int main(int argc, char *argv[])
{
   int which = 0;
   if (argc > 1) which = atoi(argv[1]);

   try {
      switch (which) {
         case 0: f0(); break;
         case 1: f1(); break;
         case 2: f2(); break;
         default: break;
      }
   }
   catch (exception& e) {
      // catches anything derived from the base exception class
      cout << e.what() << endl;
   }
   catch (...) {
      // catches anything, but you can't tell what you've caught
      cout << "Unknown exception type detected" << endl;
   }
   cout << "Processing continued after exception caught" << endl;
   return 0;
}

// custom error
void f0() {
   cout << "Throwing my own exception" << endl;
   myExcept e;
   throw e;
}

// new allocation failure
void f1() {
   cout << "New allocation failure" << endl;
   int *arr = new int[1000000];
   delete [] arr;
}

// throwing something not derived from exception
void f2()
{
   throw 3;
}


Exception handling in Java

Java's exception handling is essentially an object-oriented version of C++ exception handling.

There is a Throwable class, and all Java exceptions are objects of classes that are descendants of Throwable.

As with C++, there are no default exception handlers and it is not possible to disable exceptions.

Unlike C++, user programs can handle certain system-generated exceptions (indices out of range, accesses to null reference variables, etc).

There are a variety of system-defined descendants, here is a partial breakdown:

                         Throwable
                        /         \
                   Error           Exception
                                  /         \
                        RuntimeException   IOException
                         /         \
ArrayIndexOutOfBoundsException   NullPointerException
The Error class and its descendants are concerned with errors thrown by the Java interpreter (e.g. running out of heap memory), and are never thrown by (or handled in) user programs.

By convention, user-defined exceptions are subclasses of Exception.

Defining and using exception handlers:

As with C++, we have try blocks, in which we may throw exceptions, and use catch units to handle them.

To define our exception handler, we need to extend the Exception class, e.g.

class MyNewException extends Exception {
   public MyNewException();  // default constructor
   public MyNewException(String message) {
       super (message));     // explicitly call the superclass constructor
   }
}
Given the exception handler has been created, it can be invoked in a number of ways in the try block, for instance:
try {
   ...
   throw new MyNewException();
   ...
   MyNewException XObj = new MyNewException();
   throw XObj;
   ...
   throw new MyNewException("a message with some useful data");
   ...
}
Once thrown, we can use catch (after the try block) to try and capture and handle the exception.

The handlers (catch functions) are examined in order, and the exception is bound to the first handler whose parameter is either of the same class as the exception's or is an ancestor class of the exception's.

(E.g. in the example above, the thrown exception could be handled either by a catch whose parameter type was a class of type MyNewException or Exception.)

Try blocks can be nested, and if so the search begins with handlers in the innermost scope, and works "outwards".

As a general rule, the list of exception handlers should be ordered from most-specific to least-specific, e.g. the last handler in the list should look something like

catch (Exception generic_exception_object) {
...
}
Each method in Java is defined along with a list of the types of exceptions which can be generated (thrown) in that method.
void foo() throws MyNewException { ... }

The method can throw the listed exceptions and any descendent exceptions, e.g. void foo() throws Exception { ... essentially says the method foo can throw any type of exception.

IF YOU DO NOT LIST EXCEPTIONS THEN NONE CAN BE THROWN - this is the opposite of the C++ approach, where by default you can throw any exceptions.

Sometimes there are required completion activities for a code sequence - steps which are critical regardless of whether or not an exception was generated (e.g. closing a file that was previously opened).

To address this, Java has a finally block:

try {
   ...
}
catch (...) {
   ...
}
catch (...) {
   ...
}
...
finally {
}
You can use the finally statement even if you aren't throwing exceptions, although it probably only makes sense if you are prematurely terminating loops or selection statements in the try block, e.g.
try {
   ...
   if (...) return;
   ...
}
finally {
   // this block is executed even if the return
   // statement is executed above
   ...
}


Prolog Exceptions

Exceptions in prolog can be handled in a 3-argument catch goal, where

For example, in the query below we have the goal X is 2/0 causing a divide by zero exception, which is unified with E.

   catch(X is 2/0, E, true).
The query causes E to be unified with error(evaluation_error(zero_divisor), 0) i.e. the specific kind of exception that got generated.

Consider the following rule, where we know a divide-by-zero error might crop up:

Divide(Numerator, Denominator, Result) :- 
     catch(Result is Numerator / Denominator, error(evaluation_error(Result), ErrInfo), true).
In this case, when the exception is generated we find that Result is unified with zero_divisor and ErrInfo is unified with 0.

We can also deliberately throw exceptions using the throw(E) goal.

Exceptions that arise during processing of a particular goal can be tested for and handled using a catch statement around the goal:
..., catch(Goal, ExceptionRaised, Result), ...

If an exception does not occur during the goal, the catch statement evaluates to whatever the goal evaluated to (i.e. the catch has no observable effect other than slowing things down a tad).

If an exception does occur during the goal, the second parameter is unified with the exception value raised, and the third parameter is used to determine the result of the catch statement.

For example, in catch(X is 2/0, E, true) the goal being run is X is 2/0, which happens to cause a divide-by-zero exception. As a result, E is unified with the actual exception generated. If you run that catch statement as a query in prolog it will respond with yes, and tell you what specific exception E was unified with.

We can use this to create wrappers around queries to include exception handling, as in the safequery example below. Instead of simply issuing some query, BLAHBLAHBLAH, the user now issues safequery(BLAHBLAHBLAH), and the rules for safequery handle any exceptions raised by BLAHBLAHBLAH.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% rules for wrapping a handler around queries %%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% have one safequery rule to handle cases where the query goal, G,
%      is satisfactorily handled,
% and anothe rule to handle cases where G raises an exception
safequery(G) :- catch(G, E, true), 
                var(E), write_ln('query succeeded').
safequery(G) :- catch(G, E, true), nonvar(E), 
                write_ln('query raised an exception'), write_ln(E).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%% some example queries using safequery %%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

?- safequery(X is 4/2). 
query succeeded 
 
X = 2  
 
Yes 

?- safequery(X is 2/0). 
query raised an exception 
error(evaluation_error(zero_divisor), context(/ /2, _G318)) 
 
X = _G216  
 
Yes 
You can deliberately generate an exception within a goal using throw, e.g.
catch(throw(3), E, true). will generate an exception, unifying E with 3.

Find out how many different types of error you can get prolog queries to generate by trying different goals within the catch, e.g.

catch(X is 2/0, E, true).  % catching a divide-by-zero
catch(X is Y/Z, E, true).  % catching uninstantiated vars on the LHS
Then determine how you can further restrict the unification of E, e.g.
N is 3, D is 0, catch(X is N/D, error(E), true).  
Finally, use that to determine how to create an "exception translator" for the user.

For example, create a set of translate(E) rules, which take an exception and produce appropriate text error messages for the user. We could then rewrite the user query rules as something like:

% if the query proceeds without raising an exception 
%    then do nothing extra
query(Q) :- catch(Q, E, true), var(E).
% but if an exception is raised
%     then call the translator to interpret it for the user
query(Q) :- catch(Q, E, true), nonvar(E), translate(E).

Sample starter solution:
% if the query proceeds without raising an exception 
%    then do nothing extra
query(Q) :- catch(Q, Err, true), var(Err).
% but if an exception is raised
%     then call the translator to interpret it for the user
query(Q) :- catch(Q, Err, true), nonvar(Err), translate(Err).

% first, check to see if the error we're translating has
%    the form error(E,C),
% if so use transerror(E,C) to translate it,
% otherwise just say it's an unknown error
translate(error(Err, Context)) :- transerror(Err, Context).
translate(Err) :- write('unknown exception: '), write_ln(Err).

% if the error was identified as our error(E, C) format,
% transerror breaks it down into one of four categories:
%    evaluation_error
%    instantiation_error
%    existence_error
% or anything else

% for evaluation errors, display the error type
%     and the kind of operation and operands it occurred in
transerror(evaluation_error(Err), context(C, V)) :-
    write('arithmetic error: '), write_ln(Err),
    write(' in operation '), write(C), write(', operand '), write_ln(V).

% for instantiation errors, display the uninstantiated argument
transerror(instantiation_error, Arg) :-
    write('instantiation error with argument '), write_ln(Arg).

% for existence errors, display the nonexistent procedure/op
transerror(existence_error(Type, Proc), context(C)) :-
    write('existence error in '),
    write(Type), write(' '), write_ln(Proc),
    write(' context '), write_ln(C).

% for other error(E, C) types just display a generic message
transerror(Err, Context) :- write('error:'), write_ln(Err), 
                            write(' in context '), write_ln(Context).


Scheme Exceptions

A slightly different approach is taken in Scheme, in that at any given point in time there exists a function which is the current exception handler. (Though this seems contrary to the idea of a stateless functional language.)

When exceptions occur, the current handler is automatically called to handle them.

You can change the current exception handling routine using
(current-exception-handler newhandler) or you can get the current handler using
(current-exception-handler)

You can deliberately raise an exception with
(raise E)
where E can be anything you want to pass to the handler.