-
Notifications
You must be signed in to change notification settings - Fork 262
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
fix: Cross-modular override check #3223
Changes from 7 commits
1f0d29b
b99bff8
fbedb7a
342be57
d37cf04
d1eb5a8
117585e
922049d
1dc1ed4
4c46f74
0a3d964
2ee54f7
0c3cb41
54e1cfd
2e67b54
923f38a
7d8fe8f
d9ae00a
ee6c02d
a86643c
01b2f4b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2053,7 +2053,7 @@ void AddFunctionConsequenceAxiom(Boogie.Function boogieFunction, Function f, Lis | |
// (mh == ModuleContextHeight && fh <= FunctionContextHeight) | ||
// | ||
// USE_VIA_CONTEXT | ||
// (mh != ModuleContextHeight || fh != FunctionContextHeight) && | ||
// fh < FunctionContextHeight && | ||
// GOOD_PARAMETERS | ||
// where GOOD_PARAMETERS means: | ||
// $IsGoodHeap($Heap) && this != null && formals-have-the-expected-types && | ||
|
@@ -2156,10 +2156,9 @@ void AddFunctionConsequenceAxiom(Boogie.Function boogieFunction, Function f, Lis | |
foreach (AttributedExpression req in f.Req) { | ||
pre = BplAnd(pre, etran.TrExpr(Substitute(req.E, null, substMap))); | ||
} | ||
// useViaContext: (mh != ModuleContextHeight || fh != FunctionContextHeight) | ||
var mod = f.EnclosingClass.EnclosingModuleDefinition; | ||
Bpl.Expr useViaContext = !InVerificationScope(f) ? Bpl.Expr.True : | ||
(Bpl.Expr)Bpl.Expr.Neq(Bpl.Expr.Literal(mod.CallGraph.GetSCCRepresentativePredecessorCount(f)), etran.FunctionContextHeight()); | ||
// useViaContext: fh < FunctionContextHeight | ||
var visibilityLevel = f.EnclosingClass.EnclosingModuleDefinition.CallGraph.GetSCCRepresentativePredecessorCount(f); | ||
Expr useViaContext = !InVerificationScope(f) ? Bpl.Expr.True : Expr.Lt(MkFunctionHeight(visibilityLevel), etran.FunctionContextHeight()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good. I like this |
||
// useViaCanCall: f#canCall(args) | ||
Bpl.IdentifierExpr canCallFuncID = new Bpl.IdentifierExpr(f.tok, f.FullSanitizedName + "#canCall", Bpl.Type.Bool); | ||
Bpl.Expr useViaCanCall = new Bpl.NAryExpr(f.tok, new Bpl.FunctionCall(canCallFuncID), Concat(tyargs, args)); | ||
|
@@ -2254,15 +2253,14 @@ void AddFunctionConsequenceAxiom(Boogie.Function boogieFunction, Function f, Lis | |
return (olderParameterCount, olderCondition); | ||
} | ||
|
||
Bpl.Expr AxiomActivation(Function f, ExpressionTranslator etran) { | ||
Bpl.Expr AxiomActivation(Function f, ExpressionTranslator etran, bool requiresFullScope = false) { | ||
Contract.Requires(f != null); | ||
Contract.Requires(etran != null); | ||
Contract.Requires(VisibleInScope(f)); | ||
var module = f.EnclosingClass.EnclosingModuleDefinition; | ||
|
||
if (InVerificationScope(f)) { | ||
return | ||
Bpl.Expr.Le(Bpl.Expr.Literal(module.CallGraph.GetSCCRepresentativePredecessorCount(f)), etran.FunctionContextHeight()); | ||
var visibilityLevel = f.EnclosingClass.EnclosingModuleDefinition.CallGraph.GetSCCRepresentativePredecessorCount(f); | ||
return Expr.Le(MkFunctionHeight(visibilityLevel, !requiresFullScope), etran.FunctionContextHeight()); | ||
} else { | ||
return Bpl.Expr.True; | ||
} | ||
|
@@ -2302,7 +2300,7 @@ private Axiom GetFunctionAxiom(Function f, Expression body, List<Formal> lits) { | |
// for visibility==ForeignModuleOnly, means: | ||
// GOOD_PARAMETERS | ||
// for visibility==IntraModuleOnly, means: | ||
// fh != FunctionContextHeight && | ||
// fh < FunctionContextHeight && | ||
// GOOD_PARAMETERS | ||
// where GOOD_PARAMETERS means: | ||
// $IsGoodHeap($Heap) && this != null && formals-have-the-expected-types && | ||
|
@@ -2507,12 +2505,11 @@ private Axiom GetFunctionAxiom(Function f, Expression body, List<Formal> lits) { | |
return null; | ||
} | ||
|
||
// useViaContext: (mh != ModuleContextHeight || fh != FunctionContextHeight) | ||
ModuleDefinition mod = f.EnclosingClass.EnclosingModuleDefinition; | ||
// useViaContext: fh < FunctionContextHeight | ||
var visibilityLevel = f.EnclosingClass.EnclosingModuleDefinition.CallGraph.GetSCCRepresentativePredecessorCount(f); | ||
Bpl.Expr useViaContext = !InVerificationScope(f) | ||
? (Bpl.Expr)Bpl.Expr.True | ||
: Bpl.Expr.Neq(Bpl.Expr.Literal(mod.CallGraph.GetSCCRepresentativePredecessorCount(f)), | ||
etran.FunctionContextHeight()); | ||
? Bpl.Expr.True | ||
: Bpl.Expr.Lt(MkFunctionHeight(visibilityLevel), etran.FunctionContextHeight()); | ||
// ante := (useViaContext && typeAnte && pre) | ||
ante = BplAnd(useViaContext, BplAnd(ante, pre)); | ||
|
||
|
@@ -3486,65 +3483,41 @@ private void AddFunctionOverrideEnsChk(Function f, BoogieStmtListBuilder builder | |
builder.Add(TrAssumeCmd(f.tok, etran.TrExpr(en.E))); | ||
} | ||
|
||
//generating assume J.F(ins) == C.F(ins) | ||
Bpl.FunctionCall funcIdC = new Bpl.FunctionCall(new Bpl.IdentifierExpr(f.tok, f.FullSanitizedName, TrType(f.ResultType))); | ||
Bpl.FunctionCall funcIdT = new Bpl.FunctionCall(new Bpl.IdentifierExpr(f.OverriddenFunction.tok, f.OverriddenFunction.FullSanitizedName, TrType(f.OverriddenFunction.ResultType))); | ||
List<Bpl.Expr> argsC = new List<Bpl.Expr>(); | ||
List<Bpl.Expr> argsT = new List<Bpl.Expr>(); | ||
// add type arguments | ||
argsT.AddRange(GetTypeArguments(f.OverriddenFunction, f).ConvertAll(TypeToTy)); | ||
argsC.AddRange(GetTypeArguments(f, null).ConvertAll(TypeToTy)); | ||
// add fuel arguments | ||
if (f.IsFuelAware()) { | ||
argsC.Add(etran.layerInterCluster.GetFunctionFuel(f)); | ||
} | ||
if (f.OverriddenFunction.IsFuelAware()) { | ||
argsT.Add(etran.layerInterCluster.GetFunctionFuel(f)); | ||
} | ||
// add heap arguments | ||
if (f is TwoStateFunction) { | ||
argsC.Add(etran.Old.HeapExpr); | ||
argsT.Add(etran.Old.HeapExpr); | ||
} | ||
if (AlwaysUseHeap || f.ReadsHeap) { | ||
argsC.Add(etran.HeapExpr); | ||
} | ||
if (AlwaysUseHeap || f.OverriddenFunction.ReadsHeap) { | ||
argsT.Add(etran.HeapExpr); | ||
} | ||
// add "ordinary" parameters (including "this", if any) | ||
var prefixCount = implInParams.Count - f.Formals.Count; | ||
for (var i = 0; i < implInParams.Count; i++) { | ||
Bpl.Expr cParam = new Bpl.IdentifierExpr(f.tok, implInParams[i]); | ||
Bpl.Expr tParam = new Bpl.IdentifierExpr(f.OverriddenFunction.tok, implInParams[i]); | ||
if (prefixCount <= i && ModeledAsBoxType(f.OverriddenFunction.Formals[i - prefixCount].Type)) { | ||
tParam = BoxIfNecessary(f.tok, tParam, f.Formals[i - prefixCount].Type); | ||
} | ||
argsC.Add(cParam); | ||
argsT.Add(tParam); | ||
} | ||
Bpl.Expr funcExpC = new Bpl.NAryExpr(f.tok, funcIdC, argsC); | ||
Bpl.Expr funcExpT = new Bpl.NAryExpr(f.OverriddenFunction.tok, funcIdT, argsT); | ||
var funcExpCPossiblyBoxed = funcExpC; | ||
if (ModeledAsBoxType(f.OverriddenFunction.ResultType)) { | ||
funcExpCPossiblyBoxed = BoxIfUnboxed(funcExpCPossiblyBoxed, f.ResultType); | ||
} | ||
builder.Add(TrAssumeCmd(f.tok, Bpl.Expr.Eq(funcExpCPossiblyBoxed, funcExpT))); | ||
|
||
//generating assume C.F(ins) == out, if a result variable was given | ||
if (resultVariable != null) { | ||
var funcIdC = new FunctionCall(new Bpl.IdentifierExpr(f.tok, f.FullSanitizedName, TrType(f.ResultType))); | ||
var argsC = new List<Bpl.Expr>(); | ||
|
||
// add type arguments | ||
argsC.AddRange(GetTypeArguments(f, null).ConvertAll(TypeToTy)); | ||
|
||
// add fuel arguments | ||
if (f.IsFuelAware()) { | ||
argsC.Add(etran.layerInterCluster.GetFunctionFuel(f)); | ||
} | ||
|
||
// add heap arguments | ||
if (f is TwoStateFunction) { | ||
argsC.Add(etran.Old.HeapExpr); | ||
} | ||
if (AlwaysUseHeap || f.ReadsHeap) { | ||
argsC.Add(etran.HeapExpr); | ||
} | ||
|
||
argsC.AddRange(implInParams.Select(var => new Bpl.IdentifierExpr(f.tok, var))); | ||
|
||
var funcExpC = new Bpl.NAryExpr(f.tok, funcIdC, argsC); | ||
var resultVar = new Bpl.IdentifierExpr(resultVariable.tok, resultVariable); | ||
builder.Add(TrAssumeCmd(f.tok, Bpl.Expr.Eq(funcExpC, resultVar))); | ||
} | ||
|
||
//generating trait post-conditions with class variables | ||
foreach (var en in f.OverriddenFunction.Ens) { | ||
Expression postcond = Substitute(en.E, null, substMap, typeMap); | ||
bool splitHappened; // we don't actually care | ||
foreach (var s in TrSplitExpr(postcond, etran, false, out splitHappened)) { | ||
if (s.IsChecked) { | ||
builder.Add(Assert(f.tok, s.E, new PODesc.FunctionContractOverride(true))); | ||
} | ||
var receiver = new ImplicitThisExpr(f.tok) { Type = Resolver.GetThisType(f.tok, (TopLevelDeclWithMembers)f.EnclosingClass) }; | ||
var sub = new FunctionCallSubstituter(receiver, substMap, typeMap, f.OverriddenFunction, f); | ||
Expression postcond = sub.Substitute(en.E); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this replaces too many occurrences of the function. For example, I suspect it will make the following override verify, but it mustn't: trait Tr {
function F(x: nat, other: Tr): int
ensures x != 0 && other.F(x - 1, other) < 20 ==> F(x, other) % 2 == other.F(x - 1, other) % 2
}
class Cl extends Tr {
function F(x: nat, other: Tr): int
ensures 50 <= F(x, other)
} Note that the calls |
||
foreach (var s in TrSplitExpr(postcond, etran, false, out _).Where(s => s.IsChecked)) { | ||
builder.Add(Assert(f.tok, s.E, new PODesc.FunctionContractOverride(true))); | ||
} | ||
} | ||
} | ||
|
@@ -4768,7 +4741,7 @@ Bpl.Expr CanCallAssumption(Expression expr, ExpressionTranslator etran) { | |
Contract.Requires(predef != null); | ||
Contract.Ensures(Contract.Result<Bpl.Expr>() != null); | ||
|
||
if (expr is LiteralExpr || expr is ThisExpr || expr is IdentifierExpr || expr is WildcardExpr || expr is BoogieWrapper) { | ||
if (expr is LiteralExpr or ThisExpr or IdentifierExpr or WildcardExpr or BoogieWrapper) { | ||
return Bpl.Expr.True; | ||
} else if (expr is DisplayExpression) { | ||
DisplayExpression e = (DisplayExpression)expr; | ||
|
@@ -10747,6 +10720,14 @@ List<Variable> MkTyParamFormals(List<TypeParameter> args, bool includeWhereClaus | |
return null; | ||
} | ||
|
||
// We use the $FunctionContextHeight to restrict the applicability of certain axioms. The entity at the end of a | ||
// dependency chain has the highest number. To get more granular control over the visibility, we extend every | ||
// visibility level by a precursory intermediate level. This additional level is helpful for proofs that would be | ||
// disturbed by axioms that are visible the the final level. | ||
static Bpl.Expr MkFunctionHeight(int visibilityLevel, bool intermediateScope = false) { | ||
return Expr.Literal(visibilityLevel * 2 + (intermediateScope ? 0 : 1)); | ||
} | ||
|
||
public static void MapM<A>(IEnumerable<A> xs, Action<A> K) { | ||
Contract.Requires(xs != null); | ||
Contract.Requires(K != null); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// RUN: %dafny_0 /compile:0 "%s" > "%t" | ||
// RUN: %diff "%s.expect" "%t" | ||
|
||
module A { | ||
trait {:termination false} Trait { | ||
function TotallyNotZero() : (ret: int) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also add some tests that use the function itself (rather than the named result value |
||
ensures ret != 0 | ||
} | ||
} | ||
|
||
module B { | ||
import opened A | ||
|
||
class Class extends Trait { | ||
constructor() {} | ||
function TotallyNotZero() : (ret: int) | ||
// Missing: ensures ret != 0 | ||
{ | ||
0 | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
git-issue-2500.dfy(16,13): Error: the function must provide an equal or more detailed postcondition than in its parent trait | ||
|
||
Dafny program verifier finished with 1 verified, 1 error |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
To ensure that a `class` correctly implements a `trait`, we perform an override check. This check was previously faulty across `module`s, but works unconditionally now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: consider using "intermediateScope" here instead, or renaming the parameter, for consistency.