Exceptions

CSC 310 - Programming Languages

Outline

  • Why exceptions?

  • Syntax and informal semantics

  • Semantic analysis

  • Operational semantics

  • Code generation

  • Runtime system support

Exceptional Motivation

  • “Classroom” programs are written with optimistic assumptions.

  • Real-world programs must consider “exceptional” situations:

    • Resource exhaustion (disk full, out of memory, …)
    • Invalid input
    • Errors in the program (null pointer dereference)

Approaches To Error Handling

  • Two ways of dealing with errors:

    • Handle them where you detect them

    • Let the caller handle the errors:

      • The caller has more contextual information

      • But, we must tell the caller about the error

Error Return Codes

  • The callee can signal the error by returning a special return value or error code:

    • Must not be one of the valid outputs
    • Must be agreed upon beforehand (that is, in API)
  • The caller promises to check the error return and either:

    • Correct the error, or
    • Pass it on to its own caller

Error Return Codes

  • It is sometimes hard to select return codes

    • Example: divide(num: Double, denom: Double) : Double {...}
  • How many of you always check errors for:

    • malloc(int)?
    • open(char *)?
    • close(int)?
  • Easy to forget to check error return codes

Exceptions

  • Exceptions are a language mechanism designed to allow:

    • Deferral of error handling
    • Without (explicit) error codes
    • And without (explicit) error return code checking

Adding Exceptions to Cool

  • We extend the language of expressions:

    \[\begin{array}{rcl} expr &::=& \texttt{throw} \; expr\\ &|& \texttt{try} \; expr \; \texttt{catch} \; \texttt{ID} : \texttt{TYPE} \; \texttt{=>} \; e_2 \end{array} \]

  • (Informal) semantics of throw \(expr\)

    • Signals an exception

    • Interrupts the current evaluation and searches for an exception handler up the activation tree

    • The value of \(expr\) is an exception parameter and can be used to communicate details about the exception

Adding Exceptions to Cool

  • (Informal) semantics of \(\texttt{try} \; expr \; \texttt{catch} \; \texttt{ID} : \texttt{TYPE} \; \texttt{=>} \; e_2\)

    • \(expr\) is evaluated first

    • If \(expr\)’s evaluation terminates normally with \(v\), then \(v\) is the result of the entire expression

    • Otherwise, (\(expr\)’s evaluation terminates exceptionally)

      • If the exception parameter is of type \(\leq \texttt{TYPE}\), then

        • Evaluate \(e_2\) with \(\texttt{ID}\) bound to the exception parameter

        • The result of evaulating \(e_2\) becomes the result of the entire expression

      • Else the entire expression terminates exceptionally

Typing Exceptions

  • We must extend the Cool typing judgment \[O, M, C \vdash e : T\]

  • We will start with the rule for try:

    \[\frac{ \begin{array}{l} O,M,C \vdash e_0 : T_0\\ O[T/x],M,C \vdash e_1 : T_1\\ \end{array}} {O,M,C \vdash \texttt{try} \; e_0 \; \texttt{catch} \; x : T \; \texttt{=>} e_1 : lub(T_0, T_1)} \]

Typing Exceptions

  • The type of an expression:

    • Is a description of the possible return values, and

    • Is used to decide what contexts we can use the expression

  • throw does not return to its immediate context but directly to the exception handler

  • The same throw e is valid in any context:

    • Example: if throw e then (throw e) + 1 else (throw e).foo()
  • As if throw e has any type

Typing Exceptions

  • Rule:

    \[\frac{O,M,C \vdash e : T_1}{O, M, C \vdash \texttt{throw} \; e : T_2}\]

  • As long as \(e\) is well typed, throw \(e\) is well typed with any type needed in the context, that is, \(T_2\) is unbound.

  • This is convenient because we want to be able to signal errors from any context

Operational Semantics

  • Several ways to model the behavior of exceptions

  • A generalized value is

    • Either a normal termination value, or

    • An exception with a parameter value

      \[g ::= Norm(v) \; | \; Exc(v)\]

  • Given a generalized value, we can:

    • Determine if it is normal or exceptional return, and

    • Extract the return value or the exception parameter

Operational Semantics of Exceptions

  • The existing rules change to use \(Norm(v)\)

  • Example:

    \[\frac{ \begin{array}{l} so, E, S \vdash e_1 : Norm(Int(n_1)), S_1\\ so, E, S \vdash e_2 : Norm(Int(n_2)), S_2 \end{array}} {so, E, S \vdash e_1 + e_2 : Norm(Int(n_1 + n_2)), S_2} \]

Operational Semantics of Exceptions

  • throw returns exceptionally

    \[\frac{ so, E, S \vdash e : Norm(v), S_1} {so, E, S \vdash \texttt{throw} \; e : Exc(v), S_1} \]

  • What if the evaluation of \(e\) itself throws an exception?

    \[\frac{ so, E, S \vdash e : Exc(v), S_1} {so, E, S \vdash \texttt{throw} \; e : Exc(v), S_1} \]

Operational Semantics of Exceptions

  • All existing rules are changed to propagate the exception:

\[\frac{ \begin{array}{l} so, E, S \vdash e_1 : Exc(v), S_1\\ \end{array}} {so, E, S \vdash e_1 + e_2 : Exc(v), S_1} \]

\[\frac{ \begin{array}{l} so, E, S \vdash e_1 : Norm(Int(n_1)), S_1\\ so, E, S \vdash e_2 : Exc(v), S_2 \end{array}} {so, E, S \vdash e_1 + e_2 : Exc(v), S_2} \]

Operational Semantics of Exceptions

  • The rules for try expressions (multiple rules similar to conditionals):

    \[\frac{so, E, S \vdash e_0 : Norm(v), S_1} {so, E, S \vdash \texttt{try} \; e_0 \; \texttt{catch} \; x : T \; \texttt{=>} \; e_1 : Norm(v), S_1} \]

  • If \(e\) terminates exceptionally, then we must check whether it terminates with an exception parameter of type \(T\) or not.

Operational Semantics of Exceptions

  • If \(e\) does not throw the expected exception:

    \[\frac{ \begin{array}{l} so, E, S \vdash e_0 : Exc(v), S_1\\ v = X(...)\\ X \nleq T \end{array}} {so, E, S \vdash \texttt{try} \; e_0 \; \texttt{catch} \; x : T \; \texttt{=>} \; e_1 : Exc(v), S_1} \]

Operational Semantics of Exceptions

  • If \(e\) does throw the expected exception:

    \[\frac{ \begin{array}{l} so, E, S \vdash e_0 : Exc(v), S_1\\ v = X(...)\\ X \leq T\\ l_{new} = newloc(S_1)\\ so, E[l_{new}/x], S_1[v/l_{new}] \vdash e_1 : g, S_2 \end{array}} {so, E, S \vdash \texttt{try} \; e_0 \; \texttt{catch} \; x : T \; \texttt{=>} \; e_1 : g, S_2} \]

Operational Semantics of Exceptions Notes

  • Our semantics is precise

  • But, is not very clean; it has two or more versions of each original rule

  • It is not a good recipe for implementation

    • It models exceptions as “compiler-inserted propagation of error return codes”

    • There are much better ways of implementing exceptions

  • There are other semantics that are cleaner and model better implementations (not within the scope of this course)

Code Generation for Exceptions

  • One method is suggested by the operational semantics

  • Simple to implement

  • But not very good

  • We pay a cost at each call/return (often)

  • Even though exceptions are rare (exceptional)

  • A good engineering principle: “don’t pay often for something that you use rarely”, that is, optimize the common case.

Long Jumps

  • A long jump is a non-local goto:

    • In one shot you can jump back to a function in the caller chain (bypassing many intermediate frames)

    • A long jump can “return” from many frames at once

  • Long jumps are a commonly used implementation scheme for exceptions

  • Disadvantage: (minor) performance penalty at each try

Implementing Exceptions with Tables

  • We do not want to pay for exceptions when executing a try, only when implementing a throw

    \(cgen(try \; e_1 \; catch \; e_2) =\)
    \(\qquad cgen(e_1)\) ; code for the try block
    \(\qquad\)jump end_try
    L_catch:
    \(\qquad cgen(e_2)\) ; code for the catch block
    end_try:
    \(\qquad\) \(\cdots\)
    \(cgen(throw)\)
    \(\qquad\)jump runtime_throw ; the trick

Implementing Exceptions with Tables

  • The normal execution proceeds at full speed

  • When a throw is executed we use a runtime function that finds the correct catch block

  • For this to be possible, the compiler produces a table where each catch block maps to the corresponding instructions

Implementing Exceptions with Tables

  • The runtime_throw does a table lookup to determine which catch handler to invoke

  • Advantage: no cost except if an exception is thrown

  • Disadvantage: Tables take up space

  • The Java Virtual Machine uses this scheme

Summary

  • Real-world programs must have error-handling code. Errors can be handled where they are detected or the error can be propagated to a caller.

  • Passing special error return codes is itself error-prone.

  • Exceptions are a formal and automated way of reporting and handling errors. Exceptions can be implemented efficiently and described formally.