-
Notifications
You must be signed in to change notification settings - Fork 254
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
Basic implementation of proof dependency analysis #4461
Conversation
When enabled with -trackVerificationCoverage (an internal Boogie flag that we don't expect most people to use), this calculates proof dependencies and includes them in the output of the "text" format verification logger. Some caveats: * Some important statements aren't labeled yet. * Some descriptions are different in different contexts.
Previously it was sorted by name
This helps ensure deterministic testing.
} | ||
} | ||
|
||
public class InvariantDependency : ProofDependency { |
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.
If these classes are only used in a single location, then I suggest you place them there.
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 one, for invariant dependencies, is used in only one place (at the moment -- that could change). But most of the rest of the ProofDependency
implementations are used in multiple locations. It feels more comprehensible to me to have them all in the same place.
public void AddProofDependencyId(ICarriesAttributes boogieNode, IToken tok, ProofDependency dep) { | ||
var idString = GetProofDependencyId(dep); | ||
boogieNode.Attributes = | ||
new QKeyValue(tok, "id", new List<object>() { idString }, boogieNode.Attributes); |
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.
Is id
an attribute name that triggers something, or is it only used by the code in this PR ? Please reference it using a constant where the constant leads me to documentation about what it does.
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.
It's used by Boogie. I'll pull it out into a named constant with an explanation.
// Get the full ProofDependency indicated by a compound ID string. | ||
public ProofDependency GetFullIdDependency(string idString) { | ||
var parts = idString.Split('$'); | ||
if (idString.EndsWith("$requires") && parts.Length == 3) { |
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.
Where do the magic strings in this function come from? Please reference constants instead.
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 come from Boogie. I'll pull them out into named constants and explain them.
public void AddProofDependencyId(ICarriesAttributes boogieNode, IToken tok, ProofDependency dep) { | ||
var idString = GetProofDependencyId(dep); | ||
boogieNode.Attributes = | ||
new QKeyValue(tok, "id", new List<object>() { idString }, boogieNode.Attributes); |
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.
Why are we using a string idString
instead of directly storing dep
in the attribute, so you won't need the ProofDependencyManager
?
If the purpose is for the Boogie code to be printable and for a person to be able to map that print back to the original Dafny code, then I think you'll have to base the ids on Dafny source locations instead of generating them, and then you also wouldn't need a ProofDependencyManager
.
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 reason for this is that Boogie needs to have a string to send to Z3 -- that's the only way to specify the label on an expression you want to track for proof dependencies. Then Z3 returns a set of strings indicating which pieces it used. The notion of proof dependency that we have in Dafny is Dafny-centric, so it wouldn't make sense to have the ProofDependency
class in Boogie. It's possible that we could have an additional layer of abstraction in the Boogie implementation, so we'd have 1) names as strings, 2) structured Boogie conceptions of those strings, and 3) structured Dafny conceptions that build on the Boogie ones. But I'm not sure that that additional layer is really worth 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.
The reason for this is that Boogie needs to have a string to send to Z3
Why not do the id generation with AST node mapping in Boogie? Seems better to let Boogie encapsulate SMT generation. Then you don't need the method GetFullIdDependency
in Dafny and references to the coverage related string constants, and to move around the ProofDependencyManager
. VCResult.coveredElements
would then have type IEnumerable<Absy>
instead of IEnumerable<string>
.
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.
Although on the surface it seems like an Absy
would encapsulate everything that we need, in practice this isn't the case because of how Boogie transforms the original program and how particular nodes are used in different ways in a proof (such as how a requires clause is an assumption when proving a definition itself, but a proof goal when invoking that definition). So, we'd need something like ProofDependency
in Boogie, but it couldn't be quite the same thing, because those program constructs don't map cleanly back from Boogie to Dafny. So we'd need both a BoogeProofDependency and a DafnyProofDependency, and I don't think that extra layer of indirection is worthwhile at the moment.
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.
Although on the surface it seems like an Absy would encapsulate everything that we need, in practice this isn't the case because of how Boogie transforms the original program and how particular nodes are used in different ways in a proof (such as how a requires clause is an assumption when proving a definition itself, but a proof goal when invoking that definition)
I don't see that, since each id, also a complex one like {requireId}${callId}$requires
, ends up being attached to a unique Absy
instance. Regarding your example, a separate Absy
exists for each pair of call and requires clause, and a separate one for each requires clause.
This PR
- Dafny attaches ids to Absys, while keeping an id -> ProofDependency mapping
- Boogie creates more Absys and attaches ids to them that are combinations of ids provided by Dafny
- Boogie gets used ids from the unsat core and exposes them in
VCResult.coveredElements
- Dafny parses the ids, sometimes combined ones, to find ids generated by Dafny, maps those back to -ProofDependencys and the combined ones to combined ProofDependencys
Alternative:
- Let Dafny attach ITokens to Absys (already happens)
- Let Dafny mark Absys that it wants to track usages on
- Boogie creates more Absys, attaching ITokens to them that are combinations of tokens provided by Dafny.
- Boogie attaches ids to Absys that are marked for tracking, while keeping an id -> Absy mapping.
- Boogie gets used ids from the unsat core, maps them back to Absys and exposes them in
VCResult.coveredElements
- Dafny uses the tokens in Absys from
VCResult.coveredElements
to display UI.
The advantage of the latter is that more complexity moves to Boogie and Boogie encapsulates the interaction with the SMT solver.
The IToken
s used can be the current ProofDependency sub-classes, although they would then have to inherit from IToken
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.
Dafny can't keep track of an Absy -> ProofDependency map because Boogie introduces new Absy nodes during internal transformations that Dafny knows nothing about. Boogie would end up returning Absy nodes that Dafny has never seen, and therefore can't be in the map Dafny created.
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.
Dafny can't keep track of an Absy -> ProofDependency map
In what I described, it does not. Dafny does not keep track of anything. It retrieves an IEnumerable<Absy>
from VCResult.coveredElements
and those Absy
contain IToken
s with all the information that ProofDependency
currently provides.
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.
But IToken
values don't (and I believe shouldn't) track all of the information that ProofDependency
values do.
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.
In particular, at least one of the ProofDependency
types, and likely more in the future, keep the original Dafny AST node that gave rise the dependency. That's only lightly used at the moment, but will need to be used more heavily in the upcoming changes I have planned.
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.
It is also important to be able to print the Boogie files, as you suggest above, and encoding IDs in those that refer simply to Dafny source locations won't be enough, because Dafny source locations overlap too much. A single expression can result in multiple proof dependencies.
@@ -0,0 +1,424 @@ | |||
// RUN: %baredafny verify --log-format:text --boogie -trackVerificationCoverage "%s" > "%t" |
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'm still a little puzzled on the UX.
One use-case that's clear to me is giving warnings whenever code is not used by any assertion batch. For that I wouldn't use this report, but instead turn on a warning --warn-unused-by-verification
Can you give an example of how how the information presented in this report would be used?
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.
Yeah, this isn't really meant to be something that would be used by anyone other than the Dafny developers in its current state. It could be, but would require some expertise and manual effort. I'm building a more useful set of UI mechanisms on top of it, but I want to merge this now to get things done in pieces so there's not one monster PR to review. :) Think of it similarly to the type system refresh @RustanLeino is doing.
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.
Okay, that works.
This occurs due to a Boogie change that now dereferences a property that previously was supposed to never be null but wasn't blindly dereferenced.
Temporary until we fix a Boogie concurrency bug
And remove concurrency limitation from proof dependency test.
builder.Add(Assert(tok, ss.E, desc, wfOptions.AssertKv)); | ||
} else { | ||
builder.Add(AssertNS(tok, ss.E, desc)); | ||
// the check for .reads function must be translated explicitly: their declaration lacks |
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 change is harder to review because it's part of a very large method, so it's not easy to find the method header and gather context about what this code is supposed to do.
I see this is part of a block to check the well-formedness of a call expression. Could we put that block in a separate method?
Also, could you see if you can come up with a method name that describes either the whole block between line 714 to 762 or one of the sub blocks, and if so put that in a separate method? Maybe something like CheckReadsClauseSatisfied
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.
Hmm. I wonder whether this change and the others you comment on below are an artifact of the PR not being up-to-date with master
? Because they aren't changes made by this PR. There's some recent work @jtristan made to resolve a soundness issue that touches the same files, and that I had to resolve conflicts to merge with, and I think the changes you're pointing to are from that. Were you reviewing an intermediate commit, perhaps?
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.
Ah yes, I was reviewing a diff I shouldn't have. Please ignore the comments.
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.
Approved, but if you could either in this PR or in a follow-up address the remaining comments, that'd be much appreciated.
This enables experimental support for tracking proof dependencies, currently not advertised and enabled only by using the Boogie
-trackVerificationCoverage
option.When that option is enabled, the log produced by
/verificationLogger:text
or--log-format text
now includes the proof dependencies required to prove each assertion batch, along with the list of assertions that were proved in that batch. Each dependency includes a source range rather than just a single location, which can identify cases where only a specific sub-expression was required for the proof. The tests includes examples of this.Although the proper UI for proof dependencies isn't yet in place, a nice side effect of how this reports proof dependencies is that it's relatively easy to see vacuous proofs by looking for assertions that were proved but not included in the proof dependency list for their assertion batch. This will be automated in a future PR, but is already possible with a little manual work.
By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.