Skip to content

Commit

Permalink
fix: Omit type antecedents for unassigned variables (#5877)
Browse files Browse the repository at this point in the history
In let expressions and named function results, the bound variable was
previously introduced with an assumption that the variable satisfied its
type. That is not sound, since the type may be empty. This PR removes
those premature type assumptions.

Fixes #1958

<small>By submitting this pull request, I confirm that my contribution
is made under the terms of the [MIT
license](https://github.com/dafny-lang/dafny/blob/master/LICENSE.txt).</small>
  • Loading branch information
RustanLeino authored Nov 6, 2024
1 parent b1dda0f commit e815f23
Show file tree
Hide file tree
Showing 11 changed files with 223 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1483,7 +1483,9 @@ void CheckWellformedSpecialFunction(FunctionCallExpr expr, WFOptions options, Bp
void CheckWellformedLetExprWithResult(LetExpr e, WFOptions wfOptions, Variables locals,
BoogieStmtListBuilder builder, ExpressionTranslator etran, bool checkRhs, AddResultCommands addResultCommands) {
if (e.Exact) {
var substMap = SetupBoundVarsAsLocals(e.BoundVars.ToList<BoundVar>(), builder, locals, etran, "#Z");
// Note, in the following line, we do NOT add an assumption about the type antecedent, because we don't yet know that a value even
// exists; rather, we ignore the type antecedent.
var substMap = SetupBoundVarsAsLocals(e.BoundVars.ToList<BoundVar>(), out _, builder, locals, etran, "#Z");
Contract.Assert(e.LHSs.Count == e.RHSs.Count); // checked by resolution
var varNameGen = CurrentIdGenerator.NestedFreshIdGenerator("let#");
for (int i = 0; i < e.LHSs.Count; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,11 @@ private BoogieStmtListBuilder GetPostCheckBuilder(Function f, ExpressionTranslat
var wh = generator.GetWhereClause(f.tok, funcAppl, f.ResultType, etran, NOALLOC);
if (wh != null) {
postCheckBuilder.Add(TrAssumeCmd(f.tok, wh));
if (f.Result != null) {
var resultVarId = new Bpl.IdentifierExpr(f.Result.tok, f.Result.AssignUniqueName(f.IdGenerator), generator.TrType(f.Result.Type));
wh = generator.GetWhereClause(f.Result.tok, resultVarId, f.Result.Type, etran, NOALLOC);
postCheckBuilder.Add(TrAssumeCmd(f.Result.tok, wh));
}
}
// Now for the ensures clauses
foreach (AttributedExpression p in f.Ens) {
Expand Down Expand Up @@ -356,10 +361,8 @@ private List<Variable> GetWellformednessProcedureOutParameters(Function f, Expre
Formal p = f.Result;
Contract.Assert(!p.IsOld);
Bpl.Type varType = generator.TrType(p.Type);
Expr wh = generator.GetWhereClause(p.tok, new Bpl.IdentifierExpr(p.tok, p.AssignUniqueName(f.IdGenerator), varType),
p.Type, etran, NOALLOC);
outParams.Add(new Bpl.Formal(p.tok, new TypedIdent(p.tok, p.AssignUniqueName(f.IdGenerator), varType, wh),
true));
// Note, this variable should NOT have a "where" clause, because it gets assumed already at the beginning of the CheckWellformed procedure
outParams.Add(new Bpl.Formal(p.tok, new TypedIdent(p.tok, p.AssignUniqueName(f.IdGenerator), varType), true));
}

return outParams;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ CoinductiveProofs.dfy(208,21): Related location: this is the postcondition that
CoinductiveProofs.dfy(4,23): Related location: this proposition could not be proved

Dafny program verifier finished with 23 verified, 12 errors
Total resources used is 779029
Total resources used is 777854
Max resources used by VC is 66829
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Fuel.legacy.dfy(247,22): Error: assertion might not hold
Fuel.legacy.dfy(280,26): Error: assertion might not hold
Fuel.legacy.dfy(335,26): Error: function precondition could not be proved
Fuel.legacy.dfy(324,21): Related location
Fuel.legacy.dfy(313,41): Related location
Fuel.legacy.dfy(314,93): Related location
Fuel.legacy.dfy(335,26): Error: function precondition could not be proved
Fuel.legacy.dfy(324,21): Related location
Fuel.legacy.dfy(312,43): Related location
Expand All @@ -27,13 +27,13 @@ Fuel.legacy.dfy(324,21): Related location
Fuel.legacy.dfy(312,58): Related location
Fuel.legacy.dfy(335,26): Error: function precondition could not be proved
Fuel.legacy.dfy(324,21): Related location
Fuel.legacy.dfy(314,46): Related location
Fuel.legacy.dfy(313,41): Related location
Fuel.legacy.dfy(335,26): Error: function precondition could not be proved
Fuel.legacy.dfy(324,21): Related location
Fuel.legacy.dfy(314,72): Related location
Fuel.legacy.dfy(314,46): Related location
Fuel.legacy.dfy(335,26): Error: function precondition could not be proved
Fuel.legacy.dfy(324,21): Related location
Fuel.legacy.dfy(314,93): Related location
Fuel.legacy.dfy(314,72): Related location
Fuel.legacy.dfy(335,49): Error: destructor 't' can only be applied to datatype values constructed by 'VTuple'
Fuel.legacy.dfy(335,50): Error: index out of range
Fuel.legacy.dfy(336,38): Error: index out of range
Expand All @@ -43,19 +43,19 @@ Fuel.legacy.dfy(329,21): Related location
Fuel.legacy.dfy(311,43): Related location
Fuel.legacy.dfy(336,45): Error: function precondition could not be proved
Fuel.legacy.dfy(329,21): Related location
Fuel.legacy.dfy(312,43): Related location
Fuel.legacy.dfy(313,41): Related location
Fuel.legacy.dfy(336,45): Error: function precondition could not be proved
Fuel.legacy.dfy(329,21): Related location
Fuel.legacy.dfy(313,41): Related location
Fuel.legacy.dfy(314,72): Related location
Fuel.legacy.dfy(336,45): Error: function precondition could not be proved
Fuel.legacy.dfy(329,21): Related location
Fuel.legacy.dfy(312,58): Related location
Fuel.legacy.dfy(314,93): Related location
Fuel.legacy.dfy(336,45): Error: function precondition could not be proved
Fuel.legacy.dfy(329,21): Related location
Fuel.legacy.dfy(314,72): Related location
Fuel.legacy.dfy(312,58): Related location
Fuel.legacy.dfy(336,45): Error: function precondition could not be proved
Fuel.legacy.dfy(329,21): Related location
Fuel.legacy.dfy(314,93): Related location
Fuel.legacy.dfy(312,43): Related location
Fuel.legacy.dfy(336,71): Error: index out of range
Fuel.legacy.dfy(397,22): Error: assertion might not hold
Fuel.legacy.dfy(398,22): Error: assertion might not hold
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,5 @@ SubsetTypes.dfy(459,15): Error: assertion might not hold
SubsetTypes.dfy(464,13): Error: assertion might not hold

Dafny program verifier finished with 13 verified, 91 errors
Total resources used is 775600
Total resources used is 775200
Max resources used by VC is 101900
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

Dafny program verifier finished with 272 verified, 0 errors
Total resources used is 30526126
Total resources used is 30525479
Max resources used by VC is 2048364
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

Dafny program verifier finished with 276 verified, 0 errors
Total resources used is 28791320
Total resources used is 28790607
Max resources used by VC is 974212
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// RUN: %exits-with 4 %verify --type-system-refresh --general-traits=datatype "%s" > "%t"
// RUN: %diff "%s.expect" "%t"

module Example0 {
datatype BaseType = Ctor(x: int)
{
predicate i()
}

type R = r: BaseType | r.i() witness *

function F0(v: nat): R { // anonymous result value
var o: R := Ctor(v); // error: this BaseType might not be an R
o
}

function F1(v: nat): (r: R) { // named result value
var o: R := Ctor(v); // error: this BaseType might not be an R
o
}
}

module Example1 {
datatype BaseType = Ctor(x: int)
{
predicate i()
}

type R = r: BaseType | r.i() witness *

function G0(v: nat): R { // anonymous result value
Ctor(v) // error: this BaseType might not be an R
}

function G1(v: nat): (r: R) { // named result value
Ctor(v) // error: this BaseType might not be an R
}
}

module Example2 {
type BaseType(!new)
{
predicate i()
}

type R = r: BaseType | r.i() witness *

ghost function Ctor(x: nat): R
requires exists b: BaseType :: b.i()
{
var b: BaseType :| b.i();
b
}

ghost function F(v: nat): R {
var o: R := Ctor(v); // error: precondition failure (***) as reported in issue 1958, this was once not reported
o
}

ghost function G0(v: nat): R { // anonymous result value
Ctor(v) // error: precondition failure
}

ghost function G1(v: nat): (r: R) { // named result value
Ctor(v) // error: precondition failure (***) as reported in issue 1958, this was once not reported
}

method MF(v: nat) returns (ghost r: R) {
ghost var o: R;
o := Ctor(v); // error: precondition failure
return o;
}

predicate IsR(r: R) { true }

method AssignSuchThat() {
var r: R :| IsR(r); // error: cannot establish existence of r
}

ghost function LetSuchThat(): R {
var r: R :| IsR(r); // error: cannot establish existence of r
r
}
}

module Example3 {
datatype BaseType = BaseType
{
predicate i() {
false
}
}

type R = r: BaseType | r.i() witness *

function Ctor(x: nat): R
requires exists b: BaseType :: b.i()
{
var b: BaseType :| b.i();
b
}

function F(v: nat): R {
var o: R := Ctor(v); // error: precondition failure (***) as reported in issue 1958, this was once not reported
o
}

method Main() {
var r := F(15);
print r, "\n";
}
}

// The following tests make sure that the appropriate type information (without allocatedness information) is available
// when checking the _postcondition_ of functions.
module FunctionResultType {
datatype Option<X> = None | Some(value: X)

function Search0<T(==)>(s: seq<T>, x: T): (o: Option<nat>)
ensures
o.Some? && o.value < |s| ==>
s[o.value] == x // in order to prove ".value" is non-negative, the type of "o" is necessary
{
if |s| < 12 then
None
else if s[9] == x then
Some(9)
else if s[2] == x then
Some(2)
else
None
}

function Search1<T(==)>(s: seq<T>, x: T): Option<nat>
ensures var o := Search1(s, x);
o.Some? && o.value < |s| ==>
s[o.value] == x // in order to prove ".value" is non-negative, the type of "Search1(s, x)" and "o" is necessary
{
if |s| < 12 then
None
else if s[9] == x then
Some(9)
else if s[2] == x then
Some(2)
else
None
}

function Search2<T(==)>(s: seq<T>, x: T): Option<nat>
ensures
Search2(s, x).Some? && Search2(s, x).value < |s| ==>
s[Search2(s, x).value] == x // in order to prove ".value" is non-negative, the type of "Search2(s, x)" is necessary
{
if |s| < 12 then
None
else if s[9] == x then
Some(9)
else if s[2] == x then
Some(2)
else
None
}

function Search4<T(==)>(s: seq<T>, x: T): (o: Option<int>)
ensures
o.Some? && o.value < |s| ==>
s[o.value] == x // error: ".value" may be negative
{
if |s| < 12 then
None
else if s[9] == x then
Some(9)
else if s[2] == x then
Some(2)
else
None
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
git-issue-1958.dfy(13,16): Error: value does not satisfy the subset constraints of 'R'
git-issue-1958.dfy(18,16): Error: value does not satisfy the subset constraints of 'R'
git-issue-1958.dfy(32,4): Error: value does not satisfy the subset constraints of 'R'
git-issue-1958.dfy(36,4): Error: value does not satisfy the subset constraints of 'R'
git-issue-1958.dfy(56,16): Error: function precondition could not be proved
git-issue-1958.dfy(49,13): Related location: this proposition could not be proved
git-issue-1958.dfy(61,4): Error: function precondition could not be proved
git-issue-1958.dfy(49,13): Related location: this proposition could not be proved
git-issue-1958.dfy(65,4): Error: function precondition could not be proved
git-issue-1958.dfy(49,13): Related location: this proposition could not be proved
git-issue-1958.dfy(70,9): Error: function precondition could not be proved
git-issue-1958.dfy(49,13): Related location: this proposition could not be proved
git-issue-1958.dfy(77,13): Error: cannot establish the existence of LHS values that satisfy the such-that predicate
git-issue-1958.dfy(81,4): Error: cannot establish the existence of LHS values that satisfy the such-that predicate
git-issue-1958.dfy(104,16): Error: function precondition could not be proved
git-issue-1958.dfy(97,13): Related location: this proposition could not be proved
git-issue-1958.dfy(167,7): Error: index out of range

Dafny program verifier finished with 6 verified, 12 errors
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ module Seq {
{
}

function {:opaque} IndexOfOption<T(==)>(s: seq<T>, v: T): (o: Option<nat>)
opaque function IndexOfOption<T(==)>(s: seq<T>, v: T): (o: Option<nat>)
ensures if o.Some? then o.value < |s| && s[o.value] == v &&
forall j {:trigger s[j]} :: 0 <= j < o.value ==> s[j] != v
forall j :: 0 <= j < o.value ==> s[j] != v
else v !in s
{
if |s| == 0 then None()
Expand Down
1 change: 1 addition & 0 deletions docs/dev/news/5877.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Don't assume type information before binding variables (for let expressions and named function results)

0 comments on commit e815f23

Please sign in to comment.