-
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
Conversation
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.
The fix looks good. Please add some test cases.
@@ -213,7 +213,7 @@ public partial class Translator { | |||
AddWellformednessCheck(cf); | |||
if (InVerificationScope(cf)) { | |||
var etran = new ExpressionTranslator(this, predef, f.tok); | |||
heightAntecedent = Bpl.Expr.Lt(Bpl.Expr.Literal(cf.EnclosingModule.CallGraph.GetSCCRepresentativePredecessorCount(cf)), etran.FunctionContextHeight()); | |||
heightAntecedent = Bpl.Expr.Lt(Bpl.Expr.Literal(cf.EnclosingModule.CallGraph.GetSCCRepresentativePredecessorCount(cf) * 2 + 1), etran.FunctionContextHeight()); |
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.
I suggest making a SCCPredecessorCountToFunctionHeight
method that performs the ... * 2 + 1
computation. And I guess the + 1
should then also be controlled by a boolean, like you're doing in HeightContext
, etc.
//adding assume J.F(ins) == C.F(ins); | ||
//assert Post’; |
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.
These two should be in the other order. That is, it should first check that the postcondition of the trait holds, and only then should it be given the information J.F(ins) == C.F(ins)
.
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.
The comment is outdated. There is no assume anymore. (At least not of that equality.)
Contract.Requires(m != null); | ||
// free requires fh == FunctionContextHeight; | ||
var module = m.EnclosingModule; | ||
Boogie.Expr context = | ||
Boogie.Expr.Eq(Boogie.Expr.Literal(module.CallGraph.GetSCCRepresentativePredecessorCount(m)), FunctionContextHeight()); | ||
Boogie.Expr.Eq(Boogie.Expr.Literal(module.CallGraph.GetSCCRepresentativePredecessorCount(m) * 2 + (reducedScope ? 0 : 1)), FunctionContextHeight()); |
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.
Here's another place for that ... * 2 + 0-or-1
method.
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 lowerBound = (f.EnclosingClass.EnclosingModuleDefinition.CallGraph.GetSCCRepresentativePredecessorCount(f) + 1) * 2; |
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.
And here, too. In this instance, it would make it clear that the + 1
is indeed placed in the right place.
@@ -2254,15 +2253,15 @@ class Specialization { | |||
return (olderParameterCount, olderCondition); | |||
} | |||
|
|||
Bpl.Expr AxiomActivation(Function f, ExpressionTranslator etran) { | |||
Bpl.Expr AxiomActivation(Function f, ExpressionTranslator etran, bool hidden = false) { |
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.
I don't find hidden
to be such a good description. Can you make the name more descriptive? Or, if not, add a comment that describes it.
builder.Add(TrAssumeCmd(f.tok, Bpl.Expr.Eq(funcExpC, resultVar))); | ||
builder.Add(TrAssumeCmd(f.tok, Expr.Eq(funcExpC, resultVar))); |
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.
I know Rider keeps saying the Bpl.
is not necessary. I guess it's fine to get rid of it, but I've been fine with seeing the prefix, since it reminds me that Bpl.Expr
is not Expression
.
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 comment
The 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 other.F(x - 1, other)
should not be changed by the substitution.
var lowerBound = (f.EnclosingClass.EnclosingModuleDefinition.CallGraph.GetSCCRepresentativePredecessorCount(f) + 1) * 2; | ||
Expr useViaContext = !InVerificationScope(f) ? Bpl.Expr.True : Expr.Le(Expr.Literal(lowerBound), etran.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 comment
The reason will be displayed to describe this comment to others. Learn more.
Good. I like this ... * 2 + 1 < ...
better than the (... + 1) * 2 <= ...
.
Test/git-issues/git-issue-2500.dfy
Outdated
|
||
module A { | ||
trait {:termination false} Trait { | ||
function TotallyNotZero() : (ret: int) |
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.
Also add some tests that use the function itself (rather than the named result value ret
), since those cases may be tricky.
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.
Great simple solution, and appreciate the helpful comments and code cleanup along the way!
I assume we don't need the same solution for pre-conditions as well, since the equality axiom comes after the assertion in that case?
Test/dafny4/git-issue245.dfy
Outdated
@@ -62,19 +62,19 @@ class D extends T { | |||
class E extends T { | |||
// d does not name the result in either trait or class | |||
function method d(y: nat): nat | |||
ensures d(y) < 20 + y | |||
ensures d(y) < 20 + y // error: specification is not weaker |
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.
ensures d(y) < 20 + y // error: specification is not weaker | |
ensures d(y) < 20 + y // error: specification is weaker |
And likewise throughout - unless I'm confused this was already wrong in the original comments you copied.
Test/git-issues/git-issue-2500.dfy
Outdated
|
||
class Class extends M.Tr { | ||
function True(): (ret: bool) | ||
// Missing: ensures ret != 0 |
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.
// Missing: ensures ret != 0 | |
// Missing: ensures ret |
@@ -258,12 +258,11 @@ public ExpressionTranslator(ExpressionTranslator etran, string modifiesFrame) | |||
return new Boogie.IdentifierExpr(Token.NoToken, "$FunctionContextHeight", Boogie.Type.Int); | |||
} | |||
|
|||
public Boogie.Expr HeightContext(ICallable m) { | |||
public Boogie.Expr HeightContext(ICallable m, bool reducedScope = false) { |
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.
var receiver = Substitute(e.Receiver); | ||
var newArgs = SubstituteExprList(e.Args); | ||
var newFce = new FunctionCallExpr(e.tok, e.Name, receiver, e.OpenParen, e.CloseParen, newArgs, e.AtLabel); | ||
if (e.Function == A && e.Receiver is ThisExpr && receiver is ThisExpr) { | ||
newFce.Function = B; | ||
newFce.Type = e.Type; // TODO: this may not work with type parameters. |
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.
Still a TODO or have you fixed it?
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.
Neither? I couldn't find a situation where it affects the way I use it, Boogie has more shallow types, but there may be others, where getting this right is crucial.
function Other(x: nat, free: M.Tr): bool | ||
// Different receiver | ||
ensures x != 0 && Other(x-1, free) ==> Other(x-1, free) | ||
{ false } |
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.
I'd add a comment about how this is a negative test of the limitations of the current substitution approach.
Also this is fine but it would be good to have a version that's "obviously true" to humans as well, to be clearer about the limitation, like:
module M {
trait {:termination false} Tr {
...
function True'': (ret: bool) ensures True''()
}
}
class Class extends M.Tr {
...
function True'': (ret: bool) ensures (var alsoThis := this; alsoThis.True''())
{ true }
}
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.
This is a positive test, as the ensures is a tautology. If we weren't careful when lowering Other
the, the override check could pass. I did add a version of your suggestion nevertheless and it uncovered an issue.
This reverts commit 88ef30b.
Fixes 2500. To achieve this, we first give the override axiom the visibility of the overriding function. To ensure we only use the axiom in case we pass the check, we introduce an intermediate visibility level. The ensures clause of the overridden function can reference the underlying function, so we need to substitute the overridden function by the overriding function in the override check. Co-authored-by: Robin Salkeld <[email protected]>
Fixes 2500. To achieve this, we first give the override axiom the visibility of the overriding function. To ensure we only use the axiom in case we pass the check, we introduce an intermediate visibility level. The ensures clause of the overridden function can reference the underlying function, so we need to substitute the overridden function by the overriding function in the override check. Co-authored-by: Robin Salkeld <[email protected]>
…afny-lang#3419) This reverts PR dafny-lang#3223. It introduced an completeness issue.
Fixes #2500.
To achieve this, we first give the override axiom the visibility of the overriding function. To ensure we only use the axiom in case we pass the check, we introduce an intermediate visibility level. The ensures clause of the overridden function can reference the underlying function, so we need to substitute the overridden function by the overriding function in the override check.
By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.