predicate Contains(haystack: string, needle: string)
ensures Contains(haystack, needle) <==> exists k :: 0 <= k <= |haystack| && needle <= haystack[k..]
ensures Contains(haystack, needle) <==> exists i :: 0 <= i <= |haystack| && (needle <= haystack[i..])
ensures !Contains(haystack, needle) <==> forall i :: 0 <= i <= |haystack| ==> !(needle <= haystack[i..])
{
if needle <= haystack then
assert haystack[0..] == haystack;
true
else if |haystack| > 0 then
assert forall i :: 1 <= i <= |haystack| ==> haystack[i..] == haystack[1..][(i-1)..];
Contains(haystack[1..], needle)
else
false
}
Testing this predicate shows some interesting but frustrating behavior.
method TestContains() {
assert Contains("aba||xx||c", "|") == true; //verifies
assert Contains("aba||xx||c", "||") == true; //verifies
assert Contains("aba||xx||c", "||xx||") == true; //verifies
assert Contains("aba||xx||c", "|||") == false; //does not verify
}
In particular Dafny cannot evaluate whether Contains("aba||xx||c", "|||")
is true or false. Neither assertion will verify. All obvious true examples for Contains verify quickly but any false statement with a separator of 1 letter like Contains("aa", "b")
will verify, but with 2 letters or more it will no longer verify, like Contains("axa", "ba")
.
Is there a way to increase the limit or otherwise convince Dafny to verify them? I tried to use {:fuel X} but it did not seem to help.
Here's one way to convince Dafny:
Here's how I actually discovered this trick. I worked backwards from the failing assertion. I decided to use a
calc
to manually step through the execution ofContains
until Dafny got stuck:So then I started trying to prove that
Contains("xa", "ba") == Contains("a", "ba")
. Reading the code ofContains
, we need to know that!("ba" <= "xa")
, but Dafny can't see that either.So then I started trying to prove that. Since
<=
is built-in, we can't look at its definition easily (or can we? see below). But I guessed that roughly speaking it was defined as "all the characters are the same up until the end of the shorter string". So I figured, if I can show that some character is not the same, maybe Dafny will see it.That assertion passes, and what's more, it unlocks all the other failing asserts and the whole thing verifies. Here's the state of the program as of when I first got it to verify.
Then I did some typical cleanup-after-debugging and got to the program at the top of this answer.
If you are curious, it actually is pretty easy to find the definition of
<=
. Use this command:This will generate the underlying Boogie code. (Replace
foo.dfy
with the name of your file andfoo.bpl
with your desired output file name.)Then open
foo.bpl
in your text editor and search for the lineYou will see an
axiom
on the next line that, among other things, callsSeq#SameUntil
. So we've learned that that's the Boogie-level name for what Dafny calls string-<=
.Now search for
and you will find its defining axiom:
Turns out, it's exactly what I guessed.