2

I am trying to write a dafny program that has an array of a fixed size. This array can then be added to via a method if it has not been filled and the values being added do not already exist in the array. At first it seemed to run fine, however, when I call the method more than 4 times, I get an error

SimpleTest.dfy(37,15): Error: A precondition for this call might not hold.
SimpleTest.dfy(17,23): Related location: This is the precondition that might not hold.
Execution trace:
    (0,0): anon0

which highlights the line requires x !in arr[..] from the MCVE below.

Why does the precondition start to fail once the method has been called more than four times? It seemingly happens no matter how large the fixed size of the array is

class {:autocontracts} Test
{
    var arr: array<nat>;
    var count: nat;

    constructor(maxArrSize: nat)
        requires maxArrSize > 1
        ensures count == 0
        ensures arr.Length == maxArrSize
        ensures forall i :: 0 <= i < arr.Length ==> arr[i] == 0
    {
        arr := new nat[maxArrSize](_ => 0);
        count := 0;
    }

    method AddIn(x: nat)
        requires x !in arr[..]
        requires x > 0
        requires 0 < arr.Length
        requires count < arr.Length
        ensures arr[..] == old(arr[.. count]) + [x] + old(arr[count + 1 ..])
        ensures count == old(count) + 1
        ensures arr == old(arr)
    {
        arr[count] := x;
        count := count + 1;
    }
}

method Main()
{
    var t := new Test(20);
    t.AddIn(345);
    t.AddIn(654);
    t.AddIn(542);
    t.AddIn(56);
    t.AddIn(76);
    t.AddIn(8786);
    print t.arr[..];
    print "\n";
    print t.count;
    print " / ";
    print t.arr.Length;
    print "\n";
}
Dan
  • 7,286
  • 6
  • 49
  • 114

1 Answers1

3

To prove the precondition of the method, the verifier has to go through a lot of cases, each of which uses properties of the heap, sequences, and the quantifier in the constructor postcondition. It seems these cases are exhausting some limit in the verifier, and therefore you're getting an error that it's unable to prove something.

You can help the verifier along by writing some assertions. These assertions will also confirm your own understanding of the program state your program is building up. For example, if you add these assert statements, the verifier both confirms the assertions and is able to prove your entire program.

method Main()
{
  var t := new Test(20);
  assert t.arr[..] == seq(20, _ => 0);
  t.AddIn(345);
  assert t.arr[..] == [345] + seq(19, _ => 0);
  t.AddIn(654);
  assert t.arr[..] == [345, 654] + seq(18, _ => 0);
  t.AddIn(542);
  assert t.arr[..] == [345, 654, 542] + seq(17, _ => 0);
  t.AddIn(56);
  assert t.arr[..] == [345, 654, 542, 56] + seq(16, _ => 0);
  t.AddIn(76);
  assert t.arr[..] == [345, 654, 542, 56, 76] + seq(15, _ => 0);
  t.AddIn(8786);
  assert t.arr[..] == [345, 654, 542, 56, 76, 8786] + seq(14, _ => 0);
  print t.arr[..];
  print "\n";
  print t.count;
  print " / ";
  print t.arr.Length;
  print "\n";
}

You don't need all of these assertions, but I left them here so you can see the general form.

The reason everything verifies with these additional assertions is that each assertion can be proved fairly easily from the prior one. Thus, the additional assertions lead the verifier to a quicker proof (and, in particular, leads the verifier to a proof before it gives up).

Btw, your specification of the Test class reveals everything about the internal representation of Test objects. If you can hide those details from a caller, you generally end up with more information hiding, nicer specifications, and also better prover performance. To do this, you need to add an object invariant (idiomatically encoded in Dafny as a Valid() predicate). I would have a lot to explain. But I see you're already using {:autocontracts}, so perhaps you know some things about this. Thus, without further explanations, here is what the specifications of your class can look like in that more abstract form.

class {:autocontracts} Test
{
  // The public view of the Test object is described by the following two fields:
  ghost var Contents: seq<nat>
  ghost var MaxSize: nat
  // The private implementation of the Test object is given in terms of the
  // following fields. These fields are never mentioned in pre/post specifications.
  var arr: array<nat>
  var count: nat

  predicate Valid() {
    count <= arr.Length == MaxSize &&
    Contents == arr[..count]
  }

  constructor(maxArrSize: nat)
    ensures Contents == [] && MaxSize == maxArrSize
  {
    arr := new nat[maxArrSize];
    Contents, MaxSize, count := [], maxArrSize, 0;
  }

  method AddIn(x: nat)
    requires x !in Contents
    requires |Contents| < MaxSize
    ensures Contents == old(Contents) + [x] && MaxSize == old(MaxSize)
  {
    arr[count], count := x, count + 1;
    Contents := Contents + [x];
  }
}

method Main()
{
  var t := new Test(20);
  t.AddIn(345);
  t.AddIn(654);
  t.AddIn(542);
  t.AddIn(56);
  t.AddIn(76);
  t.AddIn(8786);
  print t.arr[..], "\n";
  print t.count, " / ", t.arr.Length, "\n";
}

Note, in this version, no additional assert statements are needed to verify the program.

Rustan Leino
  • 1,954
  • 11
  • 8
  • Thanks for the answer. I knew about the `Valid` predicate and it is actually used in my main program, it just wasn't being used for this part. I didn't know about the ghost var's though so that is really helpful. – Dan Apr 20 '21 at 15:58