4

Assume having the following setup:

type My is new Integer;
type My_Acc is access My;

procedure Replace(Self : in out My_Acc; New_Int : Integer)
  with Pre => New_Int /= Self.all, Post => Self'Old.all /= Self.all; 

Note: Code above might not be fully valid, but I hope the concept is understandable.

Now what happens if Unchecked_Deallocation() is used on Self inside Replace and a new Integer is allocated and set to Self (This should result in Self'Old pointing to a now invalid memory location)?

Does Ada keep kind of a snapshot where Self'Old points to the previous memory location, but before Unchecked_Deallocation() is executed?

If Self'Old would get invalid for use in the Post contract, how could you still access the previous value? Is it possible to create a manual snapshot in the Pre contract that can then be used in Post? Maybe it can be achieved using Ghost_Code?

I want to make everything in Spark, in case that changes something.

Edit: Fixed Self to in out as mentioned by Simon Wright.

Edit: Fixed type of Self to allow null

mhatzl
  • 173
  • 1
  • 7

2 Answers2

3

It may be that the latest versions of SPARK support access types; it used not to, at all.

First, your Not_Null_My_Acc needs to be a subtype of My_Acc, unless you meant it to be a type in its own right.

Second, you can’t deallocate Self inside Replace and allocate a new value; Self is in-mode, & hence not writable.

Third, you can’t apply ’Old to Self, because

warning: attribute "Old" applied to constant has no effect

What you can say is

Post => Self.all'Old /= Self.all;
Simon Wright
  • 25,108
  • 2
  • 35
  • 62
  • A my bad. I meant to set `Self` to `in out`. With `in out` it should now be possible to set `Self` to `null`. – mhatzl Oct 14 '21 at 12:11
  • Looks to me as though that doesn’t work - that is, my test program fails at the postcondition – Simon Wright Oct 14 '21 at 13:06
  • I will try an alternative approach with Ghost variables. If this works I will add it as possible answer/workaround. Thanks so far for your feedback. – mhatzl Oct 14 '21 at 13:31
2

In ARM 6.1.1(26ff) it says

Each X'Old in a postcondition expression that is enabled denotes a constant that is implicitly declared at the beginning of the subprogram body, entry body, or accept statement.

The implicitly declared entity denoted by each occurrence of X'Old is declared as follows: ...

X'Old : constant S := X;

... in other words, nothing fancy is expected, just a straight copy of (in this case) Self: not Self.all.

So, if your Replace deallocates Self, then Self’Old is a dangling reference, and erroneous.

I suggested previously that changing the postcondition to

Post => Self.all'Old /= Self.all;

would be safe; why wouldn’t that meet your requirements? is there something going on you haven’t told us about?


Note the subtle difference between Self’Old.all and Self.all’Old. The first one takes a copy of Self as it was before the call, which gets dereferenced after the call (by which time it’s pointing into hyperspace), while the second one dereferences the prior Self and copies the integer value it finds there; on return that’s still valid.

Simon Wright
  • 25,108
  • 2
  • 35
  • 62
  • Thanks for your research. Is your solution with `Post => Self.all'Old /= Self.all;` comparing the Integer values? I thought this is not possible if `Replace` deallocates `Self` since the old Integer object does not exist anymore. – mhatzl Oct 15 '21 at 12:11
  • 1
    Added some explanation at the end of the answer. – Simon Wright Oct 15 '21 at 21:08