CS 541 Meeting -*- Outline -*- * Execptions, esp. in Java refs: Gosling, Joy, Steele, The Java Language Specification, Addison Wesley, 1996 Arnold, Gosling, The Java Programming Language, Addison Wesley, 1996 J.B. Goodenough, "Exception Handling: Issues and a Proposed Notation", CACM 18(12):683-696, Dec. 1975. B. Liskov and A. Snyder, "Exception Handling in CLU", IEEE Transactions on Software Engineering, SE-5(6):546-558, Nov. 1979. ** the problem Suppose we want to make a "bullet-proof" abstraction, e.g., one that isn't called from trusted code, like Unix system calls. ------------------------------------------ EXCEPTION HANDLING: THE PROBLEM What to do when a procedures is called: - outside its intended domain e.g., Math.sqrt(-7.4) - when a resourse isn't available e.g., new long[999999999999999] - when the result isn't "normal" or when an expected failure occurs e.g., file.read() robot.moveForward(3) table.search(foo) def: an *exception* is ------------------------------------------ Q: other circumstances? ... an a way for a procedure to return besides the "normal" way often used for unusual situations that need special treatment NOT just an "error" Q: Is it worth writing code to handle stack overflow, running out of memory or other "unexpected" problems? maybe not, except at a topmost level... how can you expect something unexpected? ** potential solutions Q: What's usually done in C (e.g., the standard libraries or the Unix system calls)? return a "condition code", or set a global variable *** status codes ------------------------------------------ STATUS CODES: A SOLUTION? - status codes /* in C */ #include ... fp = fopen(argv[1]); if (fp == NULL) { perror("can't open file!\n"); exit(1); } ------------------------------------------ Q: What's the problem with that? inefficient in the normal case no warning if users forget to check code (insecure) hard to nest functional interfaces properly and check for exceptions at proper times. the code for handling conditions gets in the way of normal processing, or if it's postponed errors may accumulate people don't check the condition codes (or globals), (how many check the return code from printf? fclose?) many abstractions don't pass the problems along but ignore them and the caller of such an abstraction can't help it ------------------------------------------ SOME PARANOID C CODE #include int main(int argc, char * argv[]) { FILE * fp; int i,j; if (argc < 1) { if (fprintf(stderr, "no file name!\n") < 0) { exit(3); } exit(2); } fp = fopen(argv[1], "r"); if (fp == NULL) { perror("can't read file!\n"); exit(4); } if (fscanf(fp, "%d %d", &i, &j) != 2) { perror("bad format file!\n"); exit(5); } if (printf("%d\n", i+j) < 0) { perror("can't do output!\n"); exit(6); } if (fclose(fp) == EOF) { perror("can't close file!\n"); exit(7); } return(0); } ------------------------------------------ Note, perror doesn't let you know if it worked or not! People get lazy, and so don't check these because it's too much of a pain. But then they forget when it really matters. ------------------------------------------ CAREFUL_x FUNCTIONS: A SOLUTION? int do_x(/* ... */) { /* ... */ } void careful_do_x(/* ... */) { int status; status = do_x(/* ... */); if (status != GOOD) { perror("do_x failed!\n") exit(status); } } ------------------------------------------ Q: What's are the advantages and disadvantages of that? + doesn't clutter up the code - inflexible (no way to recover, can't give your own message) *** stronger preconditions ------------------------------------------ A SOLUTION? - require the callers to check conditions (use stronger preconditions) void pop(); //@ REQUIRES: self is not empty //@ ... ------------------------------------------ Q: What are the advantages and disadvantages of this? - software is less robust against untrusted clients (insecure) - debugging is harder - may be hard for clients to check the precondtion, e.g., to check bound limits, memory limits, or that a resource is available However, sometimes this is useful for efficiency. On the other hand, sometimes a better way to ensure requirements is to use data abstractions *** handlers as parameters ------------------------------------------ A SOLUTION? - pass procedures (Smalltalk block) to handle the exceptions that may occur Example in Smalltalk: class: Array ... "instance methods" at: i ifAbsent: aBlock (0 <= i and: [i < size]) ifFalse: [^aBlock value] ifTrue: [^self basicAt: i] "client code" ... myArray at: k ifAbsent: [^self error: 'oops'] ... ------------------------------------------ this works best if the procedures don't have to return to their caller, as with Smalltalk blocks, or Scheme continuations Q: What are the advantages and disadvantages of these? efficiency problems from extra parameters still have the exception code in the way worse: handlers have to be passed along through procedures that may not even want them (i.e., the client code will need to take blocks sometimes to pass along...) ** exception mechanisms *** terms ------------------------------------------ EXCEPTION HANDLING MECHANISMS A convenient form of passing blocks Terms: An exception may be The text that is executed in response ------------------------------------------ ... *signalled* or *thrown* or *raised* by an procedure activation. ... *catches* the exception and is called a *handler* (or catchblock in Java) Note: the handler is in some other procedure activation, other than the one that raised the exception. *** single vs. multi-level mechanisms ------------------------------------------ SINGLE vs. MULTI-LEVEL MECHANISMS Single-level (CLU): all exceptions that can be thrown by or propogated from a method are Multi-level (Mesa, Ada, C++): exceptions are not ------------------------------------------ ... declared in that method ... necessarily declared when a method is declared, so one can't know exactly what exceptions are may be thrown by or propogated from a given method draw stack picture 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) Java is a kind of compromise (see below) checked exceptions (those that don't inherit from RunTimeException or Error) must be declared when a method is declared, but RunTimeExceptions don't have to be. *** termination vs. resumption model ------------------------------------------ TERMINATION vs. RESUMPTION MODEL Termination (CLU, Ada, Java): Resumption (Mesa): ------------------------------------------ ... the signalling activation ceases to exist ... the signalling activation continues to exist, and may be resumed after the exeception is "repaired" The resumption model is more complex, and seems to not be needed most of the time; usually calling the procedure again suffices in the cases where one would resume the signalling activation *** exceptions as objects I think this first appeared in Flavors ------------------------------------------ EXCEPTIONS AS OBJECTS IN A HIERARCHY Throwable Error AWTError LinkageError ThreadDepthError VirtualMachineError OutOfMemoryError ... Exception AWT Exception ClassNotFoundException CloneNotSupportedException IOException EOFException FileNotFoundException ... RuntimeException ArithmeticException ArrayStoreException ClassCastException SecurityException IndexOutOfBoundsException ... ------------------------------------------ In Java, the RunTimeException and Error subclasses are not unchecked (the compiler doesn't see if they are handled statically) also Error subclasses are not checked All exceptions declared have to be a subclass of Throwable. Q: Advantages? can test for one element in the hierarchy, covers everything underneath it. Leads to compression of code for handlers. Main thing is you can classify your own exceptions in a hierarchy ** in Java *** declarations in Java of what exceptions a method can throw ------------------------------------------ DECLARING EXCEPTIONS THROWN BY METHODS IN JAVA Java uses a model that is: single-level for checked exceptions and multi-level for unchecked exceptions Syntax for declaring exceptions that may be thrown by a method: throws [, ] ... Example: char readChar() throws IOException; ------------------------------------------ In Java, all checked exceptions that can either be thrown explicitly or can propogate out of a method must be declared Q: Why? so the compiler, and clients, can check that all exceptions are either handle or propogated (safety) Q: In C readChar returns an int not a char; Why? because it can't indicate an EOF otherwise *** throwing and catching exceptions ------------------------------------------ JAVA EXCEPTIONS Java uses a termination model; Syntax: throw(new FooException); try [catch ( ) ] ... [finally ] ------------------------------------------ semantics of try: run the block, if an exception is raised, if the thrown object inherits from the type on a catch (look from the beginning), then bind the identifier to that object, and run the code of the corresponding block if there is a finally block, do that afterwards, in every case. if an exception was raised, but not caught, then the same exception is raised by the current method. Q: What else needs to be said? what happens if an exception is raised in the finally block and there's a pending exception being thrown? the one in the finally block wins, and the other throw is forgotten