12 When the contract is broken: exception handling ikeitrit is no use pretending:in spite of all static precautions,some unexpected and undesired event will sooner or later occur while one of your systems is executing.This is known as an exception and you must be prepared to deal with it. 12.1 BASIC CONCEPTS OF EXCEPTION HANDLING The literature on exception handling is often not very precise about what really constitutes an exception.One of the consequences is that the exception mechanisms present in such programming languages as PL/I and Ada are often misused:instead of being reserved for truly abnormal cases,they end up serving as inter-routine goto instructions,violating the principle of Modular Protection. Fortunately,the Design by Contract theory introduced in the preceding chapter provides a good framework for defining precisely the concepts involved. Failures Informally,an exception is an abnommal event that disrupts the execution of a system.To obtain a more rigorous definition,it is useful to concentrate first on a more elementary concept,failure,which follows directly from the contract idea A routine is not just some arbitrary sequence of instructions but the implementation of a certain specification-the routine's contract.Any call must terminate in a state that satisfies the precondition and the class invariant.There is also an implicit clause in the contract:that the routine must not have caused an abnormal operating system signal, resulting for example from memory exhaustion or arithmetic overflow and interrupting the normal flow of control in the system's execution. It must refrain from causing such events,but of course not everything in life is what it must be,and we may expect that once in a while a routine call will be unable to satisfy its contract-triggering an abnormal signal,producing a final state that violates the postcondition or the invariant,or calling another routine in a state that does not satisfy that routine's precondition(assuming run-time assertion monitoring in the last two cases)
12 When the contract is broken: exception handling Like it or not, it is no use pretending: in spite of all static precautions, some unexpected and undesired event will sooner or later occur while one of your systems is executing. This is known as an exception and you must be prepared to deal with it. 12.1 BASIC CONCEPTS OF EXCEPTION HANDLING The literature on exception handling is often not very precise about what really constitutes an exception. One of the consequences is that the exception mechanisms present in such programming languages as PL/I and Ada are often misused: instead of being reserved for truly abnormal cases, they end up serving as inter-routine goto instructions, violating the principle of Modular Protection. Fortunately, the Design by Contract theory introduced in the preceding chapter provides a good framework for defining precisely the concepts involved. Failures Informally, an exception is an abnormal event that disrupts the execution of a system. To obtain a more rigorous definition, it is useful to concentrate first on a more elementary concept, failure, which follows directly from the contract idea. A routine is not just some arbitrary sequence of instructions but the implementation of a certain specification — the routine’s contract. Any call must terminate in a state that satisfies the precondition and the class invariant. There is also an implicit clause in the contract: that the routine must not have caused an abnormal operating system signal, resulting for example from memory exhaustion or arithmetic overflow and interrupting the normal flow of control in the system’s execution. It must refrain from causing such events, but of course not everything in life is what it must be, and we may expect that once in a while a routine call will be unable to satisfy its contract — triggering an abnormal signal, producing a final state that violates the postcondition or the invariant, or calling another routine in a state that does not satisfy that routine’s precondition (assuming run-time assertion monitoring in the last two cases)
412 WHEN THE CONTRACT IS BROKEN:EXCEPTION HANDLING $12.1 Such a case will be called a failure. Definitions:success,failure A routine call succeeds if it terminates its execution in a state satisfying the routine's contract.It fails if it does not succeed The discussion will use the phrase“routine failure'”,or just“failure”,asan abbreviation for "failure of a routine call".Of course what succeeds or fails is not a routine (an element of the software text)but one particular call to that routine at run time. Exceptions From the notion of failure we can derive a precise definition of exceptions.A routine fails because of some specific event (arithmetic overflow,assertion violation...)that interrupts its execution.Such an event is an exception. Definition:exception An exception is a run-time event that may cause a routine call to fail Often an exception will cause failure of the routine.But you can prevent this from occurring by writing the routine so that it will catch the exception and try to restore a state from which the computation will proceed.This is the reason why failure and exception are different concepts:every failure results from an exception,but not every exception results in failure. The study of software anomalies in the previous chapter introduced the terms fault See "Errors,defects (for a harmful execution event),defect(for an inadequacy of system,which may cause and other creeping faults)and error(for a mistake in the thinking process,which may lead to defects).A creanres".page48. failure is a fault;an exception is often a fault too,but not if its possible occurrence has been anticipated so that the software can recover from the exception. Sources of exceptions The software development framework introduced so far opens the possibility of specific categories of exception,listed at the top of the facing page. Case El reflects one of the basic requirements of using references:a call a.f is only "Voidreferences and meaningful if a is attached to an object,that is to say non-void.This was discussed in the calls".page 240. presentation of the dynamic model. Case E2 also has to do with void values.Remember that "attachment"covers "Hybrid attach- assignment and argument passing,which have the same semantics.We saw in the ments".page 263. discussion of attachment that it is possible to attach a reference to an expanded target,the result being to copy the corresponding object.This assumes that the object exists;if the source is void,the attachment will trigger an exception
412 WHEN THE CONTRACT IS BROKEN: EXCEPTION HANDLING §12.1 Such a case will be called a failure. The discussion will use the phrase “routine failure”, or just “failure”, as an abbreviation for “failure of a routine call”. Of course what succeeds or fails is not a routine (an element of the software text) but one particular call to that routine at run time. Exceptions From the notion of failure we can derive a precise definition of exceptions. A routine fails because of some specific event (arithmetic overflow, assertion violation…) that interrupts its execution. Such an event is an exception. Often an exception will cause failure of the routine. But you can prevent this from occurring by writing the routine so that it will catch the exception and try to restore a state from which the computation will proceed. This is the reason why failure and exception are different concepts: every failure results from an exception, but not every exception results in failure. The study of software anomalies in the previous chapter introduced the terms fault (for a harmful execution event), defect (for an inadequacy of system, which may cause faults) and error (for a mistake in the thinking process, which may lead to defects). A failure is a fault; an exception is often a fault too, but not if its possible occurrence has been anticipated so that the software can recover from the exception. Sources of exceptions The software development framework introduced so far opens the possibility of specific categories of exception, listed at the top of the facing page. Case E1 reflects one of the basic requirements of using references: a call a ● f is only meaningful if a is attached to an object, that is to say non-void. This was discussed in the presentation of the dynamic model. Case E2 also has to do with void values. Remember that “attachment” covers assignment and argument passing, which have the same semantics. We saw in the discussion of attachment that it is possible to attach a reference to an expanded target, the result being to copy the corresponding object. This assumes that the object exists; if the source is void, the attachment will trigger an exception. Definitions: success, failure A routine call succeeds if it terminates its execution in a state satisfying the routine’s contract. It fails if it does not succeed. Definition: exception An exception is a run-time event that may cause a routine call to fail. See “Errors, defects and other creeping creatures”, page 348. “Void references and calls”, page 240. “Hybrid attachments”, page 263
$12.1 BASIC CONCEPTS OF EXCEPTION HANDLING 413 Definition:exception cases An exception may occur during the execution of a routine r as a result of any of the following situations: EI.Attempting a qualified feature call a./and finding that a is void. E2 Attempting to attach a void value to an expanded target. E3.Executing an operation that produces an abnormal condition detected by the hardware or the operating system. E4 .Calling a routine that fails. E5.Finding that the precondition ofr does not hold on entry. E6 .Finding that the postcondition ofr does not hold on exit. E7 .Finding that the class invariant does not hold on entry or exit. E8 .Finding that the invariant of a loop does not hold after the from clause or after an iteration of the loop body. E9 Finding that an iteration of a loop's body does not decrease the variant. E10.Executing a check instruction and finding that its assertion does not hold. El 1.Executing an instruction meant explicitly to trigger an exception. Case E3 follows from signals that the operating system sends to an application when it detects an abnormal event,such as a fault in an arithmetic operation (underflow, overflow)or an attempt to allocate memory when none is available. Case E4 arises when a routine fails,as a result of an exception that happened during its own execution and from which it was not able to recover.This will be seen in more detail below,but be sure to note the rule that results from case E4: Failures and exceptions A failure of a routine causes an exception in its caller. See“Monitoring Cases E5 to E10 can only occur ifrun-time assertion monitoring has been enabled at assertions at run the proper level:at least assertion(require)for E5,assertion (loop)for E8 and E9 etc. time".page 393. Case E11 assumes that the software may include calls to a procedure raise whose sole goal is to raise an exception.Such a procedure will be introduced later. Causes of failure Along with the list of possible exception cases,it is useful for the record to define when a failure(itself the source of an exception in the caller,as per case E4)can occur:
§12.1 BASIC CONCEPTS OF EXCEPTION HANDLING 413 Case E3 follows from signals that the operating system sends to an application when it detects an abnormal event, such as a fault in an arithmetic operation (underflow, overflow) or an attempt to allocate memory when none is available. Case E4 arises when a routine fails, as a result of an exception that happened during its own execution and from which it was not able to recover. This will be seen in more detail below, but be sure to note the rule that results from case E4: Cases E5 to E10 can only occur if run-time assertion monitoring has been enabled at the proper level: at least assertion (require) for E5, assertion (loop) for E8 and E9 etc. Case E11 assumes that the software may include calls to a procedure raise whose sole goal is to raise an exception. Such a procedure will be introduced later. Causes of failure Along with the list of possible exception cases, it is useful for the record to define when a failure (itself the source of an exception in the caller, as per case E4) can occur: Definition: exception cases An exception may occur during the execution of a routine r as a result of any of the following situations: E1 • Attempting a qualified feature call a ● f and finding that a is void. E2 • Attempting to attach a void value to an expanded target. E3 • Executing an operation that produces an abnormal condition detected by the hardware or the operating system. E4 • Calling a routine that fails. E5 • Finding that the precondition of r does not hold on entry. E6 • Finding that the postcondition of r does not hold on exit. E7 • Finding that the class invariant does not hold on entry or exit. E8 • Finding that the invariant of a loop does not hold after the from clause or after an iteration of the loop body. E9 • Finding that an iteration of a loop’s body does not decrease the variant. E10 • Executing a check instruction and finding that its assertion does not hold. E11 • Executing an instruction meant explicitly to trigger an exception. Failures and exceptions A failure of a routine causes an exception in its caller. See “Monitoring assertions at run time”, page 393
414 WHEN THE CONTRACT IS BROKEN:EXCEPTION HANDLING $12.2 Definition:failure cases We have yet to see what it means for a A routine call will fail if and only if an exception occurs during its execution routine to"recover” from an exception. and the routine does not recover from the exception. The definitions of failure and exception are mutually recursive:a failure arises from an exception,and one of the principal sources ofexceptions in a calling routine(E4)is the failure of a called routine. 12.2 HANDLING EXCEPTIONS We now have a definition of what may happen-exceptions-and of what we would prefer not to happen as a result-failure.Let us equip ourselves with ways to deal with exceptions so as to avoid failure.What can a routine do when its execution is suddenly interrupted by an unwelcome diversion? As so often in this presentation,we can get help towards an answer by looking at examples of how not to do things.Here the C mechanism(coming from Unix)and an Ada textbook will oblige. How not to do it-a C-Unix example The first counter-example mechanism(most notably present on Unix,although it has been made available on other platforms running C)is a procedure called signal which you can call under the form signal (signal code,your routine) with the effect of planting a reference to your routine into the software,as the routine that should be called whenever a signal of code signal code occurs.A signal code is one of a number of possible integers such as S/GILL(illegal instruction)and S/GFPE(floating- point exception).You may include as many calls to signal as you like,so as to associate different routines with different signals. Then assume some instruction executed after the call to signal triggers a signal of code signal code.Were it not for the signal call,this event would immediately terminate the execution in an abnormal state.Instead it will cause a call to your routine,which presumably performs some corrective action,and then will...resume the execution exactly at the point where the exception occurred.This is dangerous,as you have no guarantee that the cause of the trouble has been addressed at all;if the computation was interrupted by a signal it was probably impossible to complete it starting from its initial state What you will need in most cases is a way to correct the situation and then restart the routine in a new,improved initial state.We will see a simple mechanism that implements this scheme.Note that one can achieve it in C too,on most platforms,by combining the signal facility with two other library routines:setjmp to insert a marker into the execution record for possible later return,and longimp to retum to such a marker,even if several calls have been started since the setimp.The setjmp-longimp mechanism is
414 WHEN THE CONTRACT IS BROKEN: EXCEPTION HANDLING §12.2 The definitions of failure and exception are mutually recursive: a failure arises from an exception, and one of the principal sources of exceptions in a calling routine (E4) is the failure of a called routine. 12.2 HANDLING EXCEPTIONS We now have a definition of what may happen — exceptions — and of what we would prefer not to happen as a result — failure. Let us equip ourselves with ways to deal with exceptions so as to avoid failure. What can a routine do when its execution is suddenly interrupted by an unwelcome diversion? As so often in this presentation, we can get help towards an answer by looking at examples of how not to do things. Here the C mechanism (coming from Unix) and an Ada textbook will oblige. How not to do it — a C-Unix example The first counter-example mechanism (most notably present on Unix, although it has been made available on other platforms running C) is a procedure called signal which you can call under the form signal (signal_code, your_routine) with the effect of planting a reference to your_routine into the software, as the routine that should be called whenever a signal of code signal_code occurs. A signal code is one of a number of possible integers such as SIGILL (illegal instruction) and SIGFPE (floatingpoint exception). You may include as many calls to signal as you like, so as to associate different routines with different signals. Then assume some instruction executed after the call to signal triggers a signal of code signal_code. Were it not for the signal call, this event would immediately terminate the execution in an abnormal state. Instead it will cause a call to your_routine, which presumably performs some corrective action, and then will … resume the execution exactly at the point where the exception occurred. This is dangerous, as you have no guarantee that the cause of the trouble has been addressed at all; if the computation was interrupted by a signal it was probably impossible to complete it starting from its initial state. What you will need in most cases is a way to correct the situation and then restart the routine in a new, improved initial state. We will see a simple mechanism that implements this scheme. Note that one can achieve it in C too, on most platforms, by combining the signal facility with two other library routines: setjmp to insert a marker into the execution record for possible later return, and longjmp to return to such a marker, even if several calls have been started since the setjmp. The setjmp-longjmp mechanism is, Definition: failure cases A routine call will fail if and only if an exception occurs during its execution and the routine does not recover from the exception. We have yet to see what it means for a routine to “recover” from an exception
$12.2 HANDLING EXCEPTIONS 415 however,delicate to use;it can be useful in the target code generated by a compiler-and can indeed serve,together with signal,to implement the high-level O-O exception mechanism introduced later in this chapter-but is not fit for direct consumption by human programmers. How not to do it-an Ada example Here is a routine taken from an Ada textbook: From Sommerville sqrt (n:REAL)return REAL is and Morrison,.“Sof.- begin ware Development ifx<0.0 then with Ada”,Addison- Wesley,1987.Letter raise Negative case,indentation, else semicolon usage and normal square root computation the name of the float- end ing-point type have been adapted to the exce ption conventions of the when Negative = present book;Non_ put ("Negative argument") positive has been return changed to Negative. when others =>.. end -sqrt This example was probably meant just as a syntactical illustration of the Ada mechanism,and was obviously written quickly (for example it fails to return a value in the exceptional case);so it would be unfair to criticize it as if it were an earnest example of good programming.But it provides a useful point of reference by clearly showing an undesirable way of handling exceptions.Given the intended uses of Ada-military and space systems-one can only hope that not too many actual Ada programs have taken this model verbatim. The goal is to compute the real square root of a real number.But what if the num ber is negative?Ada has no assertions,so the routine performs a test and,if it finds n to be negative,raises an exception. The Ada instruction raise Exc interrupts execution of the current routine,triggering an exception of code Exc.Once raised,an exception can be caught,through a routine's (or block's)exeeption clause.Such a clause,of the form exception when code al,code a2,...=>Instructions a, when code bl,...=Instructions b; is able to handle any exception whose code is one of those listed in the when subclauses; it will execute Instructions a for codes code al,code a2,...and so on for the others.One of the subclauses may,as in the example,start with when others,and will then handle any exception not explicitly named in the other subclauses.If an exception occurs but its code
§12.2 HANDLING EXCEPTIONS 415 however, delicate to use; it can be useful in the target code generated by a compiler — and can indeed serve, together with signal, to implement the high-level O-O exception mechanism introduced later in this chapter — but is not fit for direct consumption by human programmers. How not to do it — an Ada example Here is a routine taken from an Ada textbook: sqrt (n: REAL) return REAL is begin if x < 0.0 then raise Negative else normal_square_root_computation end exception when Negative => put ("Negative argument") return when others => … end -- sqrt This example was probably meant just as a syntactical illustration of the Ada mechanism, and was obviously written quickly (for example it fails to return a value in the exceptional case); so it would be unfair to criticize it as if it were an earnest example of good programming. But it provides a useful point of reference by clearly showing an undesirable way of handling exceptions. Given the intended uses of Ada — military and space systems — one can only hope that not too many actual Ada programs have taken this model verbatim. The goal is to compute the real square root of a real number. But what if the number is negative? Ada has no assertions, so the routine performs a test and, if it finds n to be negative, raises an exception. The Ada instruction raise Exc interrupts execution of the current routine, triggering an exception of code Exc. Once raised, an exception can be caught, through a routine’s (or block’s) exception clause. Such a clause, of the form exception when code_a1, code_a2, …=> Instructions_a; when code_b1, … => Instructions_b; … is able to handle any exception whose code is one of those listed in the when subclauses; it will execute Instructions_a for codes code_a1, code_a2, … and so on for the others. One of the subclauses may, as in the example, start with when others, and will then handle any exception not explicitly named in the other subclauses. If an exception occurs but its code From Sommerville and Morrison, “Software Development with Ada”, AddisonWesley, 1987. Letter case, indentation, semicolon usage and the name of the floating-point type have been adapted to the conventions of the present book; Non_ positive has been changed to Negative