Programming Languages
Exceptions
CSCI-GA.2110-003 Fall 2020
Exceptions
General mechanism for handling abnormal conditions
One way to improve robustness of programs is to handle errors. How can we do this?
We can check the result of each operation that can go wrong (e.g., popping from a stack, writing to a file, allocating memory).
Unfortunately, this has a couple of serious disadvantages:
1. it is easy to forget to check
2. writing all the checks clutters up the code and obfuscates the common
case (the one where no errors occur)
Exceptions let us write clearer code and make it easier to catch errors.
2 / 26
Predefined exceptions in Ada
¡ö Defined in Standard:
¡ô Constraint_Error : value out of range
¡ô Program_Error : illegality not detectable at compile-time:
unelaborated package, exception during finalization, etc.
¡ô Storage_Error : allocation cannot be satisfied (heap or stack)
¡ô Tasking_Error : communication failure
¡ö Defined in Ada.IO_Exceptions:
¡ô Data_Error,End_Error,Name_Error,Use_Error,Mode_Error, Status_Error, Device_Error
3 / 26
Handling exceptions
Any begin-end block can have an exception handler:
procedure Test is X: Integer := 25; Y: Integer := 0;
begin
X := X / Y;
exception
when Constraint_Error =>
Put_Line(“did you divide by 0?”); when others =>
Put_Line(“out of the blue!”); end;
4 / 26
A common idiom
function Get_Data return Integer is X: Integer;
begin loop
end;
begin Get(X);
return X;
— if got here, input is valid, — so leave loop
exception
when others =>
end; end loop;
Put_Line(“input must be integer, try again”);
— will restart loop to wait for a good input
5 / 26
User-defined Exceptions
package Stacks is Stack_Empty: exception; …
end Stacks;
package body Stacks is
procedure Pop (X: out Integer;
begin
if Empty(From)
From: in out Stack) is
then raise Stack_Empty;
else … end Pop;
…
end Stacks;
6 / 26
The scope of exceptions
¡ö an exception has the same visibility as other declared entities: to handle an exception it must be visible in the handler (e.g., caller must be able to see Stack_Empty).
¡ö an others clause can handle unnamed exceptions
when others =>
Put_Line(“disaster somewhere”); raise; — propagate exception,
— program will terminate
7 / 26
Exception run-time model
How to propagate an exception:
1. When an exception is raised, the current sequence of statements is abandoned (e.g., current Get and return in example)
2. Starting at the current frame, if we have an exception handler, it is executed, and the current frame is completed.
3. Otherwise, the frame is discarded, and the enclosing dynamic scopes are examined to find a frame that contains a handler for the current exception (want dynamic as opposed to static scopes because those are values that caused the problem).
4. If no handler is found, the program terminates. Note: The current frame is never resumed.
8 / 26
Exception information
¡ö an Ada exception is a label, not a value: we cannot declare exception variables and then assign to them
¡ö but an exception occurrence is a value that can be stored and examined
¡ö an exception occurrence may include additional information: source
location of occurrence, contents of stack, etc.
¡ö predefined package Ada.Exceptions contains needed machinery
9 / 26
Ada.Exceptions (std libraries)
package Ada.Exceptions is
type Exception_Id is private;
type Exception_Occurrence is limited private;
function Exception_Identity (X: Exception_Occurrence) return Exception_Id;
function Exception_Name (X: Exception_Occurrence) return String;
procedure Save_Occurrence
(Target: out Exception_Occurrence;
Source: Exception_Occurrence);
procedure Raise_Exception (E: Exception_Id;
…
end Ada.Exceptions;
Message: in String := “”)
10 / 26
Using exception information
begin …
exception
when Expected: Constraint_Error =>
— Expected has details
Save_Occurrence(Event_Log, Expected);
when Trouble: others => Put_Line(“unexpected ” &
Exception_Name(Trouble) & ” raised”);
Put_Line(“shutting down”);
raise; end;
11 / 26
Exceptions in C++
¡ö similar runtime model,…
¡ö but exceptions are bona-fide values,
¡ö handlers appear in try/catch blocks
try { some_complex_calculation();
} catch (const RangeError& e) {
// RangeError might be raised // in some_complex_calculation cerr << "oops\n";
} catch (const ZeroDivide& e) {
// same for ZeroDivide
cerr << "why is denominator zero?\n"; }
12 / 26
Defining and throwing exceptions
The program throws an object. There is nothing needed in the declaration of the type to indicate it will be used as an exception.
struct ZeroDivide {
int lineno;
ZeroDivide (...) { ... } // constructor ...
};
...
if (x == 0)
throw ZeroDivide(...);
// call constructor // and go
13 / 26
Exceptions and inheritance
A handler names a class, and can handle an object of a derived class as well:
class class class class
Matherr { }; // a bare object, no info Overflow : public Matherr {...}; Underflow : public Matherr {...}; ZeroDivide : public Matherr {...};
try { weatherPredictionModel(...);
} catch (const Overflow& e) {
// e.g., change parameters in caller
} catch (const Matherr& e) {
}
// Underflow, ZeroDivide handled here
} catch (...) {
// handle anything else (ellipsis)
14 / 26
Rethrowing exceptions in C++
When an exception can be only partially handled it should be rethrown.
¡ö
Rethrow the same object, not a new object. This preserves important information such as the stack trace.
Accomplished with parameterless throw.
¡ö
void test(string en) { try {
}
if (!en.compare("First")) { throw MyFirstException();
} else {
throw MySecondException();
}
} catch (MyException& e) {
// partially handle here
throw; }
15 / 26
Exceptions in Java
¡ö Model and terminology similar to C++:
¡ô exceptions are objects that are thrown and caught
¡ô try blocks have handlers, which are examined in succession
¡ô a handler for an exception can handle any object of a derived class
¡ö Differences:
¡ô all exceptions are extensions of predefined class Throwable
¡ô checked exceptions are part of method declaration
¡ô the finally clause specifies clean-up actions
¡ö in C++, cleanup actions are idiomatically done in destructors
¡ô try-with-resourcesautomaticallyreleasesresourcesuponexit from the try block.
16 / 26
Exception class hierarchy
Throwable
Error Exception
¡ö System errors are extensions of Error and RuntimeException.
¡ö System errors are unchecked exceptions. Examples:
RuntimeException
ClassCastException, NullPointerException, OutOfMemoryError. ¡ö All other exception classes are checked. These exceptions must be either
handled or declared in the method that throws them; this is checked by the compiler.
17 / 26
Java ¡°throws¡± declaration
Checked exceptions must be declared with throws if the exception could be emitted by the method:
public void replace (String name,
Object newValue) throws NoSuch
{
Attribute attr = find(name);
if (attr == null) throw new NoSuch(name); newValue.update(attr);
}
To avoid declaring NoSuch, the programmer must catch NoSuch.
This exception declaration requirement alerts programmers of the (checked) exceptions that might be thrown when a given method is called.
18 / 26
Mandatory cleanup actions
Some cleanups must be performed whether the method terminates normally or throws an exception.
public void parse (String file) throws IOException { BufferedReader input =
}
new BufferedReader(new FileReader(file)); try {
while (true) {
String s = input.readLine();
if (s == null) break;
parseLine(s); // may fail somewhere
}
} finally {
if (input != null) input.close(); } //regardless of how we exit
19 / 26
Java try-with-resources
¡ö An alternative to finally, as of Java 7
¡ö Resources declared at the top of the try block are automatically closed
regardless of outcome
¡ö Resources only visible within try (not catch)
¡ö Resources must implement the AutoCloseable interface
¡ö Similar to the C# using clause and the IDisposable interface
try (FileInputStream inputStream =
new FileInputStream(f);) {
// use the inputStream to read a file
} catch (FileNotFoundException e) { log.error(e);
} catch (IOException e) { log.error(e);
}
20 / 26
Exceptions in ML
¡ö runtime model similar to Ada/C++/Java
¡ö exception is a single type (like a datatype but dynamically extensible)
¡ö declaring new sorts of exceptions:
exception StackUnderflow
exception ParseError of { line: int, col: int }
¡ö raising an exception:
raise StackUnderflow
raise (ParseError { line = 5, col = 12 })
¡ö handling an exception:
expr1 handle pattern => expr2
If an exception is raised during evaluation of expr1, and pattern matches that exception, expr2 is evaluated instead
21 / 26
A closer look
exception DivideByZero
fun f i j = if j <> 0
then i div j
else raise DivideByZero
(f 6 2
handle DivideByZero => 42)
(* evaluates to 3 *) (* evaluates to 42 *)
(f 4 0
handle DivideByZero => 42)
Typing issues:
¡ö the type of the body and the handler must be the same
¡ö the type of a raise expression can be any type
(whatever type is appropriate is chosen)
22 / 26
Exception Best Practices
¡ö Throw the most specific type possible (e.g. not Exception)
¡ö Catch the most specific exception first
¡ö Populate exception objects with useful information
¡ö Do not ignore exceptions when catching them
¡ö Do not use exception handling as an ordinary control structure
¡ö Document the exceptions thrown by any given method (if not Java)
¡ö Log exceptions for later debugging (unless rethrowing)
23 / 26
Call-with-current-continuation
Available in Scheme and SML/NJ; usually abbreviated to call/cc. In Scheme, it is called call-with-current-continuation.
A continuation represents the computation of ¡°rest of the program¡±.
call/cc takes a function as an argument. It calls that function with the current continuation (which is packaged up as a function) as an argument. If this continuation is called with some value as an argument, the effect is as if call/cc had itself returned with that argument as its result.
The current continuation is the ¡°rest of the program¡±, starting from the point when call/cc returns.
(call/cc (lambda (c) (c 5))) ;; returns 5 (call/cc (lambda (c) 5)) ;; so does this (call/cc (lambda (c) (+ 1 (c 5)))) ;; ditto
24 / 26
The power of continuations
We can implement many control structures with call/cc: ¡ö return:
(lambda (x)
(call/cc (lambda (ret)
)) )
¡ö goto:
…
(ret 76) …
;; body of function
;; call continuation with result
(define cont #f) (define (object)
(let ((i 0))
(call/cc (lambda (k) (set! cont k)))
; The next time cont is called, we start here.
(set! i (+ i 1)) i))
25 / 26
Exceptions via call/cc
Exceptions can also be implemented by call/cc: ¡ö Need global stack: handlers
¡ö For each try/catch:
(call/cc (lambda (k) (begin
¡ö For each raise:
(push handlers (lambda () (begin
(try-block)
(pop handlers))))
((top handlers)) ; call the top function on ; the handlers stack
(pop handlers) (catch-block)
(k ()))))
26 / 26