-
|
Hello! I'm running into an issue with correctly specifying a modifies clause for a program that create a sequence of objects and then modifies those object. To preface this, I have found a work-around by refactoring my code, but I'm curious if there is a way to fix the issue without refactoring my code and instead providing Dafny more information. Related PostsDiscussion 2164: relates to modifies clauses for loops and methods, but does not cover my specific warning. Detailed Problem DescriptionI created the following program that represents a small example of what I'm trying to accomplish. I have a object type, class Counter {
var x: nat
constructor() { x := 0; }
method inc()
modifies this`x
ensures old(x) + 1 == x
{
x := x + 1;
}
}
// Impl0 does not verify
method Impl0()
{
var counters: seq<Counter> := [];
for i := 0 to 10
{
var c := new Counter();
counters := counters + [c];
}
var n := 0;
while n < 10
modifies counters[..]
{
for i := 0 to |counters|
{
counters[i].inc();
}
n := n + 1;
}
}
// Impl1 verifies just fine
// In this method I have refactored out the creation of the
// sequence of counters.
method Impl1(counters: seq<Counter>)
modifies counters[..]
{
var n := 0;
while n < 10
modifies counters[..]
{
for i := 0 to |counters|
{
counters[i].inc();
}
n := n + 1;
}
}The method This is the specific error message: The method I'd like to understand why Thank you! Have a great day.
DisclaimerThe presented program is a silly program that doesn't appear to accomplish anything useful, but my actual program is full of large objects and lots of additional logic so I tried to create a pared down version that contains the same behavior. Dafny InformationI ran my example on MacOS Tahoe 26.1 with Dafny version 4.11.0 and Z3 4.15.4 (64-bit) |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
|
Well, apparently I should have read this Zulip thread that I linked in my related posts section more carefully, because it actually contained the solution. SolutionWhen allocating each object in method Impl2()
{
var counters: seq<Counter> := [];
for i := 0 to 10
invariant |counters| == i
// Inform Dafny that each Counter is freshly allocated
invariant forall j | 0 <= j < i :: fresh(counters[j])
{
var c := new Counter();
counters := counters + [c];
}
// This assertion is just there for demonstration, and is not necessary for verification
assert forall j | j in counters :: fresh(j);
var n := 0;
while n < 10
modifies counters[..]
{
for i := 0 to |counters|
{
counters[i].inc();
}
n := n + 1;
}
}Bonus ContentIn my original example I was just modifying a natural number in each In the example below I've created an additional class,
To accomplish step 3. I used a set comprehension (I think that's the name), which I'm not sure if that is the best way but it was the way that worked for me. class Counter {
var x: nat
constructor() { x := 0; }
method inc()
modifies this`x
ensures old(x) + 1 == x
{
x := x + 1;
}
}
class Counters {
// Marking these as const indicates that they are allocated once
// once in the constructor, but then are not allocated again. The
// const modifier only applies to the reference (in my understanding)
// and not the variables inside each Counter object
const c0: Counter
const c1: Counter
constructor()
// Provide information that each Counters object freshly
// constructs c0 and c1
ensures fresh(c0) && fresh(c1)
{
c0 := new Counter();
c1 := new Counter();
}
method mod()
modifies this.c0
}
method Impl()
{
var seqOfCounters: seq<Counters> := [];
for i := 0 to 10
invariant |seqOfCounters| == i
// Update invariant to also include fresh(c0)
invariant forall j | 0 <= j < i :: fresh(seqOfCounters[j]) && fresh(seqOfCounters[j].c0)
{
var c := new Counters();
seqOfCounters := seqOfCounters + [c];
}
for i := 0 to 10
modifies seqOfCounters[..]
// Indicate that the set of `c0` objects in each `Counters` object are modified
modifies (set x | x in seqOfCounters :: x.c0)
{
for j := 0 to |seqOfCounters|
{
seqOfCounters[j].mod();
}
}
} |
Beta Was this translation helpful? Give feedback.
Well, apparently I should have read this Zulip thread that I linked in my related posts section more carefully, because it actually contained the solution.
Solution
When allocating each object in
countersI need to remind Dafny that it's a freshly allocated object--i.e., eachCounterwas freshly allocated inside of theImplmethod. Here is version of my problem that verifies correctly: