Academia.eduAcademia.edu

ADA exceptions: specification and proof techniques

1980

A method of documenting exception propagation and handling in Ada programs is proposed. Exception propagation declarations are introduced as a new component of Ada specifications. This permits documentation of those exceptions that can be propagated by a subprogram. Exception handlers are documented by entry assertions. Axioms and proof rules for Ada exceptions are given. These rules are simple extensions of previous rules for Pascal and define an axiomatic semantics of Ada exceptions. As a result, Ada programs specified according to the method can be analysed by formal proof techniques for consistency with their specifications, even if they employ exception propagation and handling to achieve required results (i.e. non-error situations). Example verifications are given.

February 1980 Stanford Verification Group Report No. 16 Computer Science Department Report No. STAN-CS-80-789 ADA EXCEPTIONS: Specification and Proof Techniques bY D. C. Luckham and W. Polak Research sponsored by Advanced Research Projects Agency and Rome Air Development Center COMPUTER SCIENCE DEPARTMENT Stanford University February 1980 Stanford Verification Group Report No. 16 Computer Science Department Report No. STAN-CS-80-789 ADA EXCEPTIONS: Specification and Proof Techniques bY 0. C. Luckham and W. Polak ABSTRACT a A method of documenting exception propagation and handling in Ada programs is proposed. Exception propagation declarations are introduced as a new component of Ada specifications. Thi permits documentation of those exceptions that can be propagated by a subprogram. Exception handlers are documented by entry assertions. Axioms and proof rules for Ada exceptions are given. These rules are simple extensions of previous rules for Pascal and define an axiomatic semantics of Ada exceptions. As a result, Ada programs specified according to the method can be analysed by formal proof techniques for consistency with their specifications, even if they employ exception propagation and handling to achieve required results (i.e. non error situations). Example verifications are given. This research was supported by the Advanced Research Projects Agency of the Department of Defense under Contract MDA 903-80-C-0159 and Rome Air Development Center under Contract F 30602-80-C-0022. The views and conclusions contained in this document are those of the authors and should not be interpreted as necessarily representing the oflcial policies, either expressed or implied, of Stanford University, or any agency of the U. S. Government. ADA EXCEPTIONS : SPECIFXCATION AND PROOF TECHNIQUES D, C. Luckham and W. Polak Abrttect: A method of documenting ezception propagation and handling in Ada programs is proposed. Ezception propagation declarations are introduced aa a new component o j Ada specifications. This permite documentation of those ezceptiona that can be propagated bg a subprogram. Ezception handlers are documented bg entry aesertions. Azioms and proof rules for Ada ezceptions are given. These rulea are simple eztenaions of previous rules for Parcal and define an aziomatic semantica of Ada ezceptions. As a result, Ada programs specified according to the method can be analgsed by formal proof technique8 for consirtencg with their rpecijications, euen if they employ ezception propagation and handling to achieue required result8 (i.e. non error rituations). Ezample verification8 are given. This research wad supported by the Aduanced Research Projects Agenw of the Department of Defense under Contract M DA 905 -- 80 - - C -- 0150 and Rome Air Development Center under contract F 50602 -- 80 -- C -- 0022 1 1. Introduction a Exceptions in the Ada language [I] are intended to provide a facility for dealing with errors and exceptional situations arising during program execution. An exception is an event that causes suspension of normal program execution called raising an exception. Actions in response to the occurrence of an exception are coded in special units called handlers. When an exception is raised, execution of a handler replaces execution of the current unit. The choice of handlers is dynamic - i.e., determinable only at runtime. Ada exceptions are clearly a powerful programming facility and a study of their semantics and proof is needed. The obvious semantics of raising an exception is that of a jump to the corresponding handler. In fact, the conventional go to rule [lo, ll] is sufficient to describe exceptions with static association of handlers. Problems arise in the case where an exception is not handled locally in a subprogram and the association of the exception with the handler is dynamic. The answer lies in requiring documentation for exceptions that are propagated by procedures or functions; it is then possible to modify the procedure call rule to describe the dynamic association of handlers with exceptions at call time. We propose a method of specifying exceptions in Ada programs. Axioms and proof rules based on these specifications are given. These define an axiomatic semantics of Ada exceptions, and make possible the verification (proof of consistency with specifications) of Ada programs containing exceptions. Our specifications and proof rules apply to programs with exceptions irrespective of whether exceptions are used only for error situations or as a method of programming normal program behavior (e.g., the program is intended to continue running when exceptions are raised and handled). The proof rules are simple extensions of rules of conventional axiomatics e.g. those of Pascal [7]. Our results show that if the programmer follows a simple specification discipline, then the use of exceptions as a normal programming tool does not greatly complicate the analysis of programs for consistency with specifications. We restrict presentation of our method to simple cases. In particular, the semantics assumes call by reference implementation for out and tn out parameters. Similar proof rules can be given for other implementations of parameter passing. Extensions to more complicated programs, e.g. Ada packages or Ada programs in which exceptions are propagated beyond their scope, are outlined in section 5 and the appendix. We will not deal with the task failure exception propagated between tasks or with suppression of exceptions. Throughout this paper we will use the notation and terminology of the Ada report [l), except in logical assertions, where we use o[i] to denote the i-th element of array u to improve readability. The word subprogram will be used for a 2 function or procedure. A statement of the form arrert <assertion> is not the assert statement of Ada; <assertion> is a logical assertion and is not intended to be executed. Logical assertions are more general specification statements than Ada conditions, but they obey the normal scope rules: all free variables are visible program variables. 2. Specifying Exceptionti The exception facility of Ada includes - declarations of user defined exceptions - a raise statement that causes an exception, and - the definition of exception handlers, i.e. code that is to be executed whenever a particular exception is raised. We now de%ne the formal documentation that has to be provided for exceptions. (1) No documentation is required for exception declarations and raise statements. (3) For each exception handler the programmer will be required to provide an assertion that is to be true whenever one of the exceptions in the when clause is raised. Following Ada we will use the syntax: exception-handler ::= when exception (1 exception } ar,ert assertion => sequence,ol,statements (3) An exception that is raised in a subprogram and is not handled locally is said to be propagated by this subprogram ([I] 11.34 We introduce an exception propagation declaration and require that each exception propagated by a Hubprogram be specified in the subprogram’s propagation declaration. Analogous to a handler, a propagation declaration has to provide an assertion that is to be true whenever one of the exceptions in the propagate clause is propagated. Propagation declarations are placed at the end of the Ada subprogram specification ([1]6.2). We consider propagation declarations as part of a (to be defined) specification language for Ada. As such, they are of the same nature as global, entry, and exit specifications for procedures and functions of Pascal Plus [II]. We use the syntax: 3 propagation,declaration :: = propagate exception (1 exception } assert assertion subject to the normal scope rules. Note, that a call to a procedure that propagates an exception is a statement that Van raise an exception”. We restrict such calls to the scope of the exception; the use of propagate declarations now makes this restriction compiler checkable. In section 5 we show how these specification requirements may be relaxed by use of the others clause. The appendix contains an example of how our specifications will require modifications of an Ada program that propagates an exception out of the scope of the declaration of that exception. In a scope S where an exception E can be raised either by a raise statement or a subprogram call the assertion arrociated with E in this scope is defined as follows: - If S is a block with a handler for E then the assertion of this handler is the associated assertion, - If S is a block without a handler for E then the assertion associated with E in S is the assertion associated with E in the scope immediately enclosing S. - - If S is a subprogram, then the assertion of the propagation declaration for E is associated with E. Remark: An exception has an associated assertion in every scope where it can be raised. For example, consider the following program. 4 procedure p(. . . ) propagate E errcrt A ir begin begin ... raire E; - B is associated assertion exception when E arrert B => . . . . end; begin ... - A is associated assertion raise E; ... end end p 3. Axiomatic Semanticr of Exceptionr 3.1. Raising Exceptions Wherever the statement raise Ei occurs, the above specification principles guarantee that there is an assertion Ai associated with Ei at that point. The semantics of raire Ei is described by the raise axiom: { Ai} raise Ei (fd8e). 3.2. Blocks The rule of exceptions given below requires that any exception handler achieves the exit assertion of the block in which it is contained. The handler may assume . the truth of the assertion in the when clause. Let C be the block 5 exception when El arrert Al => S1 when . . . ... when E, arrert A,, => S, end then we have the following rule ol exceptions: 3.3. Procedure calls - Rules given in 3.1 and 3.2 are sufficient to describe the effect of exceptions handled in the subprogram in which they are raised. We now propose a modified procedure call rule that describes the effect of an exception propagated by a procedure. We will restrict our attention to procedures only; a similar treatment for functions is immediate. Let p be a procedure that propagates the exceptions El.. . E, and let Al.. . A, be the corresponding assertions in the propagate declaration of p. Let Bj be the assertion associated with Ej in the calling environment according to the principles in 2. The procedure call rule will require that Aj + Bj for all exceptions Ej propagated by p with proper substitutions for in parameters and quantification for out and in-out parameters. The procedure call rule presented below is taken from [sI], adapted for Ada and exception handling. Let p be a procedure with ji, jO, and jia being the formal in, out, and in out parameters of p respectively. We assume that the correctness of the body of p has been established with respect to the input condition I( ji, jio) and output condition O( ji, jO, jio). Let ui, uO, and uio be the corresponding actual 1 parameters of a call to p, then this call is described by the rule: P + I(ui, uio) A Vuop uio*(O(ui, ~0, uio) + Q ) P + I(Ui, Qio) A VU09 Qo*(Al(%, Qo9 (ho) + Bl) ... P --+ I(Ui, Qio) A V&J, aioe(An(oi, 00, Uio) + &) 6 where the clause P + I(ai, oio) A Vuo, uio.(Aj(ui, uo, uio) + Bj) has to be proven for each propagated exception Ej with propagate assertion Ai and Bj associated with Ej in the calling environment. Note that the treatment of exceptions is similar to that of the normal output condition; raising exceptions may be viewed as just another exit of p. The above rule assumes p to be a restricted procedure without any global variables. It may be extended to provide for global variables. Furthermore, we assume an implementation of procedure calls using call by reference for out and in out parameters. Our rules can be adapted for other procedure call mechanisms. Also, other procedure call rules (e.g. [2,5,7]) can be extended to include the conditions Ai + Bi. 4. Example1 4.1. Consider the. procedure seurch with in parameters CL, n, and z and out parameter i with the entry assertion true and the exit assertions 1 s i 5 n A Z= u[i]. In case that the value z is not in u, seurch will raise the exception notfound. For this exception eeurch contains a propagation with the assertion V&(1 5 j 5 n + z # u[j]). Altogether we have the procedure declaration type taurruy ir array (1. .n) of integer; procedure seurch(u: in nurruy; w in integer; z: in integer; i: out integer) entry true exit 15 is nAz=u[i] propagate not found arrert Vj.(l 5 j 5 n + 5 # ub]) ir begin ... raire notf ound; ... end A proof of the body of search may use the axiom {Vj.(l 5 j 5 YJ --) z # u[j] )} raire notfound {j&e} Procedure search could be used in the following context (m, y, and b are declared in some global scope): 7 areert true; begin search(b, m, y, k); exception w h e n notfound arrert Vj.(l < j < m + p # bcil, => k.a- 0 end; arrcrt {y = b[k] v (V&(1 5 j 5 m + g # b[j]) A k = 0)) Consistency of this block with its specifications can be shown by Vk (1 5 k ,< mAy =b[k])+ v= b[k] v (Vj.(l 5 j 5 m ‘YZWhk=0)) Vk(Vj.(l 5 j 5 m ~~# ~[j])- ,Vi.(l~ ilm+k# ~Cjl)) Vj.(l < j < - m --+ y # b[j]) + v= b[O] V(Vj(l,< j< m+y # b[j])AO =O ) (1) (2) (3) Formula (1) proves that the exit assertion of the call to eeurch implies the exit assertion of the block (note, that P and I of the procedure call rule are both true). This proves consistency if the call to search terminates normally. Formula (2) proves that the assertion associated with notfound inside search implies the assertion associated with notjound in the calling environment (see call rule). This proves that notjound is propagated by the call to search only in situationa that the handler Uexpectsn. Finally, (3) proves that the handler does indeed establish the exit condition of the block (i.e. the handler “handles” correctly). 4.2. The methods presented in this paper are also useful in proving that an exception will never be raised. If we have an exception associated with the assertion false, then the corresponding handler will never be executed since its precondition is never satisfied. Consider the example: 8 assert 3j.l 5 j < mA y = &‘I; begin seurch(b, m, v, k); exception when not found arrert false => < empty > end; arrert {y = b[k] } The corresponding verification conditions are (numbering aa in previous example): 3j.(l 2 j< mAg=b[j])- , (1) Vk.((l ,< k 5 mAy = b[k]) -by= 01) 3j.(l < - j < mA y = b[j]) + Vk.(Vj.(l 5 j 3 m --) y # b[j]) + false) (2) fulee -+ y = b[k] (3) 5. Extennionr 5.1. Use of the “others” construct Our method also applies if an exception handler contains the otherr clause. If a block contains such a handler, exception when otherr arrert A => a am - then in this block A is the assertion associated with all exceptions that are raised in the block and are not contained in another when clause in the handler. All proof rules and the raise axiom apply unchanged. The situation is different in propagate declarations. If we allow an others 1 clause in propagate declaration8 then any exception that is not explicitly mentioned in this propagate assertion can potentially be propagated via the otherr clause. Our method can be extended to handle this case if we accept a more complicated procedure call rule. The general principle of the call rule remains the same: for each exception that can potentially be propagated by the called procedure we have to prove that the assertion specified for this exception in the propagate declaration implies the assertion associated with the exception in the calling environment. 9 First, note that a scope containing a call to a procedure with an otherr clause in the propagation delcaration must define an associated assertion for every exception that may be raised in it; thus in general its handler or propagate declaration also has to have an otherr clause. Given the above situation we can classify all exceptions as follows. so - set of exceptions explicitly named in handlers of propagate declarations for calling environment. . Si - set of exceptions explicitly named in propagate declaration for called procedure. Bj - associated assertion for each Ej E So. Aj - assertion specified in propagate declaration for each Ej E Si. &c-- assertion for other8 clause in handler (or propagate declaration) of calling environment. &---- assertion for others clause in propagate declaration of called procedure. To prove that a procedure call is correct we have to verify that: a.) Ai --+ Bj for all Ej E Sin So, b.) Aj --+ B, for all Ej E S; - So, t.) A, + Bj for all Ej E SO - Si, and d . ) A , + &,. The procedure call rule has to be augmented by these caaea, where proper substitutions have to be preformed as described in section 3. A better proposal to reduce the number of necessary propagate declaration8 is to introduce a single global exception, error. If a user does not want to include each propagated exception with a corresponding assertion in a propagate declaration he may include the handler exception when other8 arrert A => raise error; in any subprogram and provide a propagate declaration for error. This conver- sion has been discussed in 12.5. of the Ada rational but was rejected on efficiency considerations. 5.2. Application to Packager The method described in this paper is aleo applicable to exceptions propagated from within modules (or Ada packages). It applies directly to calls of module procedure8 but requires some changes when applied to module bodies. Let us 10 briefly outline the key idea without going into the detail8 of a particular method of specification and verification of modules, Let us assume that to the using program a module represents an abstract object together with certain operations (module procedures). Some of these may propagate exceptions, overAow in a stack for instance. Part of the visible specification of module procedures are exception propagation declaration8 using abstract specifications. Then the procedure di rule of section 3.3 applies to module procedure calls as well. For example, the package in [l] 12.3, could be specified aa generic (size: integer; type elem) package stuck ir over flow, under flow: exception; procedure push(E: in elem) propagate over flow arrert f ull(etuck); procedure pop(E: out elem) propagate underflow arrcrt empty(atack); end stuck - This additional documentation is valuable to the programmer even if a formal verification is not attempted. The Ada syntax merely states that any of the module procedures may raise any one of the listed exceptions. The propagate declarations give the extra information that, for example, pop will never raise the exception over flow. The extra problem cornea in verifying that exceptions are raised correctly in the stack body. Internal assertions associated with exceptions may refer to local variables of the module body in addition to parameters of procedures. Thus the condition f ull(shzck) may internally correspond to a condition length = size for some variable fen&h local to the module body. Consequently, for ouerjlow the following raise axiom is valid within the stack body: {length = size} raire over flow {fulse}. A “module declaration rule” has to enforce that the visible propagate assertion for an exception is equivalent to the assertion in the body that describes the internal state in which the exception is propagated. 5.3. Predeflned exceptions It is conceivable that predeflned exceptions can be handled w ith our method. The si,tuation here is complicated by the fact that predefined exceptions are 11 usually raised implicitly by numerous different statements and expressions. Therefore, all proof rules have to be changed to account for exceptions. For example, the assignment z + y/z in a scope where A is the assertion associated with diuide,error is not described by the standard assignment axiom {P l&}z + y/z(P) but rather by the rule {&I 2 + Y/Z (P) This rule differs from the standard assignment axiom in having an extra premiss requiring proof that the associated assertion, A, is true when divide-error is raised. Similar extra premisses must be added to the proof rules for many other language constructs leading to an exponential increase in the number of subproblems such as & /\ z = 0 + A. If, however, A is the assertion true, then the additional subproblems are trivial. In many casea associating true with an exception handler will be sufficient to verify that a program continues correctly after an exception has been raised. Since predeflned exceptions frequently correspond to “runtime erroram, an alternate approach towards predeflned exceptions is to verify that for certain sets of input values these exception8 will never be raised. Positive results in this direction are reported in [4]. 6. Conclusion - We have given an axiomatic semantics for Ada exceptions based on a method of formally specifying exceptions; the method extends to packages in a natural way. The required documentation appears very natural and should not be too demanding for the programmer. The propagation declaration is a helpful specification even in cases where a formal verification is not desired as is shown by the documentation of the stack module in 5.1. The additional information provided - by propagate declarations can be used to check for certain semantic restrictions at compile time, e.g. to check that no exception is propagated out of its scope, or that all exceptions that may be raised within a subprogram and not propagated are handled within that subprogram. The question of how the task failure exception can be treated in a multi task program and whether this can be done consistently with our method has to be investigated in future research. 12 Acknowledgement We are greatful for helpful conversations with Jean Ichbiah and Steve German. Reference1 1. Preliminary Ado reference manual and r&mule; Sigplan Notices 14,6 (1979) 2. Cartwright, R.S., Oppen, D.C.: ‘I;he Logic of Akzsing; Proc. 5th. ACM Symp. on Principles of Programming Languages, Tucon (1978) 3. Department of Defense: STEELM AN requirements for high order computer progrumming lunguugee; (June 1978) 4. German, KM.: Automating proofs ojthe ubsence oj common runtime errors; Proc. 5th. ACM Symp. on Principles of Programming Languages, Tucon (1978) pp 105-118 5. Grier, D., Levin, G.: A procedure cull proof rule (with u dimple ezplunutbon); Tech. Report 79-379, Cornell University (1979) 6. von Eenke F. W., Luckham D, C,: Automatic Program Verification III: A Methodology for Verifying Progrums; Memo AIM-256, Stanford Artiflcail Intelligence Laboratory (1974) 7. Hoare, C. A. R.: Procedures and parameters: an uziomutic approach; Lecture Notes in Mathematics 188, Springer (1971) 8. Hoare, C. A. R.: An Axiomatic Basis of Computer Programming; Comm ACM 12,lO (October 1969) pp 576-580 9. Igararhi, S., London, R. L., Luckham, D. C.: Automatic Program Verification 1: Logicul &bsis and Its Implementation; Acta Informatica, Vol 4 (1975) pp 145-182 10. Knuth, D.E.: Structured Programming with go to etutements; Surv., 6,4, (1974) Comp. 11. Stanford Verification Group: Stanford Pascal Verifier, User Manual; Report No. STAN-CS-79-731, Computer Science Department, Stanford University (1979) 13 Appendix Propagating cxceptionr out of their scope The Ada rationale [l]gives the following example of exceptions propagated out of their scope (12.5.2): package D ir procedure A; procedure B; end - Outside is declared outside the scope of error procedure Outside ir begin e.. D.A; ... end - D.A may propagate error in which case Outside will propagate “something” that cannot be named syntactically in the declaration of O utside . package body D ir error: exception; procedure A iI begin - scope of error-exception - call to Outside may propagate ‘something” which the Ada implementation will recognize as error . . . raire error; end; procedure B 1s begin ... Outside; ... exception when error => . end; l . end D In this example the scope of the exception error is the body of D. The call D.A in outside may propagate error and cause error to be propagated from Outside. Since Outside is declared outside the scope of error, this exception cannot be named explicitly in a handler for Qutside. It can be handled only by 14 . an others clause. The programmer has essentially lost the ability to distinguish error from other exceptions and to handle it distinctly. In programs, exceptions propagated out of their scope become anonymous. Interestingly, the Ada implementation will maintain the identity of error when it is propagated outside its scope. Therefore, if it is propagated back into its scope again - as in this example when Outside is called from B - it will be handled by a handler for error and not by others. The implementation thus behaves as though all exception propagations are within the scope of the exception. If Ada is extended to require propagate declarations (without an others clause), propagation of exceptions beyond their scope is syntactically illegal. For example the package D must be modified. Since A can propagate error and A is a visible procedure of D, the exception error has to be declared in the visible part of D. Then the exception, D.error, is visible to all scopes which can call D.A. Using our documentation, the above example could be written as: package D is - D.error exception declaration error: exception; - all calls to D.A are within the sco~~~-.~~f procedure A propagate error arrert P; D.error procedure B; end procedure Outside propagate D.error assert & ir begin ... D.A; - ... ... - & is the assertion associated with D.error - a handler for D.error could also be included in outside end If one chooses to allow others clauses in propagate declarations as indicated in section 5, then bropagation of exceptions out of their scope becomes possible. In this case our method will work correctly according to the Ada semantics. 15 Proof of file tramfer example We consider a slightly modifled version of the file transfer example given in the Ada rationale in 12.3.3. In addition to propagate and handler assertions, the program is documented with entry, exit, initial, and invariant assertions as introduced for Pascal Plus in [II]. We assume that the system routines get and put have the following specifi- cations: generic(type t) procedure get(j: in out flle of t; z: out t) initial j = j0 entry true exit lempty(jO) /\ j0 = uppend(ht(z), j) propagate end-o j- file arrert empty( fo) A j = j0; generic(type t) procedure put( j:in out flle of t; z: in t) - initial j = j0 entry true exit j = append( jO,list(z)); We prove the correctness of the following transfer procedure; get and put are assumed to be appropriate instances of the generic procedures declared above: procedure tram jer(in j: In out file of t; outj: out fllc of t) initial inj = info entry empty(out j) exit outj = in j0 ir c:t; begin invariant append(out j, in j) = in j0 loop !74w, 4; put(outj, 4 end loop; exception when end-o j- file assert empty(inj) A outj = in j0 =>; end; The correctness is established by proving the following verification conditions. Note, that instead of introducing universal quantifiers we use new variables to 16 construct formulas logically equivalent to the quantified formulas in the procedure call rule. 1) Correctnerr of the handler: The handler code (empty statement) has to achieve the exit condition of the block: (empty(in j) A out j = in j0 + outf = info) This verification condition is trivially true. 1) Corrcctnerr of iairing the exception: The propagate assertion for end-o j- file in get must imply the handler assertion. Since get is called within the loop, it may be assumed that the loop invariant is true when a propagation occurs; the verification condition has the form invariant /\ propagate assertion + handler assertion: inf -0 is a new variable denoting the value of the actual parameter in j of the call to get when propagation occurs. (append(out j, inf) = info A in j = inj,0 A empty(in j) ezpty(inf -0) A out f = info) This verification condition simplifies to true, observing that append(outj, inf) = outf whenever empty&n j). 3) Correctnerr of entering and leaving the loop: The entry condition of procedure transfer must imply the loop invariant; when the loop terminates the loop invariant and the negation of the loop test (-true) must imply the exit condition of transfer. outj,0, in j- 0 are new variables denoting the final values of outj and in j on exit from the loop. - info I\ e’ipt y(out j) . - ( f azpend(outj, in j) = dnjoA (append(outj,0, in j- 0) = info A -rtrue -b outf-0 = in j0)) Propositional reasoning reduces this condition to ( inf = in j0 /\ empty(outf) + 17 append(outf, in j0) = info) which can be seen to be true by properties of append. 4) Correctnerr of loop: The loop invariant must be preserved by executing the loop body: (append(out j, in j) = in j0 A -rempty(in j) A inj = append(ldat(c,O), in j-1) I\ out j- 1 = append(out j, list(c,0)) aTpend(outj-I, inj-1) = info) After some substitutions this becomes append(out j, append(list(c,O), in j-l)) = in j0 aGpend(append(out j, list(c,O)), in j-1) = in j0 which is true by associativity of append. 18