Version 1.6 of ai05s/ai05-0145-2.txt
!standard 13.3.2 (00) 09-12-28 AI05-0145-2/03
!class amendment 09-06-12
!status work item 10-01-14
!status ARG Approved 11-0-1 09-11-07
!status work item 09-06-12
!status received 09-06-12
!priority Medium
!difficulty Medium
!subject Pre- and Postconditions
!summary
To augment the basic "assert" pragma capability in Ada 2005, we propose
to allow the specification of pre/postconditions for subprograms
and entries.
We presume the use of the "aspect_specification" syntax defined in
AI05-0183-1.
!problem
A number of programming paradigms include the heavy use of pre/post
conditions on subprograms. Having a basic standardized "Assert" pragma
supports this approach to some degree, but generally requires that the
Assert pragmas actually be inserted inside the bodies of the associated
code. It would be much more appropriate if these pre/post conditions
appeared on the specification of the subprograms rather than buried in the
body, as they really represents part of the contract. In particular, one
wants any implementation of a given operation to conform to the
pre/post conditions specified, in the same way one wants any implementation
of a package to conform to the constraints of the parameter and result
subtypes of the visible subprograms of the package.
Effectively, pre/post conditions may be considered as generalized,
user-definable constraints. They have the same power to define more
accurately the "contract" between the user and implementor of a package,
and to thereby catch errors in usage or implementation earlier in the
development cycle. They also can provide valuable documentation of the
intended semantics of an abstraction.
!proposal
Using the aspect_specification syntax, we propose to allow the
specification of pre/postconditions for subprograms and entries.
The aspects Pre and Post are used for this purpose. In addition, for
primitive subprograms of tagged types, the aspects Pre'Class and
Post'Class are also available. These aspects are inherited by the
corresponding primitive subprogram of types descended from the original
tagged type. When multiple preconditions apply to a subprogram, they are
"or"ed together (that is, if any of the preconditions is satisfied, the
overall precondition is satisfied). When multiple postconditions apply
to a subprogram, they are "and"ed together (that is, all of the
postconditions must be satisfied for the overall postcondition is
satisfied).
!wording
Add a new section:
13.3.2 Preconditions and Postconditions
Preconditions and postconditions may be specified for subprograms and
entries, using an aspect_specification for aspect Pre, Pre'Class,
Post, or Post'Class. The expression associated with a Pre or Pre'Class
aspect is called a precondition expression, and the expression
associated with a Post or Post'Class aspect is called a *postcondition
expression.*
Name Resolution
The expected type for a precondition or postcondition expression is
the predefined type Boolean. Within a postcondition expression of a
function, the attribute Result is defined for the function, yielding
the value returned by the function. Within a postcondition expression
of a subprogram or entry with at least one IN OUT formal parameter of a
nonlimited type, the attribute Old is defined for each such formal
parameter, yielding the value of the formal parameter at the beginning
of the execution of the subprogram or entry.
Within the expression for a Pre'Class or Post'Class aspect for a primitive
subprogram of a tagged type T, a name that denotes a formal parameter of type
T is interpreted as having type T'Class. Similarly, a name that denotes a
formal access parameter of type access-to-T is interpreted as having type
access-to-T'Class. Finally, if the subprogram is a function returning T or
access T, the Result attribute is interpreted as having type T'Class or
access-to-T'Class, respectively. [Redundant: This ensures the expression is
well-defined for a primitive subprogram of a type descended from T.]
Legality Rules
The Pre or Post aspect shall not be specified for an abstract
subprogram. [Redundant: Only the Pre'Class and Post'Class aspects may
be specified for such a subprogram.]
Static Semantics
If a Pre'Class or Post'Class aspect is specified for a primitive
subprogram of a tagged type T, then the associated expression also
applies to the corresponding primitive subprogram of each descendant
of T.
Dynamic Semantics
If one or more precondition expressions apply to a subprogram or
entry, and the Assertion_Policy in effect at the point of the
subprogram or entry declaration is Check, then upon a call of the
subprogram or entry, after evaluating any actual parameters, a
precondition check is performed. This consists of the evaluation of
the precondition expressions that apply to the subprogram or entry. If
and only if all the precondition expressions evaluate to False, then
Ada.Assertions.Assertion_Error is raised. The order of performing the
checks is not specified, and if any of the precondition expressions
evaluate to True, it is not specified whether the other precondition
expressions are checked. It is not specified whether any check for
elaboration of the subprogram body is performed before or after the
precondition check. It is not specified whether in a call on a
protected operation, the check is performed before or after starting
the protected action. For an entry call, the check is performed prior
to checking whether the entry is open.
If one or more postcondition expressions apply to a subprogram or
entry, and the Assertion_Policy in effect at the point of the
subprogram or entry declaration is Check, then upon successful return
from a call of the subprogram or entry, prior to copying back any
by-copy IN OUT or OUT parameters, a postcondition check is performed.
This consists of the evaluation of the postcondition expressions that
apply to the subprogram or entry. If any of the the postcondition
expressions evaluate to False, then Ada.Assertions.Assertion_Error is
raised. The order of performing the checks is not specified, and if
one of them evaluates to False, it is not specified whether the others
are checked. It is not specified whether any constraint checks
associated with copying back IN OUT or OUT parameters are performed
before or after the postcondition check.
If a precondition or postcondition check fails, the exception is
raised at the point of the call. [Redundant: The exception cannot
be handled inside the called subprogram.]
For a dispatching call or a call via an access-to-subprogram value,
the precondition or postcondition check performed is determined by the
subprogram actually invoked. [Redundant: Note that for a dispatching
call, if there is a Pre'Class aspect that applies to the subprogram
named in the call, then if the precondition expression for that aspect
evaluates to True, the precondition check for the call will succeed.]
If the Assertion_Policy in effect at the point of a
subprogram or entry declaration is Ignore, then no precondition or
postcondition check is performed on a call on that subprogram or entry.
!discussion
This is based on the previous alternative AI05-0145-1. The Pre/Post aspects
are specified using the aspect_specification syntax defined in AI05-0183-1.
There is no message associated with the failure of a precondition or
postcondition check: it was deemed that these annotations are intended for
verification, and that for debugging purposes the Assert pragma is
sufficient.
The new version preserves the flexibility of the previous one concerning
the point at which the aspect is checked: either callee or caller can
perform the check, except for indirect calls, where a wrapper will be
required if the caller normally performs the checks. Note however that
the callee cannot handle the Assertion_Error, as there seems no reason
to allow that degree of implementation dependence.
These aspects can be specified for protected operations and for
protected and task entries. It appears necessary that preconditions on
entries be evaluated by the caller, because their failure should take
place before the caller is suspended waiting on the entry queue. In the
case of an entry that implements a primitive subprogram, the
preconditions would have to be checked by the "wrapper" prior to calling
the entry. We allow the precondition check to be performed inside or
outside the protected action associated with a protected subprogram
call.
!examples
Pre and Post are most useful for untagged types, since Pre'Class and
Post'Class are preferred for dispatching operations. Pre and Post can be
used with concrete dispatching operations - they specify more about the
particular implementation of an operation - but that information can only
be useful with a non-dispatching call.
Using Pre and Post for an untagged type could look like the following:
generic
type Item is private;
package Stacks is
type Stack is private;
function Is_Empty(S : in Stack) return Boolean;
function Is_Full(S : in Stack) return Boolean;
procedure Push(S : in out Stack; I : in Item)
with
Pre => not Is_Full(S),
Post => not Is_Empty(S);
procedure Pop(S : in out Stack; I : out Item)
with
Pre => not Is_Empty(S),
Post => not Is_Full(S);
function Top(S : in Stack) return Item
with
Pre => not Is_Empty(S);
function Is_Valid_Stack(S : in Stack) return Boolean;
Stack_Error : exception;
private
--
end Stacks;
Pre'Class and Post'Class are the only things that make sense for abstract
primitive operations, since there are no non-dispatching calls to them. For
example:
generic
type Item is private;
package Stack_Interfaces is
type Stack is interface;
procedure Push(S : in out Stack; I : in Item) is abstract
with Pre'Class => not Is_Full(S),
Post'Class => not Is_Empty(S);
procedure Pop(S : in out Stack; I : out Item) is abstract
with Pre'Class => not Is_Empty(S),
Post'Class => not Is_Full(S);
function Is_Empty(S : Stack) return Boolean is abstract;
function Is_Full(S : Stack) return Boolean is abstract;
end Stack_Interfaces;
These would be inherited by types which have Stack as an ancestor:
generic
type Item is private;
package Intf is new Stack_Interfaces (Item);
package Bounded_Stacks is
type Bounded_Stack (Capacity : Natural) is new Intf.Stack with private;
procedure Push(S : in out Bounded_Stack; I : in Item);
procedure Pop(S : in out Bounded_Stack; I : out Item);
function Is_Empty(S : Bounded_Stack) return Boolean;
function Is_Full(S : Bounded_Stack) return Boolean;
end Bounded_Stacks;
package Intf_Inst is new Stack_Interfaces (Natural);
package Stk_Inst is new Bounded_Stacks (Natural, Intf_Inst);
A call on Stk_Inst.Push would evaluate "not Is_Empty(S)" as its precondition
expression (that being inherited from the interface).
!ACATS test
!appendix
From: Erhard Ploedereder
Sent: Saturday, November 7, 2009 11:42 AM
I still have 2 issues with AI-145-2.
I still am bothered about the race conditions on preconditions of protected
actions. Normally, I assume that I can use the "usual" Floyd style of
propagating the Pre across the code of the subprogram to determine what holds at
any given point.
Here, the Pre might have been checked ages ago, when I was blocked, and need no
longer hold when I am unblocked. The Floyd-style arguing about the code is not
valid any more. And I do not see how I can make it valid again, short of almost
unreasonable restrictions on the nature of Pre. See also the 2. issue. Local
state may be very important. It is clear that references to shared global state
in Pre will introduce general data race conditions, but that is well known and
unavoidable short of drastic restrictions on Pre. I don't buy the argument that,
if we cannot prevent this general problem, it is not worth to solve the specific
problem.
There are various implementation models possible:
a) check Pre as part of the call: has the above race condition and consequences
b) check after the barrier check: does not "protect" the barrier check,
but everything else. Satisfies Floyd.
c) check before the barrier check and also as part of being unblocked:
"protects" everything, satisfies Floyd, costs a bit more.
In a safe language, I would expect b) or c), certainly not a) as currently
proposed.
----
The other comment that I have is that the difference between subprograms
operating on any (other) composite data and protected operations is striking.
Just because "self" is not a parameter of the subprogram (but is for primitive
ops), direct access to the local state of protected objects is unavailable,
while access to local state of any other object is available in Pre. Such access
is necessary, if timed logic gets involved, e.g., "must have been initialized by
a call on xyz" presumably needs info from the local state to be an executable
Pre.
****************************************************************
From: Bob Duff
Sent: Saturday, November 7, 2009 3:14 PM
...
> There are various implementation models possible:
> a) check Pre as part of the call: has the above race condition and
> consequences
> b) check after the barrier check: does not "protect" the barrier check,
> but everything else. Satisfies Floyd.
> c) check before the barrier check and also as part of being unblocked:
> "protects" everything, satisfies Floyd, costs a bit more.
>
> In a safe language, I would expect b) or c), certainly not a) as
> currently proposed.
Yes, I see what you mean.
> ----
>
> The other comment that I have is that the difference between
> subprograms operating on any (other) composite data and protected
> operations is striking. Just because "self" is not a parameter of the
> subprogram (but is for primitive ops), direct access to the local
> state of protected objects is unavailable, while access to local state
> of any other object is available in Pre. Such access is necessary, if
> timed logic gets involved, e.g., "must have been initialized by a call
> on xyz" presumably needs info from the local state to be an executable Pre.
I don't think that's right. The type has to be private, so Pre has to refer to
some accessor functions if it wants to depend on the innards. So protected
types are no different, here, as far as I can see.
P.S. I'm sitting right next to you as I write this. ;-)
****************************************************************
From: Tucker Taft
Sent: Sunday, November 8, 2009 9:52 PM
I can sympathize with the desire of checking preconditions both before and after
any queuing, but if it makes a difference I believe that it is probably a "bad"
precondition. How is the caller supposed to know if something *will be* true by
the time the entry body/accept statement finally gets executed?
From the entry body's point of view, any part of the conceptual Floyd-ish
precondition that depends on the state of the object should really be part of
the entry barrier. The only part of the precondition that should be enforced on
the caller is the part that they have control over, namely the value of their
parameters.
It would be helpful if you could give an example where a "good"
precondition can legitimately depend on the variable state of the protected
object. That might be more convincing that appealing to Floyd here, since I
don't think he was talking about concurrent data structures.
As far as the specific issue of referening the local state, the
pre/postconditions can only reference visible entities (since they are on the
spec, and are supposed to be meaningful to the caller), but they could call
visible protected functions. Again, though, if the protected function is
returning a value that could change between the time the call is made and the
entry body is executed, then it probably shouldn't be included in a
precondition, or else there is clearly a race condition.
****************************************************************
From: Randy Brukardt
Sent: Monday, December 28, 2009 11:42 PM
...
> I still am bothered about the race conditions on preconditions of
> protected actions.
> Normally, I assume that I can use the "usual" Floyd style of
> propagating the Pre across the code of the subprogram to determine
> what holds at any given point.
I don't think this works in Ada, in the general case. Ada expressions can easily
depend on functions with side-effects, on functions that modify their parameters
(think "Random"), on global variables, on aliased objects that can be modified
by other tasks, constants that aren't really constant, and the like. About all
you can say for sure about an arbitrary precondition expression is that it was
true when it was checked (at the point of the call). You most certainly cannot
assume that it holds throughout the subprogram.
Note that this has nothing whatsoever to do with protected objects; it is a
"feature" of Ada expressions that becomes a "feature" of Assertions,
preconditions, invariants, and anything else that you like.
A fine example of that is the Claw "precondition" of object validity. For
instance, in the following (written using our new syntax rather than the textual
comments actually used in Claw):
procedure Move (Window : in out Root_Window_Type; Position : in Point_Type)
with Pre'Class => Is_Valid (Window);
The problem is that the user of the Claw application can close a window
asynchronously to the application code. So simply putting
if Is_Valid (Window) then
Move (Window, Top_Left);
end if;
is insufficient to prevent Not_Valid_Error from happening. (Indeed, there is no
way to prevent it; handling the exception is the only alternative.) [Yes, the
state changes are synchronized to prevent dangerous race conditions related to
the actual state change; we tried to prevent higher level race conditions, but
that simply made responsiveness terrible and often caused deadlocks in typical
code.]
> Here, the Pre might have been checked ages ago, when I was blocked,
> and need no longer hold when I am unblocked. The Floyd-style arguing
> about the code is not valid any more. And I do not see how I can make
> it valid again, short of almost unreasonable restrictions on the
> nature of Pre.
The *only* way to be able to argue *anything* about Pre *anywhere* is to put
"almost unreasonable restrictions" on the nature of Pre. Pretty much the only
Pre that can be guaranteed to work is one that could have been used in a
function that has Global In/Global Out equal to null -- this is substantially
stronger than the restrictions of a Pure function. Anything else will be at risk
of becoming untrue because of something some other task does (even in the
supposedly protected code).
For instance, the problem with the Claw Is_Valid function is that other tasks
have access to part of the Window object, and thus can change the state of that
object. That problem will happen for any object that has a portion that is
potentially shared with another task (which, of course, includes the variable
parts of all protected objects).
One hopes that there will exist tools that detect and warn about "bad"
preconditions (and all of the other sorts of contracts that we're planning to
add). Without that, it will be very hard to reason about anything.
My primary point here is that your concerns about protected objects are just the
tip of the iceberg. If you write "good" preconditions, there won't be any
problem with using Floyd, but if the preconditions can change (and that is true
for many preconditions that might be written in a real program), they're
essentially meaningless. The blocking of the task at the barrier might make this
problem somewhat more visible, but it is hardly worth worrying about on its own.
****************************************************************
From: Tucker Taft
Sent: Tuesday, December 29, 2009 8:38 AM
I essentially agree with Randy, as I wrote on 11/8/2009.
Let me know if I need to repost that message.
****************************************************************
From: Bob Duff
Sent: Tuesday, December 29, 2009 11:03 AM
(How did you get to be such an egregious top-poster, Tuck? ;-))
Tucker Taft wrote:
> I essentially agree with Randy, as I wrote on 11/8/2009.
I agree with Randy's conclusion, that the precondition should be checked before an entry call (after locking the lock), and not at the beginning of the entry body. I suppose it should also be checked on a requeue, right?
The caller has to ensure that the precondition is true, and the caller can't be expected to know what goes on between enqueueing the call and starting the body.
Yes, that could cause race conditions -- it's the responsibility of the protected type to deal with it (e.g. don't write preconditions that can change in that time).
But I don't entirely agree with Randy's reasoning -- Randy is overly pessimistic, as is his wont ;-), when he says:
> The *only* way to be able to argue *anything* about Pre *anywhere* is
> to put "almost unreasonable restrictions" on the nature of Pre.
One can prove that:
If there are no shared variables, then procedure P's precondition
is True at the start of P.
(I'm assuming we have globals annotations.) A tool that knows that could be extremely useful, so long as its users understand that they must analyze any shared variables separately "by hand".
Alternatively, one could come up with annotations that allow the proof tool (the compiler?) to know about shared variables.
I should have suggested that before the deadline for new ideas -- at least part of my brain understood the issue, because such annotations exist in my hobby language.
Too late -- but it could be an impl-def pragma or something.
Another alternative (which I don't like) would be for the tool to do whole-program analysis. I don't like it because it's slow. I also don't like it because it's not modular: I don't just want to prove that a procedure works in this particular program;
I want to prove that it works, period, even if other parts of the program are modified.
- Bob
P.S. Because you have to read the conversation backwards.
P.P.S. Why is top-posting evil?
;-)
****************************************************************
From: Pascal Leroy
Sent: Tuesday, December 29, 2009 5:18 AM
> One hopes that there will exist tools that detect and warn about "bad"
> preconditions (and all of the other sorts of contracts that we're planning
> to add). Without that, it will be very hard to reason about anything.
I couldn't agree more: these contracts are either pure-ish and not terribly
useful, or full of dependencies on global/external state, and devilishly hard to
analyze. This is exactly why I think that all this contract business is useless
at best and actively harmful at worst.
****************************************************************
From: Tucker Taft
Sent: Tuesday, December 29, 2009 1:41 PM
...
> This is exactly why I think that all this
> contract business is useless at best and actively harmful at worst.
Yeah, but how do you really feel? ;-)
I'll admit I am curious why you have developed such negative feelings toward
"all this contract business." Is it based on bad experience, or on the potential
for trouble if preconditions are impure?
We certainly use "pragma Assert" heavily, to good effect, for the equivalent of
preconditions in our static analysis tool. Usually there is also a comment such
as "--#pre XXX" or "-- Requires: XXX". In most cases these would translate
rather directly into "with Pre => XXX" which would seem to be a feature. Where
does the "active harm" arise?
****************************************************************
From: Bob Duff
Sent: Tuesday, December 29, 2009 2:12 PM
> I couldn't agree more: these contracts are either pure-ish and not
> terribly useful, or full of dependencies on global/external state, and
> devilishly hard to analyze. This is exactly why I think that all this
> contract business is useless at best and actively harmful at worst.
I'm puzzled by that comment. Why would anyone want to refer to lots of global
variables in a precondition? I'd expect them to be mostly "pure-ish" and
"terribly useful".
****************************************************************
From: Steve Baird
Sent: Wednesday, January 13, 2010 11:15 PM
The pragma-based version of the AI-145 included an intended implementation model
for the 'Old attribute. [That's AI-0145-1 - Editor.]
It was defined in terms of a wrapper which saves the needed values as local
constants, calls the "real" subprogram, and then uses the saved values as needed
in order to provide 'Old attribute values in postconditions.
It seems like we need to say something about the case where the parameter type
has controlled parts. We'd better get the right Adjust/Finalize calls, or
problems will result.
1) Do you agree that some action is needed to address this issue?
2) If so, then what?
****************************************************************
From: Randy Brukardt
Sent: Thursday, January 14, 2010 1:20 AM
The current definitions of the attributes is just completely wrong, in that it
doesn't follow the style of the RM for defining attributes at all. E'Count is
similar to these (in that its use is highly restricted), and it manages to be
defined in the correct style (see 9.9(4-5)). Following that style is necessary
for them to show up in the automatically generated Annex K, which I think most
Ada programmers refer to frequently.
Moreover, I agree that there is no explanation of the *real* dynamic semantics
of these attributes. We did agree that copies of the parameter values are made
on entry to the call (using the normal Ada semantics), and that needs to be
explained. Moreover, we need to decide when that happens (Always? That's
expensive. Only when the attribute is used in a postcondition? That's yucky as
improving a postcondition could change the behavior of the program rather
dramatically [the copy of the parameter would prevent the original parameter
from being freed after a re-assignment, for instance].)
Anyway, this gives me a good excuse for not putting this AI into the current
standard draft, which is good because the associated syntax, resolution rules,
and freezing rules are in the unfinished AI-183. We shouldn’t have approved this
one anyway without.
****************************************************************
From: Randy Brukardt
Sent: Thursday, January 14, 2010 1:55 AM
It seems to me that in extreme cases, the use of 'Old could invalidate the
entire Postcondition. So the semantics need to be nailed down.
For example, consider:
package Ref_Count is
type Ref_Counted_Type is new Controlled with private;
function Count (Obj : Ref_Counted_Type) return Natural;
-- Return the reference count for Obj.
procedure Free (Obj : in out Ref_Counted_Type);
-- Free this instance of Obj.
private
... -- Define Initialize, Adjust, and Finalize as needed.
end Ref_Count;
procedure Do_It (Obj : in out Ref_Counted_Type; ...) with
Post => (if <some_condition> then (if Count(Obj'Old) = 1 then Count(Obj) = 0)
else (Count(Obj'Old) = Count(Obj))) is
begin
if <Some_condition> then Free (Obj)
else ...
end if;
end Do_It;
The problem is, the copy of Obj made to support the 'Old attribute will increase
the reference count, so the object will not be freed when the Postcondition is
executed: Count(Obj) could not be zero yet.
(Yes, I know this is a lousy Postcondition -- I said it was in extreme cases...)
****************************************************************
Questions? Ask the ACAA Technical Agent