Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Surprising Verification Failures with Nested Datatypes + Classes #5370

Open
whonore opened this issue Apr 26, 2024 · 2 comments
Open

Surprising Verification Failures with Nested Datatypes + Classes #5370

whonore opened this issue Apr 26, 2024 · 2 comments
Labels
during 2: compilation of correct program Dafny rejects a valid program during compilation kind: bug Crashes, unsoundness, incorrect output, etc. If possible, add a `part:` label part: verifier Translation from Dafny to Boogie (translator) priority: next Will consider working on this after in progress work is done

Comments

@whonore
Copy link

whonore commented Apr 26, 2024

Dafny version

4.6.0

Code to produce this issue

class C {
  function Foo(): int { 0 }
}

class Box<T> {
  var inner: T
  constructor(t: T) { inner := t; }
}

datatype Wrap<T> = Wrap(inner: T)

class D {
  method Fail1(c: Box<Wrap<C>>)
    ensures c.inner.inner.Foo() == 0
  {}

  method Fail2(c: Wrap<Wrap<C>>)
    ensures c.inner.inner.Foo() == 0
  {}

  method Ok1(c: Box<Wrap<C>>)
    ensures c.inner.inner.Foo() == 0
  {
    assert c.inner.inner.Foo() == 0;
  }

  method Ok2(c: Box<Wrap<C>>)
    ensures c.inner.inner.Foo() == 0
  {
    assert (c.inner.inner as object) != this;
  }

  method Ok3(c: Box<C>)
    ensures c.inner.Foo() == 0
  {}

  method Ok4(c: Wrap<C>)
    ensures c.inner.Foo() == 0
  {}

  method Ok5(c: Wrap<Box<C>>)
    ensures c.inner.inner.Foo() == 0
  {}

  method Ok6(c: Box<Box<C>>)
    ensures c.inner.inner.Foo() == 0
  {}
}

Command to run and resulting output

$ dafny verify test.dfy

test.dfy(15,2): Error: a postcondition could not be proved on this return path
   |
15 |   {}
   |   ^

test.dfy(14,12): Related location: this is the postcondition that could not be proved
   |
14 |     ensures c.inner.inner.Foo() == 0
   |             ^^^^^^^^^^^^^^^^^^^^^^^^

test.dfy(19,2): Error: a postcondition could not be proved on this return path
   |
19 |   {}
   |   ^

test.dfy(18,12): Related location: this is the postcondition that could not be proved
   |
18 |     ensures c.inner.inner.Foo() == 0
   |             ^^^^^^^^^^^^^^^^^^^^^^^^


Dafny program verifier finished with 15 verified, 2 errors

What happened?

I'd expect all of the postconditions to verify without additional hints. I find it especially surprising that the assertion in Ok2 does anything useful.

What type of operating system are you experiencing the problem on?

Mac

@whonore whonore added the kind: bug Crashes, unsoundness, incorrect output, etc. If possible, add a `part:` label label Apr 26, 2024
@keyboardDrummer
Copy link
Member

I agree, this is unexpected behavior.

@keyboardDrummer keyboardDrummer added priority: next Will consider working on this after in progress work is done during 2: compilation of correct program Dafny rejects a valid program during compilation part: verifier Translation from Dafny to Boogie (translator) labels Apr 29, 2024
@RustanLeino
Copy link
Collaborator

RustanLeino commented Apr 29, 2024

Thanks you for the many useful test cases! (Note from @keyboardDrummer: this is indeed a bug)

Workaround

As a workaround, adding any one of these three statements in the body of Fail1 makes the postcondition verify:

assert c.inner.inner.Foo() == 0;
assert c.inner.Wrap?;
var cc := c.inner.inner;

Notes on fixing the problem in the Dafny verifier

The problem is that the verifier needs the information that c.inner (in Fail1) has type Wrap<C> or, alternatively, that c.inner.Wrap? holds. This assumption is generated by the verifier, but the information is missing since the c.inner appears in a postcondition.

The information is generated into a free ensures postcondition for Impl$$_module.__default.Fail1 in Boogie. However, a free ensures in Boogie is ignored when checking an implementation. The Boogie attribute {:always_assume} that turns a free ensures into an assumption when verifying the implementation. The fix is to add that attribute (to all "assume conditions" generated by TrSplitExpr, both when those condition go into free ensures and when they go into free requires. This fix may have a large impact on other verifications.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
during 2: compilation of correct program Dafny rejects a valid program during compilation kind: bug Crashes, unsoundness, incorrect output, etc. If possible, add a `part:` label part: verifier Translation from Dafny to Boogie (translator) priority: next Will consider working on this after in progress work is done
Projects
None yet
Development

No branches or pull requests

3 participants