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 fragment 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 function that handles the
		exception to the callee
	   a. The callee calls the function 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., result = setjmp(buffer);
		if (!result)
	   	     protected code
		     longjmp(buffer, error_code) // longjmp could appear in a subroutine
		 } else
		     error handling based on error_code
	   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
		   error_code, 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.
        4. Signal handler: Certain system-generated exceptions, such
	   as seg faults and bus errors, are expressed via signals.

	    a. It is possible to provide signal handlers to catch signals.
	    b. A signal handler is a function that takes an int parameter denoting
		the number of the signal.
	    c. When the signal handler returns, the behavior of the process is
		often undefined. Often the control picks up where the signal
		occurred and the offending instruction is re-executed. If the
		problem causing the signal has not been fixed, the signal will
		be re-generated, thus causing an infinite series of handler calls.
	    d. You register a signal handler with the kernel by calling the
		signal() function with the number of the signal and a pointer to
		the signal handler

                void sig_handler(int signal_num) {
		    if (signal_num == SIGSEGV)
			printf("received SIGSEGV\n");
		}

		int main() {
		    signal(SIGSEGV, sig_handler);
		}

	    e. It is inadvisable to write a signal handler because it is very difficult
		to fix the problem, and hence your program will seem to go into an
		infinite recursion unless the signal handler terminates the program.

    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).

	2. If the exception was thrown out of the function and up the
		call stack, then all stack frames up to the catching
		function are popped off the stack and any stack-allocated
		local variables/parmeters are de-allocated.

		a. need to be careful that heap-allocated objects get
		   de-allocated. This may require catching an exception
		   you might otherwise not catch. For example:

		   void g() {
			throw std::exception();
		   }

		   void f() {
			int *x = new int;
			*x = 3;
			g();
			delete x;
		   }

		   int main() {
			try {
			    f();
			} catch (std::exception e) { ... }
		   }

		   Because f() did not catch the exception thrown by g(),
		   the memory pointed to by x never gets deleted. To
		   fix this problem, you would need to put a try/catch
		   clause into f() and have the catch clause delete x.

    D. Other Design Choices

	1. The standard template library provides a std::exception class
	    a. Included using <exception> 
	    b. Contains a virtual what method that returns a char string
		denoting the type of exception:

		virtual const char* what();

		For the exception class it returns the string "std::exception".

	    c. There are several subclasses of interest, all of which can
		be caught by your program
	
		exception
		    bad_alloc: thrown by new on allocation failure (this is
			why you don't check to see if new returns NULL--it
			does not--it throws a bad_alloc error)
		    bad_cast: thrown by dynamic_cast on failure
		    runtime_error: thrown when certain runtime conditions
			occur
			underflow_error: thrown when an arithmetic underflow
				occurs
			overflow_error: thrown when an arithmetic overflow
				occurs
			system_error: thrown by the OS for certain errors
				and includes an error code that is typically
				specific to that platform and is non-portable
                    logic_error
			out_of_range: thrown by vector, queue, dequeue,
			   and some other std classes on out of range errors
			length_error: thrown by vector or string on resizing
			   error

	2. Exceptions cannot be disabled
	3. System-detected exceptions cannot be handled except those 
		thrown as system_error (e.g., seg fault and bus error
		cannot be handled via this exception handling approach
		because these exceptions do not generate C++
		exceptions but instead generate signals)
	4. You can specify which exceptions get thrown out of a function
		without being handled using the throw keyword. For example:

		void f() throw (DivisionByZeroException) {...}

	    a. The throw statement was deprecated in the C11 standard and
		should not be used any more
	    b. The C++ compiler does not verify whether or not you 
		throw other exceptions out of the function, so this option
		is primarily for documentation purposes.
        5. Be careful when you throw out of a constructor. When you throw out 
	        of a constructor:

                a. the destructor for the object being constructed will not 
			be called.
                b. destructors for member objects contained in that object's 
			class will be called
    		c. the memory for the object that was being constructed will 
			be freed. For example, if the object was being 
			allocated off the heap, the memory will be 
			automatically returned to the heap.

		The fact that the destructor is not called means that any
		resources you allocated in the constructor before the call
		will not be freed. Specifically that means that any memory
		you allocated will not be freed. That means that either the
		exception handler must free any allocated resources or that
		the resources should be allocated in the member objects, and
		since the member object's destructors are called, the resources
		will get freed. For example:

		class Stack {
		    protected:
		        vector data;
		    public:
			Stack(int size) : data(size) {
			    throw exception();
			}
		};

		Since data is a member object, its destructor gets called
		and its memory is automatically de-allocated when the
		throw occurs.

    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 ArrayOutOfBoundsException : public ArrayException {
	    public:
	        int index;    // the index that was tried
	        int size;     // the size of the array
	    public:
	        ArrayOutOfBoundsException(int i, int s): index(i), size(s) {}
	        void response() {}
	};

	// 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];
		    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 ArrayOutOfBoundsException(index, size);
		  }
	          // note the rethrow of the error condition
		  catch (ArrayOutOfBoundsException &e) { printf("caught it\n"); throw; }
		}
	};

	int main() {
	    SafeArray *myArray;

	    try {
		myArray = new SafeArray(100);
		myArray->set(3, 10);
		myArray->get(-1);
	    }
	    catch (const bad_alloc &e) {
		fprintf(stderr, "Out of memory error occurred while trying to allocate memory for myArray\n");
	    }
	    catch (const ArrayOutOfBoundsException &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 ArrayOutOfBoundsException &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. Try-with-resources statement: In addition to a normal try, you can
	have the try statement declare one or more resources. A resource is an object that must be closed after the program is finished with it. The try-with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.lang.AutoCloseable, which includes all objects which implement java.io.Closeable, can be used as a resource.

	1. Such a statement is called a try-with-resources statement.

	2. Example: try (Scanner buffer = new Scanner(line)) { ... }

    B. 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. It has numerous exceptions declared
			as subclasses, including EOFException and 
			FileNotFoundException

		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

    C. 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();
		    ...
		}

    D. Exception Binding: Same as C++

    E. Continuation: Same as C++

    F. 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 throw 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

    G. 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 in main since Java requires me
	to handle all exceptions that are declared to be thrown by a
	method. In the C++ example I did not handle the ArraySizeError
	exception in the first try statement but in Java I must.

         class SafeArray {
	    int data [];
	    int size = 10;
	    
	    // Note that we assume the new operator succeeds. If it does
	    // not, Java throws an OutOfMemoryError. Note that this is
	    // a subtype of Error, which means your program should not
	    // attempt to handle it--your program will be terminated

	    public SafeArray(int s) throws ArraySizeError {
		size = s;
		if (size <= 0) throw new ArraySizeError(size);
		data = new int[size];
		for (int i = 0; i < size; i++)
		    data[i] = 0;
	    }

	    public void set(int index, int value) throws ArrayOutOfBoundsError {
		rangeCheck(index);
		data[index] = value;
	    }
	    public int get(int index) throws ArrayOutOfBoundsError {
		rangeCheck(index);
		return data[index];
	    }
	    
	    void rangeCheck(int index) throws ArrayOutOfBoundsError {
		try {
		    if ((index < 0) || (index >= size))
			throw new ArrayOutOfBoundsError(index, size);
		}
		catch (ArrayOutOfBoundsError 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 (ArrayOutOfBoundsError 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 (ArrayOutOfBoundsError 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 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 ArrayOutOfBoundsError extends ArrayError {
	    public int index;
	    public int size;
	    public ArrayOutOfBoundsError(int i, int s) {
		index = i;
		size = s;
	    }
	    public void response() {}
	}
	
    H. Another Java Example: Stack