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

Basic implementation of proof dependency analysis #4461

Merged
merged 34 commits into from
Sep 7, 2023

Conversation

atomb
Copy link
Member

@atomb atomb commented Aug 23, 2023

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.

@atomb atomb self-assigned this Aug 28, 2023
@atomb atomb marked this pull request as ready for review August 29, 2023 16:42
Source/DafnyCore/Verifier/Translator.cs Outdated Show resolved Hide resolved
Source/DafnyCore/Verifier/Translator.BoogieFactory.cs Outdated Show resolved Hide resolved
Source/DafnyCore/Verifier/ProofDependency.cs Show resolved Hide resolved
Source/DafnyCore/Verifier/ProofDependency.cs Outdated Show resolved Hide resolved
}
}

public class InvariantDependency : ProofDependency {
Copy link
Member

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.

Copy link
Member Author

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);
Copy link
Member

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.

Copy link
Member Author

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) {
Copy link
Member

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.

Copy link
Member Author

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);
Copy link
Member

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.

Copy link
Member Author

@atomb atomb Aug 30, 2023

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.

Copy link
Member

@keyboardDrummer keyboardDrummer Aug 31, 2023

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>.

Copy link
Member Author

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.

Copy link
Member

@keyboardDrummer keyboardDrummer Aug 31, 2023

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 ITokens used can be the current ProofDependency sub-classes, although they would then have to inherit from IToken

Copy link
Member Author

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.

Copy link
Member

@keyboardDrummer keyboardDrummer Sep 1, 2023

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 ITokens with all the information that ProofDependency currently provides.

Copy link
Member Author

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.

Copy link
Member Author

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.

Copy link
Member Author

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.

Source/DafnyCore/Verifier/Translator.cs Outdated Show resolved Hide resolved
@@ -0,0 +1,424 @@
// RUN: %baredafny verify --log-format:text --boogie -trackVerificationCoverage "%s" > "%t"
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, that works.

@atomb atomb enabled auto-merge (squash) August 31, 2023 03:06
@atomb atomb disabled auto-merge September 5, 2023 23:56
@atomb atomb enabled auto-merge (squash) September 6, 2023 17:25
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
Copy link
Member

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

Copy link
Member Author

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?

Copy link
Member

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.

Copy link
Member

@keyboardDrummer keyboardDrummer left a 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.

@atomb atomb merged commit 2537367 into dafny-lang:master Sep 7, 2023
18 checks passed
@atomb atomb deleted the verification-coverage-ids branch January 4, 2024 16:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants