CS 342 Lecture -*- Outline -*- * Exception Handling Problems with procedural abstractions May be partial maps e.g., cannot divide by zero read past end of file, or pop an empty stack May need to return "in different ways" e.g., instr$execute may halt or find a run-time error ** What is an exception? Exception: an unusual situation, that may require special processing. when it occurs the exception is said to be signalled (or raised) Exception handler: the special processing. e.g., return maximum integer, ask user for advice, exit a loop, ignore it, terminate program after closing files, etc. ** Is a language-defined exception handling mechanism necessary? Ways one might handle exceptions otherwise: *** require that caller insure that they don't happen pop = proc(s: stack) % REQUIRES: s is not empty % MODIFIES: s % EFFECT: remove the top element of s -problems: software less robust (insecure) makes debugging harder -however, sometimes this is useful for efficiency. But better way to ensure requirements is to use data abstractions *** status codes each procedure turns into a function that passes back a status code indicating success or error status := stream$getc(s,c) if status = EXCEPTION then % exceptional processing. end % normal processing -problems: inefficient in normal case no warning if users forget to check code (insecure) code for exceptional cases gets in the way (readability) hard to nest functional interfaces properly and check for exceptions at proper times. *** label parameters (gotos) subroutine jumps to exceptional label if needed possible in FORTRAN, Algol 60 -problems: slightly less efficient in normal case hard to communicate other information about exception (e.g., reason couldn't open file) *** pass procedure to be called in case of exception -problems: doesn't help unless that procedure can alter flow of control ** Trade-offs in langauge-defined exception mechanism *** benefits: more expressive, more secure more efficient (as we'll see) *** problems: more complex language ** Trade-offs in use of exceptions (exceptions vs. precondition) most of the time more secure to have an exception e.g. stack$pop sometimes too expensive to check ==> use precondition e.g., binary search of an array, checking that array is sorted takes O(n) time ** Exceptions in CLU *** Where can exceptions be handled? in routine where exception detected? (not helpful) caller? (certainly) caller of caller? (makes it hard to read code, hard to have abstractions) **** single level model (CLU) only caller can handle exceptions in CLU **** multiple level model (Ada) Ada allows any routine on the dynamic chain to handle exception exceptions declared separately from procedures (names statically scoped) not part of procedural abstraction in Ada when exception is raised, search dynamic chain of blocks for handler (handlers dynamically scoped) exceptions continue to propogate, terminating activations until handled but not propogated outside program (or task). -advantage: no interactions with type system can handle exceptions that called subprogram does not know about (if has generic subprogram parameter) -disadvantage: less secure because caller does not know about the entire interface of callee *discuss this, consider: methodology (abstraction) *** Declarations part of procedure header (and procedure types) ----------- pop = proc(s: stack) signals(empty) ----------- *** Interaction with type checking (discuss) f: proctype() signals(foo, bar) % suppose fbb has proctype() signals(foo, bar, baz) % suppose g has proctype() signals(foo) % which of following are/should be legal? f := fbb f := g *** Raising an exception (signal statement) ----------------------- if rep$empty(s) then signal empty end ---------------------- -can only raise exceptions with names listed in header **** Effect of raising an exception ***** termination model (CLU) -when exception is raised, called procedure terminates, handler (if any) in caller is run, if no handler, caller terminates with failure exception ***** resumption model (Mesa, Ceder, ...) allow procedure to be resumed (from after the signal) if problem can be "fixed" handler thought of as implicit procedure parameter costs: more difficult to define, implement less efficient if have procedures as first-class objects can simulate *** Exceptions handlers may be attached to any statement not expressions! propogate out to smallest surrounding statement with an attached handler ----------- stack$pop(foo(mystack)) except when empty: % handler code stream$putl(stderr, "popped empty stack") when foo_ex(i: int) stream$putl(stderr, "foo exception: " || int$unparse(i)) when bar, baz (*): % ignore exception results, if any, of these % bar and baz may have different number and types of exception results others: % all other exceptions handled here but results are lost end % flow continues here ----------- **** scope of exception handlers exception handlers bound to exception names *statically* signals handled by handlers that from signaller's point of view are dynamically bound *discuss the above compiler can determine for each invocation what handlers can be invoked exception names are not scoped as are identifiers know exact set of exceptions a call can raise (since declared) can nest exception handlers **** Finding exception handlers look outward among surrounding statements starting at stmt where call was made that raised it if has attached handler with same name, use that if not, look at surrounding statement, etc. **** Begin - end blocks often used to group statements for exception handling (so don't have to attach same handler to 2 or more statements) ------------- begin check_format(s) check_for_illegal_opcodes(s) end except when syntax_error: % ... end ------------- **** Unhandled exceptions Exceptions that are not handled by caller make caller raise "failure" with a string argument that indicates the name of the unhandled exception (should terminate program or go into debugger) only language defined exception not listed in procedure heading * discuss this: regularity? does it ever make sense to try to handle failure? system support for collecting information about failure debugging **** propogation of exceptions Exceptions can be easily propagated to caller by "resignal" statement --------------- p[i] := instruction$parse(in_str) resignal syntax_error --------------- (this is syntactic sugar) ** Exit statement for local flow of control exit jumps to surrounding exception handler in local procedure (implemented by a goto) structured? ------------ lt = proc(s1, s2: setoft) returns(bool) % EFFECT: Is s1 a proper subset of s2? begin % suppose we can't find out the sizes of s1 or s2 for e: t in setoft$elements(s2) do if ~setoft$is_member(s1, e) then exit okay end %********here end % every element in s2 is in s1 return(false) end except when okay : end % there is some element, e', in s2 such that ~setoft$is_member(s1, e') for e: t in setoft$elements(s1) do if ~setoft$is_member(s2, e) then return(false) end end % for all elements, e, in s1, setoft$is_member(s2, e) and % there is some element, e', in s2 such that ~setoft$is_member(s1, e') return(true) end lt ------------ ** Implementation (may be omitted) from Liskov & Snyder's IEEE Trans. on Soft. Engin, Nov, 1979 (these implementation considerations did not affect the design) *** Goals: no slowdown for normal case fast handling of exceptions reasonable use of space *** Actions: 1. discard AR of called proc (save execption results) 2. locate handler in caller 3. adjust caller's AR to reflect possible termination of expressions and statements 4. copy the result objects into callers AR (if any) 5. transfer control to handler compiler has information to determine 2 and 3 *** branch table method follow each invocation with a branch table containing one entry for each exception (+failure) return transfers control to after branch table signals jump indirect through branch table advantages: fast disadvantages: lots of space *** handler table method single table per procedure entry for each handler in the procedure containing list of exceptions handled, pair of addresses defining scope of handler address of the handler's code indicator of whether return objects used return as normal signal: search exception table for handler ** Summary Lots of languages have data abstraction nowadays, few have so nice an exception handling mechanism Makes a big difference in writing robust programs