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.. 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 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
416 WHEN THE CONTRACT IS BROKEN:EXCEPTION HANDLING $12.2 is not listed (explicitly or through when others),the routine will pass it to its caller;if there is no caller,meaning that the failed routine is the main program,execution terminates abnormally. In the example there is no need to go to the caller since the exception,just after being raised,is caught by the exception clause of the routine itself,which contains a subclause when Negative =>.. But what then do the corresponding instructions do?Here they are again: put ("Negative argument") return In other words:print out a message-a delicate thought,considering was happens next;and then return to the caller.The caller will not be notified of the event,and will continue its execution as if nothing had happened.Thinking again of typical applications of Ada,we may just wish that artillery computations,which can indeed require square root computations,do not follow this scheme,as it might direct a few missiles to the wrong soldiers(some of whom may,however,have the consolation of seeing the error message shortly before the encounter). This technique is probably worse than the C-Unix signal mechanism,which at least picks up the computation where it left.A when subclause that ends with return does not even continue the current routine(assuming there are more instructions to execute);it gives up and returns to the caller as if everything were fine,although everything is not fine. Managers-and,to continue with the military theme,officers-know this situation well: you have assigned a task to someone,and are told the task has been completed-but it has not.This leads to some of the worst disasters in human affairs,and in software affairs too. This counter-example holds a lesson for Ada programmers:under almost no circumstances should a when subclause terminate its execution with a return.The qualification "almost"is here for completeness,to account for a special case,the false alarm,discussed below;but that case is very rare.Ending exception handling with a return means pretending to the caller that everything is right when it is not.This is dangerous and unacceptable.If you are unable to correct the problem and satisfy the Ada routine's contract,you should make the routine fail.Ada provides a simple mechanism to do this:in an ex ception clause you may execute a raise instruction written as just raise whose effect is to re-raise the original exception to the caller.This is the proper way of terminating an execution that is not able to fulfill its contract. Ada Exception rule The execution of any Ada exception handler should end by either executing a raise instruction or retrying the enclosing program unit
416 WHEN THE CONTRACT IS BROKEN: EXCEPTION HANDLING §12.2 is not listed (explicitly or through when others), the routine will pass it to its caller; if there is no caller, meaning that the failed routine is the main program, execution terminates abnormally. In the example there is no need to go to the caller since the exception, just after being raised, is caught by the exception clause of the routine itself, which contains a subclause when Negative => … But what then do the corresponding instructions do? Here they are again: put ("Negative argument") return In other words: print out a message — a delicate thought, considering was happens next; and then return to the caller. The caller will not be notified of the event, and will continue its execution as if nothing had happened. Thinking again of typical applications of Ada, we may just wish that artillery computations, which can indeed require square root computations, do not follow this scheme, as it might direct a few missiles to the wrong soldiers (some of whom may, however, have the consolation of seeing the error message shortly before the encounter). This technique is probably worse than the C-Unix signal mechanism, which at least picks up the computation where it left. A when subclause that ends with return does not even continue the current routine (assuming there are more instructions to execute); it gives up and returns to the caller as if everything were fine, although everything is not fine. Managers — and, to continue with the military theme, officers — know this situation well: you have assigned a task to someone, and are told the task has been completed — but it has not. This leads to some of the worst disasters in human affairs, and in software affairs too. This counter-example holds a lesson for Ada programmers: under almost no circumstances should a when subclause terminate its execution with a return. The qualification “almost” is here for completeness, to account for a special case, the false alarm, discussed below; but that case is very rare. Ending exception handling with a return means pretending to the caller that everything is right when it is not. This is dangerous and unacceptable. If you are unable to correct the problem and satisfy the Ada routine’s contract, you should make the routine fail. Ada provides a simple mechanism to do this: in an exception clause you may execute a raise instruction written as just raise whose effect is to re-raise the original exception to the caller. This is the proper way of terminating an execution that is not able to fulfill its contract. Ada Exception rule The execution of any Ada exception handler should end by either executing a raise instruction or retrying the enclosing program unit
$12.2 HANDLING EXCEPTIONS 417 Exception handling principles These counter-examples help show the way to a disciplined use of exceptions.The following principle will serve as a basis for the discussion. Disciplined Exception Handling principle There are only two legitimate responses to an exception that occurs during the execution of a routine: RI.Retrying:attempt to change the conditions that led to the exception and to execute the routine again from the start. R2.Failure (also known as organized panic):clean up the environment, terminate the call and report failure to the caller. The classification of In addition,exceptions resulting from some operating system signals(case exception cases, E3 of the classification of exceptions)may in rare cases justify a false alarm including E3,is on response:determine that the exception is hammless and pick up the routine's page 413. execution where it started. Let us do away first with the false alarm case,which corresponds to the basic C-Unix mechanism as we have seen it.Here is an example.Some window systems will cause an exception if the user of an interactive system resizes a window while some process is executing in it.Assume that such a process does not perform any window output;then the exception was harmless.But even in such case there are usually better ways,such as disabling the signals altogether,so that no exception will occur.This is how we will deal with false alarms in the mechanism of the next sections. False alarms are only possible for operating system signals-in fact,only for signals of the more benign kind,since you cannot ignore an arithmetic overflow or an inability to allocate requested memory.Exceptions of all the other categories indicate trouble that cannot be ignored.It would be absurd,for example,to proceed with a routine after finding that its precondition does not hold. So much for false alarms(unfortunately,since they are the easiest case to handle) For the rest of this discussion we concentrate on true exceptions,those which we cannot just turn off like an oversensitive car alarm. Retrying is the most hopeful strategy:we have lost a battle,but we have not lost the war.Even though our initial plan for meeting our contract has been disrupted,we still think that we can satisfy our client by trying another tack.If we succeed,the client will be entirely unaffected by the exception:after one or more new attempts following the initial failed one,we will return normally,having fulfilled the contract.("Mission accomplished, Sir.The usual little troubles along the way,Sir.All fine by now,Sir.") What is the "other tack"to be tried on the second attempt?It might be a different algorithm;or it might be the same algorithm,executed again after some changes have been brought to the state of the execution (attributes,local entities)in the hope of preventing the exception from occurring again.In some cases,it may even be the original routine tried again without any change whatsoever;this is applicable if the exception was due to some
§12.2 HANDLING EXCEPTIONS 417 Exception handling principles These counter-examples help show the way to a disciplined use of exceptions. The following principle will serve as a basis for the discussion. Let us do away first with the false alarm case, which corresponds to the basic C-Unix mechanism as we have seen it. Here is an example. Some window systems will cause an exception if the user of an interactive system resizes a window while some process is executing in it. Assume that such a process does not perform any window output; then the exception was harmless. But even in such case there are usually better ways, such as disabling the signals altogether, so that no exception will occur. This is how we will deal with false alarms in the mechanism of the next sections. False alarms are only possible for operating system signals — in fact, only for signals of the more benign kind, since you cannot ignore an arithmetic overflow or an inability to allocate requested memory. Exceptions of all the other categories indicate trouble that cannot be ignored. It would be absurd, for example, to proceed with a routine after finding that its precondition does not hold. So much for false alarms (unfortunately, since they are the easiest case to handle). For the rest of this discussion we concentrate on true exceptions, those which we cannot just turn off like an oversensitive car alarm. Retrying is the most hopeful strategy: we have lost a battle, but we have not lost the war. Even though our initial plan for meeting our contract has been disrupted, we still think that we can satisfy our client by trying another tack. If we succeed, the client will be entirely unaffected by the exception: after one or more new attempts following the initial failed one, we will return normally, having fulfilled the contract. (“Mission accomplished, Sir. The usual little troubles along the way, Sir. All fine by now, Sir.”) What is the “other tack” to be tried on the second attempt? It might be a different algorithm; or it might be the same algorithm, executed again after some changes have been brought to the state of the execution (attributes, local entities) in the hope of preventing the exception from occurring again. In some cases, it may even be the original routine tried again without any change whatsoever; this is applicable if the exception was due to some Disciplined Exception Handling principle There are only two legitimate responses to an exception that occurs during the execution of a routine: R1 •Retrying: attempt to change the conditions that led to the exception and to execute the routine again from the start. R2 •Failure (also known as organized panic): clean up the environment, terminate the call and report failure to the caller. In addition, exceptions resulting from some operating system signals (case E3 of the classification of exceptions) may in rare cases justify a false alarm response: determine that the exception is harmless and pick up the routine’s execution where it started. The classification of exception cases, including E3, is on page 413
418 WHEN THE CONTRACT IS BROKEN:EXCEPTION HANDLING $12.2 external event-transient hardware malfunction,temporarily busy device or communication line-which we do not control although we expect it will go away. With the other response,failure,we accept that we not only have lost the battle(the current attempt at executing the routine body)but cannot win the war(the attempt to terminate the call so as to satisfy the contract).So we give up,but we must first ensure two conditions,.explaining the use of“organized panic”as a more vivid synonym for“failure”: Making sure(unlike what happened in the sqrt counter-example)that the caller gets an exception.This is the panic aspect:the routine has failed to live up to its contract. Restoring a consistent execution state-the organized aspect. What is a "consistent"state?From our study of class correctness in the previous chapter we know the answer:a state that satisfies the invariant.We saw that in the course of its work a routine execution may temporarily violate the invariant,with the intention of restoring it before termination.But if an exception occurs in an intermediate state the invariant may be violated.The routine must restore it before returning control to its caller. The call chain To discuss the exception handling mechanism it will be useful to have a clear picture of the sequence of calls that may lead to an exception.This is the notion of call chain,already present in the explanation of the Ada mechanism. The call chain Routine call Let ro be the root creation procedure of a certain system(in Ada ro would be the main program).At any time during the execution,there is a current routine,the routine whose execution was started last;it was started by the execution of a certain routine;that routine was itself called by a routine;and so on.If we follow this called-to-caller chain all the way through we will end up at ro.The reverse chain(ro,the last routine r that it called,the last routine r2 that r called,and so on down to the current routine)is the call chain. If a routine produces an exception (as pictured at the bottom-right of the figure),it may be necessary to go up the chain until finding a routine that is equipped to handle the exception-or stop execution if we reachr not having found any applicable exception handler.This was the case in Ada when no routine in the call chain has an exception clause with a when clause that names the exception type or others
418 WHEN THE CONTRACT IS BROKEN: EXCEPTION HANDLING §12.2 external event — transient hardware malfunction, temporarily busy device or communication line — which we do not control although we expect it will go away. With the other response, failure, we accept that we not only have lost the battle (the current attempt at executing the routine body) but cannot win the war (the attempt to terminate the call so as to satisfy the contract). So we give up, but we must first ensure two conditions, explaining the use of “organized panic” as a more vivid synonym for “failure”: • Making sure (unlike what happened in the sqrt counter-example) that the caller gets an exception. This is the panic aspect: the routine has failed to live up to its contract. • Restoring a consistent execution state — the organized aspect. What is a “consistent” state? From our study of class correctness in the previous chapter we know the answer: a state that satisfies the invariant. We saw that in the course of its work a routine execution may temporarily violate the invariant, with the intention of restoring it before termination. But if an exception occurs in an intermediate state the invariant may be violated. The routine must restore it before returning control to its caller. The call chain To discuss the exception handling mechanism it will be useful to have a clear picture of the sequence of calls that may lead to an exception. This is the notion of call chain, already present in the explanation of the Ada mechanism. Let r0 be the root creation procedure of a certain system (in Ada r0 would be the main program). At any time during the execution, there is a current routine, the routine whose execution was started last; it was started by the execution of a certain routine; that routine was itself called by a routine; and so on. If we follow this called-to-caller chain all the way through we will end up at r0. The reverse chain (r0, the last routine r1 that it called, the last routine r2 that r1 called, and so on down to the current routine) is the call chain. If a routine produces an exception (as pictured at the bottom-right of the figure), it may be necessary to go up the chain until finding a routine that is equipped to handle the exception — or stop execution if we reach r0, not having found any applicable exception handler. This was the case in Ada when no routine in the call chain has an exception clause with a when clause that names the exception type or others. The call chain r0 r1 r2 r3 r4 Routine call
$12.3 AN EXCEPTION MECHANISM 419 12.3 AN EXCEPTION MECHANISM From the preceding analysis follows the exception mechanism that fits best with the object-oriented approach and the ideas of Design by Contract. The basic properties will follow from a simple language addition-two keywords to the framework of the preceding chapters.A library class,EXCEPTIONS,will also be available for cases in which you need to fine-tune the mechanism. Rescue and Retry First,it must be possible to specify,in the text of a routine,how to deal with an exception that occurs during one of its calls.We need a new clause for that purpose;the most appropriate keyword is rescue,indicating that the clause describes how to try to recover from an undesirable run-time event.Because the rescue clause describes operations to be executed when the routine's behavior is outside of the standard case described by the precondition (require),body (do)and postcondition (ensure),it will appear,when present,after all these other clauses: routine is require precondition local ..Local entity declarations... do body ensure postcondition rescue rescue clause end The rescue clause is a sequence of instructions.Whenever an exception occurs during the execution of the normal body,this execution will stop and the rescue clause will be executed instead.There is at most one rescue clause in a routine,but it can find out what the exception was(using techniques introduced later),so that you will be able to treat different kinds of exception differently if you wish to. The other new construct is the retry instruction,written just retry.This instruction may only appear in a rescue clause.Its execution consists in re-starting the routine body from the beginning.The initializations are of course not repeated. These constructs are the direct implementation of the Disciplined Exception Handling principle.The retry instruction provides the mechanism for retrying;a rescue clause that does not execute a retry leads to failure
§12.3 AN EXCEPTION MECHANISM 419 12.3 AN EXCEPTION MECHANISM From the preceding analysis follows the exception mechanism that fits best with the object-oriented approach and the ideas of Design by Contract. The basic properties will follow from a simple language addition — two keywords — to the framework of the preceding chapters. A library class, EXCEPTIONS, will also be available for cases in which you need to fine-tune the mechanism. Rescue and Retry First, it must be possible to specify, in the text of a routine, how to deal with an exception that occurs during one of its calls. We need a new clause for that purpose; the most appropriate keyword is rescue, indicating that the clause describes how to try to recover from an undesirable run-time event. Because the rescue clause describes operations to be executed when the routine’s behavior is outside of the standard case described by the precondition (require), body (do) and postcondition (ensure), it will appear, when present, after all these other clauses: routine is require precondition local … Local entity declarations … do body ensure postcondition rescue rescue_clause end The rescue_clause is a sequence of instructions. Whenever an exception occurs during the execution of the normal body, this execution will stop and the rescue_clause will be executed instead. There is at most one rescue clause in a routine, but it can find out what the exception was (using techniques introduced later), so that you will be able to treat different kinds of exception differently if you wish to. The other new construct is the retry instruction, written just retry. This instruction may only appear in a rescue clause. Its execution consists in re-starting the routine body from the beginning. The initializations are of course not repeated. These constructs are the direct implementation of the Disciplined Exception Handling principle. The retry instruction provides the mechanism for retrying; a rescue clause that does not execute a retry leads to failure
420 WHEN THE CONTRACT IS BROKEN:EXCEPTION HANDLING $12.3 How to fail without really trying The last observation is worth emphasizing: Failure principle Execution of a rescue clause to its end,not leading to a retry instruction, causes the current routine call to fail. So if you have wondered how routines can fail in practice-causing case E4 of the See page 413. exception classification-this is it. As a special case,consider a routine which does not have a rescue clause.In practice this will be the case with the vast majority of routines since the approach to exception handling developed here suggests equipping only a select few routines with such a clause. Ignoring possible local entity declarations,arguments,precondition and postcondition,the routine appears as routine is do body end Then if we consider-as a temporary convention-that the absence of a rescue clause is the same thing as an empty rescue clause,that is to say routine is do body rescue --Nothing here (empty instruction list) end the Failure principle has an immediate consequence:if an exception occurs in a routine without rescue clause it will cause the routine to fail,triggering an exception in its caller. Treating an absent rescue clause as if it were present but empty is a good enough For the exact comven- approximation at this stage of the discussion,but we will need to refine this rule slightly tion see“When there when we start looking at the effect of exceptions on the class invariant. is no rescue clause", page 430. An exception history table If a routine fails,either because it has no rescue clause at all or because its rescue clause executes to the end without a retry,it will interrupt the execution of its caller with a "Routine failed"(E4)exception.The caller is then faced with the same two possibilities: either it has a rescue clause that can execute a successful retry and get rid of the exception,or it will fail too,passing the exception one level up the call chain
420 WHEN THE CONTRACT IS BROKEN: EXCEPTION HANDLING §12.3 How to fail without really trying The last observation is worth emphasizing: So if you have wondered how routines can fail in practice — causing case E4 of the exception classification — this is it. As a special case, consider a routine which does not have a rescue clause. In practice this will be the case with the vast majority of routines since the approach to exception handling developed here suggests equipping only a select few routines with such a clause. Ignoring possible local entity declarations, arguments, precondition and postcondition, the routine appears as routine is do body end Then if we consider — as a temporary convention — that the absence of a rescue clause is the same thing as an empty rescue clause, that is to say routine is do body rescue -- Nothing here (empty instruction list) end the Failure principle has an immediate consequence: if an exception occurs in a routine without rescue clause it will cause the routine to fail, triggering an exception in its caller. Treating an absent rescue clause as if it were present but empty is a good enough approximation at this stage of the discussion; but we will need to refine this rule slightly when we start looking at the effect of exceptions on the class invariant. An exception history table If a routine fails, either because it has no rescue clause at all or because its rescue clause executes to the end without a retry, it will interrupt the execution of its caller with a “Routine failed” (E4) exception. The caller is then faced with the same two possibilities: either it has a rescue clause that can execute a successful retry and get rid of the exception, or it will fail too, passing the exception one level up the call chain. Failure principle Execution of a rescue clause to its end, not leading to a retry instruction, causes the current routine call to fail. See page 413. For the exact convention see “When there is no rescue clause”, page 430