Exception Handling
I. Basic Concepts
A. Exception:
1. An unusual event, either erroneous or not
2. Detectable by either hardware or software
3. May require special processing
B. Exception handler: A code segment that processes an exception
C. Examples of exceptions
1. End of file
2. Division by 0
3. Bad input
4. Subscript out of range
5. Pointer out of range (seg fault in C)
D. Exception handling in languages that do not provide exception-handling
capabilities (e.g., C)
1. Function returns a status flag
a. program unit that calls the function must check the status
flag and take appropriate action
2. Caller passes the name of a subprogram that handles the
exception to the callee
a. The callee calls the subprogram with appropriate parameters
if an exception occurs.
b. disadvantage--if several exceptions must be handled, several
exception handlers must be provided with every call
3. Programmer uses setjmp/longjmp
e.g., if (!setjmp(buffer)) {
protected code
longjmp(buffer) // longjmp could appear in a subroutine
} else
error handling
a. setjmp stores registers, including pc, sp and fp, into a buffer
i. first call to setjmp returns 0
ii. call by longjmp restores the registers in the buffer and
returns control to the setjmp call. This time setjmp returns
1, causing control to pass to the error handling routine
b. problem: setjmp tosses all nested stack frames by restoring
the old values of sp and fp
i. any values that were held in registers by the protected
code and not written to memory are lost. Hence some
changes made by the successfully executed portion of
the protected code may be lost
ii. C's solution: programmer may use the volatile
keyword to indicate that a variable's value may
change "spontaneously", e.g., due to an I/O operation
or a concurrently executing thread
1) all C implementations must honor the volatile
keyword by immediately writing to memory any
change to a variable and always loading from
memory the value of a variable before using it.
Note that frequent loads or writes could significantly
slow down the execution of the program because loads
from memory require many more instruction cycles
then register reads and hence may stall the
processor pipeline.
E. Advantages to having built-in exception handling capabilities
1. Code can be considerably less cluttered
a. exception handling can be put in a separate section rather than
interspersed with the code that's trying to accomplish some
task
b. separating the exception code from the normal processing
code allows the flow of the normal processing code to be
uninterrupted, which makes it easier to read and understand
by a programmer
2. Encourages a user to consider all of the events that could
occur during program execution and consider how they should
be handled. The compiler can enforce this consideration by
flagging as an error any unhandled exception. In contrast,
a C programmer can blissfully ignore an error (maybe the
programmer does not even know the function called can raise
an error) and then have the program unceremoniously dump
core some undetermined amount of time after the software
has been released.
II. Exception Handling in C++
A. Exception Handlers
1. Specified using a try-catch clause
try {
// Code that is expected to raise the exception
}
catch(formal parameter) {
// A handler body
}
...
catch(formal parameter) {
// A handler body
}
catch(...) {
// An optional catch-all handler (the ... is actually C++
// syntax)
}
2. catch functions can have only a single formal parameter
a. parameter can be either a basic type or a user-defined class
b. user-defined classes provide a way of passing multiple
parameters.
c. the parameter can be an ellipsis (...) in which case the
handler is a catch-all handler
d. the formal parameter can be ignored by not providing a variable
name
3. any variable declared within the try block is de-allocated before
the catch statement is executed. Hence if you want access to
a variable used or assigned a value within the try block, make
sure you declare that variable outside the try block.
B. Binding Exceptions to Handlers
1. exceptions are raised using the throw command:
throw [expression];
Example: throw new BadOperatorException(operatorToken,
"bad operator token");
2. A throw without an operand can only appear in a handler
a. Such a throw reraises the exception and propagates it to
an outside program unit. If there is another, more general
exception handler that could handle the exception in the
same catch list as the handler that reraises this throw,
the handler will not be called.
b. Throws are matched to exception handlers whose formal parameter
matches the type of the thrown expression
i. A handler with a formal parameter of type T matches an
expression of type T, const T, T&, or const T&
ii. A handler with a formal parameter that is a class type T
matches any expression with class type T or whose
type is a derived class of T.
Example: class Exception {...}
class StackException {...}
try {
throw StackException();
}
All of the following catch statements will
catch the above exception:
a. catch (StackException e) {...}
b. catch (StackException &e) {...}
c. catch (Exception e) {...}
d. catch (Exception &e) {...}
You could put a const in front of any
of the formal parameter declarations
(e.g., catch (const StackException e)) and
each catch would still catch the exception
The following catch statement will not
catch the above exception because Exception *
expects a heap allocated object, not a stack
allocated object:
catch (Exception *e) {...}
iii. A handler with a formal parameter of *T matches only
exception objects that are allocated off the heap
Example: try {
throw new StackException();
}
catch (StackException &e) {
// does not catch the exception
...
}
catch (StackException e) {
// does not catch the exception
}
catch (StackException *e) {
// catches the exception because it matches
// the the type of the exception object,
// which is (StackException *).
}
1) It is bad form to throw an exception object that
is allocated from the heap because the catcher
has to remember to de-allocate the object.
2) It is better to throw an exception object that
is allocated from the stack because C++ will
automatically de-allocate the object.
3) If you throw an object, modify the object, and
then rethrow it, use a reference parameter to
catch the object. If you use a value parameter,
C++ will copy the original thrown
object, not the modified object
Example: throw StackException();
Right: catch (StackException &e) {
e.setValue(10); throw; }
Wrong: catch (StackException e) {
e.setValue(10); throw; }
c. The throw is matched by examining the catch statements that
follow the try statement in sequential order. That means
that exception handlers with more restrictive types should
be placed before exception handlers with more general types.
i. If the throw cannot be matched locally then it is
dynamically propagated up the call chain.
C. Continuation
1. After a handler completes execution, control flows to the first
statement following the try construct (the statement
immediately after the last catch statement).
D. Other Design Choices
1. No built-in exceptions
2. Exceptions cannot be disabled
3. Exceptions are not pre-defined but they may be declared as classes
4. System-detected exceptions cannot be handled
5. When an exception terminates a try construct, all stack-dynamic
variables declared within the scope of the try construct
are de-allocated and therefore are not accessable by the
catch statements.
E. Example:
// superclass for all array exceptions. It can catch any array
// exception that is thrown
class ArrayException {
public:
virtual void response() = 0;
};
// thrown if the program tries to create an array whose size is
// negative
class ArraySizeException : public ArrayException {
public:
int size;
ArraySizeException(int s) : size(s) {}
void response() { printf("Error: The array size of %d is negative\n",
size);
}
};
class ArrayRangeException : public ArrayException {
public:
int index; // the index that was tried
int size; // the size of the array
public:
ArrayRangeException(int i, int s): index(i), size(s) {}
void response() {}
};
// thrown if the program is unable to allocate an object of the
// requested size
class OutOfMemoryException {};
// note that all throws do not involve the allocation of memory
// off the heap. Instead they throw a stack-allocated object
class SafeArray {
protected:
int *data;
int size;
public:
SafeArray(int s = 10): size(s) {
if (size <= 0) throw ArraySizeException(size);
data = new int[s];
if (data == 0)
throw OutOfMemoryException();
for (int i = 0; i < size; i++)
data[i] = 0;
}
void set(int index, int value) {
rangeCheck(index);
data[index] = value;
}
int get(int index) {
rangeCheck(index);
return data[index];
}
protected:
void rangeCheck(int index) {
try {
if ((index < 0) || (index >= size))
throw ArrayRangeException(index, size);
}
// note the rethrow of the error condition
catch (ArrayRangeException &e) { printf("caught it\n"); throw; }
}
};
main() {
SafeArray *myArray;
try {
myArray = new SafeArray(100);
myArray->set(3, 10);
myArray->get(-1);
}
catch (const OutOfMemoryException &e) {
fprintf(stderr, "Out of memory error occurred while trying to allocate memory for myArray\n");
}
catch (const ArrayRangeException &e) {
fprintf(stderr, "index %d is out of range. Must be between 0 and %d\n", e.index, e.size);
}
for (int i = -2; i <= 100; i++) {
try {
myArray->set(i, i);
if (i == 50)
new SafeArray(-10);
}
catch (const ArrayRangeException &e) {
fprintf(stderr, "index %d is out of range. Must be between 0 and %d\n", e.index, e.size);
}
catch (ArrayException &e) {
e.response();
}
}
}
Executing this code produces the following output:
caught it
index -1 is out of range. Must be between 0 and 100
caught it
index -2 is out of range. Must be between 0 and 100
caught it
index -1 is out of range. Must be between 0 and 100
Error: The array size of -10 is negative
caught it
index 100 is out of range. Must be between 0 and 100
III. Exception Handling in Java
A. Types of Exceptions
1. All Java exceptions are descendants of the Throwable class.
Throwable provides three very helpful methods:
a. printStackTrace(): prints the standard error message for
this class plus a record of the method calls leading
up to this exception. This method should not be redefined by
subclasses.
b. getMessage(), toString(): both methods return a string object
that contains the standard error message for this class.
Typically each subclass redefines this method.
2. Java pre-defines two exception classes that are subclasses of
Throwable
a. Error: This class and its descendants are related to errors
that are thrown by the Java virtual machine, such as running
out of heap memory.
i. These exceptions are never thrown by user programs and
should never be handled by user programs
ii. Your program will be terminated when one of these errors
occurs
b. Exception: Has two descendents
i. IOException: Thrown when an input or output operation
has an error
ii. RuntimeException: All other run-time errors
1. Java provides a number of pre-defined exception classes
such as ArrayIndexOutOfBoundsException and
NullPointerException
--User-defined exceptions should subclass Exception
B. Exception Handlers
1. Same form as C++ except that all formal parameters must be
present (can't use ellipsis).
2. Ellipsis can be simulated by catching an Exception object.
a. To get the specific name of the class that was thrown,
use Java's getClass() method to get the class object and
then the getName() method to get the class's name:
catch (Exception genericObject) {
String name = genericObject.getClass().getName();
...
}
C. Exception Binding: Same as C++
D. Continuation: Same as C++
E. Differences from C++
1. All thrown objects are allocated off the heap, so all throw
statements have the form:
throw new ExceptionObject;
2. Rethrowing an object: In Java, when you rethrow an object you
must specify the object that you are rethrowning. In C++,
you simply call throw.
a. In Java since all exception objects are allocated off the
heap you do not have to worry about what happens when you
modify an exception object. The modified object is automatically
passed by the rethrow statement
Example: Java C++
catch(StackException e) { catch(StackException &e) {
e.setValue(10); e.setValue(10);
throw e; throw;
} }
3. Declaring unhandled exceptions: In Java, if a method throws an
exception that it does not handle, then it should declare
that it does not do so by using the throws statement.
a) Syntax: ret-type methName(param-list) throws exception-list
{ ... }
where exception-list is a comma separated list of exception
names
Example: char readToken() throws IOException {
return (char) System.in.read();
}
Notice that you must worry about exceptions thrown by
methods that you call. Hence the throws statement is
required both for exceptions that a method explicitly throws
and for exceptions that are generated but not handled by
methods it calls
b) Exception to the rule (pardon the pun :)): If the exception
is a subclass of Error or RuntimeException, then the method
does not need to specify the exception in the throws list
c) If you fail to specify an unhandled exception in the throw
list, and it is not subject to the above exception, the Java
compiler will generate an error message
i) In C++ it is not required for a method to declare that it
does not handle an exception (although it is optional
and can be done using the throws statement).
4) The finally clause: In Java, the finally clause provides a
way to execute a block of code regardless of whether the
try exits normally or abnormally. You might need to do this
to clean up system state, such as closing a file.
a) The finally statement is placed after the last catch statement
b) The code in a finally statement is executed even if a return
statement is executed within the try or one of the catch
clauses
c) The code in a finally statement is executed even if no catch
clause catches the exception
Example:
public class temp {
public temp() {}
public void execute() throws MyException {
try {
throw new MyException();
}
finally {
System.out.println("Leaving execute via finally");
}
}
public static void main(String [] args) {
temp foo = new temp();
try {
foo.execute();
} catch (MyException e) {
System.out.println("caught MyException in main");
}
System.out.println("Leaving main");
}
}
class MyException extends Exception {}
Executing this code generates the following output:
Leaving execute via finally
caught MyException in main
Leaving main
F. Java Example: Here's the Java code that implements the same C++
program shown earlier. Notice that I had to add an additional
catch clause to the first try statement since Java requires me
to handle all exceptions that are declared to be thrown by a
method.
class SafeArray {
int data [];
int size = 10;
// We do not have to declare that SafeArray throws an
// OutOfMemoryException since it is a subclass of RuntimeException
public SafeArray(int s) throws ArraySizeError {
size = s;
if (size <= 0) throw new ArraySizeError(size);
data = new int[size];
if (data == null)
throw new OutOfMemoryException();
for (int i = 0; i < size; i++)
data[i] = 0;
}
public void set(int index, int value) throws ArrayRangeError {
rangeCheck(index);
data[index] = value;
}
public int get(int index) throws ArrayRangeError {
rangeCheck(index);
return data[index];
}
void rangeCheck(int index) throws ArrayRangeError {
try {
if ((index < 0) || (index >= size))
throw new ArrayRangeError(index, size);
}
catch (ArrayRangeError e) {
System.err.println("caught it");
throw e;
}
}
public static void main(String args[]) {
SafeArray myArray = null;
try {
myArray = new SafeArray(100);
myArray.set(3, 10);
myArray.get(-1);
}
catch (OutOfMemoryException e) {
System.err.println("Out of memory error occurred while trying to allocate memory for myArray\n");
System.exit(1);
}
catch (ArrayRangeError e) {
System.err.println("index " + e.index + " is out of range. Must be between 0 and " + e.size);
}
catch (ArrayError e) { // catches the ArraySizeError exception
// thrown by SafeArray
e.response();
System.exit(1);
}
for (int i = -2; i <= 100; i++) {
try {
myArray.set(i, i);
if (i == 50)
new SafeArray(-10);
}
catch (ArrayRangeError e) {
System.err.println("index " + e.index + " is out of range. Must be between 0 and " + e.size);
}
catch (ArrayError e) {
e.response();
}
}
}
}
abstract class ArrayError extends Exception {
abstract public void response();
}
class OutOfMemoryException extends RuntimeException {}
class ArraySizeError extends ArrayError {
public int size;
public ArraySizeError(int s) { size = s; }
public void response() {
System.err.println("Error: The array size of " + size + " is negative");
}
}
class ArrayRangeError extends ArrayError {
public int index;
public int size;
public ArrayRangeError(int i, int s) {
index = i;
size = s;
}
public void response() {}
}
G. Another Java Example: Stack