-
Notifications
You must be signed in to change notification settings - Fork 25
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: Change reads clause of filter to * #99
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.
Great catch, and I think this option is the best tradeoff among the options you listed.
One thing to be careful about in the future: this looks like it would be a breaking change for code that specifically depended on the tighter reads clause. In this case since it's fixing a soundness issue and it's hard to imagine code that couldn't be quickly fixed if affected, I think it's reasonable, but otherwise we're relying on the fact that this codebase is still marked as subject to change, which we can't get away with forever.
In other words, the next time we hit this kind of issue we may need to pick your last option of forking code to avoid unbounded work for consumers to upgrade. :)
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 agree with @robin-aws that this is the most pragmatic step to take at the moment. While we're at it, I notice that there are a few other instances of reads f.reads
in the repository. Should we fix those, too?
Thanks for the comments and quick reviews. @atomb I took a look at the others (in I also looked at reads set i, o | 0 <= i < |xs| && o in f.reads(xs[i]) :: o This is exact and has none of the drawbacks. (I should have thought of this obvious fix before.) I updated the PR to use this While I was at it, I also removed many manual |
Nice, even better!
Another phrase that triggers (ha!) my worries about eventual backwards compatibility. I think we'll need a mechanism to verify your assertion in the future. :) |
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 looks very nice now. I like the reduction in {:trigger ...}
annotations.
Fixes #3606 Fixes #3607 Under the now-fixed `/allocated:4`, also fixes #19 and #3605. This PR discovered a necessary change in the standard library: dafny-lang/libraries#99 (which has been merged) Also, this PR is what's referred to in #3600. This PR fixes several previous problems with quantifications and comprehensions over reference types. For example, previously, the verifier encoded `set o: object | true` as the set of _all_ objects, both allocated and unallocated, which is wrong in spirit and also allowed for unsound verification. The PR makes several general fixes and some fixes under just `/allocated:4` (which is not yet the default). We should make `/allocated:4` the default and remove the possibility for users to choose how allocation state is encoded (that is, we should remove the `allocated` flag altogether). However, it seems best to get this PR into Dafny version 3.Last, then make `/allocated:4` the default in Dafny 4.0 (and probably deprecate the `/allocated` flag at that time, too), and later remove the `/allocated` switch. The general changes in this PR include three breaking changes and one change that may affect verification. * One breaking change is that some functions used in `reads` clauses are no longer allowed. It was a bug that these were allowed before. This is checked by the resolver. * Another breaking change is that lambda expressions, including the `requires` and `reads` clauses of lambda expressions, are no longer allowed to depend on the allocation state. The previous omission of this check was a bug. This is checked by the resolver. * The third breaking change is that the verifier now include allocation antecedents for bound variables whose allocation isn't already implied by the body of the quantifier/comprehension. This is a virtue of (the buggy design of) `/allocated:3` and is fixed in `/allocated:4`. (Historically, this problem was not known at the time `/allocated:3` was designed. The intended design of `/allocated:4` does not have the problem, but the implementation of `/allocated:4` was previously incomplete and buggy.) So, switching from the default `/allocated:3` to `/allocated:4` (which will soon be the new default) changes (fixes!) the encoding of these allocation antecedents, which may be a breaking change for programs that (intentionally or inadvertently) made use of the previous, buggy encoding. * The possible change to verification is that this PR generalizes one axiom. Logically, adding an axiom does not make any verifying program not verify. However, as with any change in the verifier, in cases where verification was previously unstable, the presence of this new axiom may change verification behavior (e.g., change in verification performance or verification outcome). This axiom is added for both `/allocated:3` and `/allocated:4`. Unfortunately, there were no tests for `/allocated:4`, so no wonder it didn't work. Fortunately, this PR fixes `/allocated:4`. Unfortunately, the PR adds only a scant amount of specific `/allocated:4` tests. Fortunately, I did run the entire test suite locally with `/allocated:4` and it passes. So, the lack of many `/allocated:4` tests will be remedied as soon as we switch to `/allocated:4` as the default (in a separate PR). The long-standing issue #19 is fixed with this PR. Actually, the original program reported in #19 has been fixed for a while, but the long discussion of #19 showed many more issues. With `/allocated:4`, all of those have been fixed (and all of them are recorded in tests in `git-issue-19[ab].dfy`). ## More details The rewrite of functions in `reads` clauses was previously done in more than place. Now, this happens as a desugaring that's part of the resolver's bounds-discovery phase. Here are descriptions of the changes in the PR, listed more or less in the order that github shows the changes: * (code improvement) Add `ASTVisitor.VisitTopLevelFrameExpression`. * (code improvement) Remove `requiredVirtues` parameter to `ComprehensionExpr.GetBest`, since it was always passed in as `0`. * In `FrameExpression`, capture user-supplied frame expressions into `.OriginalExpression`, add `.DesugaredExpression` to hold any possible frame-expression rewrite (in particular, functions in `reads` clauses get desugared during resolution, and this desugaring is stored in `.DesugaredExpression`), and let `.E` return the "better" of these two fields. * (code improvement) Move `FrameArrowToObjectSet` method from `ArrowType` to `BoundsDiscoveryVisitor`. * (code improvement) Specialized the `AstVisitor` `Context` type parameter in the implementation of `BoundsDiscoveryVisitor`. * (code improvement) Specialized the `AstVisitor` `Context` type parameter in the implementation of `TypeInferenceChecker`. This makes it clearer what parts of the context are important to type-inference checking. * (fix) Fill in `.Bounds` for various quantifiers/comprehensions/forall-statements that are generated automatically. (These are needed, because `/allocated:4` expects `.Bounds` to be set.) * Previously, each datatype constructor gave rise to an allocation axiom, roughly `forall x, xs :: IsAlloc(x) && IsAlloc(xs) ==> IsAlloc(Cons(x, xs))`. Now, if no constructor depends on the allocation state, the entire type gets one allocation axiom, rather than having a separate one for each constructor (roughly `forall c: Color :: IsAlloc(c)`). This makes the axiom more useful where there isn't already a case split for each constructor. * (fix) The previous translation of `allocated(_)` made it sometimes not provable. (This has to do with boxing and has now been fixed.) * (fix) There was a code path for generating an allocation-consequence axiom for functions. However, it was broken in many places. Apparently, this was not detected, because the axiom is generated only for `/allocated:4` and there were no tests for `/allocated:4`. This PR fixes the code. * Make some error messages more specific. <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> --------- Co-authored-by: John Tristan <[email protected]>
This PR changes
Seq.Filter
's frame specification fromreads f.reads
toreads *
.The reason for the change is that there are problems with using a function (here, the function
f.reads
) in a frame specification, because such a function desugars to, essentially, a big union and, depending on the parameters of the function, that big union may depend on the allocation state. In the case ofSeq.Filter<T>(f: T ~> bool, xs: seq<T>)
, that desugaring wasSince
T
may depend on the allocation state, so does the set comprehension, and that is not allowed in a the declaration of functionFilter
.There are four ways to fix this problem.
f
fromf ~> bool
tof --> bool
(orf -> bool
). This restrictsf
to functions that don't read the heap, and hence thereads
clause ofFilter
can be removed altogether.T
to types that don't include heap references; that is, declareT
withT(!new)
. This will allow the previousreads
clause ofFilter
, but it also restricts the kinds of sequencesFilter
can be applied to.reads
clause ofFilter
toreads *
. This allowsFilter
to read anything in the allocated heap, but also weakens the power of the automatically generated frame axiom forFilter
.Filter
function, each accommodating one of the possible fixes above.This PR implements the third fix,
reads *
.To date, Dafny erroneously didn't complain about the
reads f.reads
. That is currently being fixed, so this PR is needed to keeplibraries
verifying.By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.