-
Notifications
You must be signed in to change notification settings - Fork 257
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
Update --enforce-determinism #3052
Update --enforce-determinism #3052
Conversation
…nit type as well. Remove --strict-definite-assignment option
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 change to enforce-determinism looks good, but why remove strict-definite-assignment? Does this mean that a customer cannot reproduce the full range of /definiteAssignment:<n>
anymore?
That's correct, 0 (no check) is already no longer available and 2 wouldn't be available after this PR. Do you think there's a use-case for 0 or 2? |
… where the missing initialization of
|
Thanks! I forgot that the uninitialised value is non-deterministic. Easy access counter-examples would make this problem relatively trivial to debug though. Do you have a similar example where |
Not really (it's easy in general to fix the errors that |
Following our chat, method M() {
var v := new int[15];
} |
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 implementation looks reasonable, but if we go this route won't we break large amounts of code? At the very least we'd have to rewrite the manual (e.g. http:https://dafny.org/dafny/DafnyRef/DafnyRef.html#sec-definite-assignment), and there's also the worry that this doesn't match Rustan's book. Maybe @RustanLeino can comment?
As a baseline, the language always requires:
Note that an assignment with a right-hand side Auto-initialization for compiled variables and the equivalent for ghost variables is nondeterministic. The language only promises to pick a value in the variable's type. Here are some additional things that some users may want:
Note that A0 and A1 still allow nondeterministic statements, such as A2 implies A0 and implies that an A3 implies A0, A1, and A2 (since compiled variables can be used in ghost statements) and implies that an A2 (and thus also A3) has the property that values seen during execution will be the same in every run of the program. Here are some combinations of these options, in terms of
I agree we should drop |
What surprises me about your suggestion @RustanLeino is that combining Why do you think A0 is more useful than A1 ? My impression is that A0 and A1 both protect against thinking a variable is assigned a particular default value while it is really assigned an arbitrary value, and then getting a surprising error message, as is the case in @cpitclaudel 's example (#3052 (comment)). Also, what's the use-case of A3? Why would I want my ghost code to be deterministic? Also, do you think it would make sense to change the default of A0? I'm suggesting to include A0 by default, and to allow turning it off using |
@keyboardDrummer You're right that my suggestion that both switches together give something more than the union of the switches individually is odd. I hadn't noticed that myself. I propose we first decide which modes we want to support and then figure out names of switches for them.
The argument for "just baseline" is that Dafny will still verify that all executions meet their specifications. So, the language doesn't need any further restrictions. There are two arguments for A0. One is that a program may contain compiled variables that were not included in specifications. This can happen if it doesn't seem important enough to write specifications for that variable or if writing such specifications would involve so much effort that the engineer would rather resort to testing. In this case, a user may be surprised at the observed value at run time. The other argument for A0 is that it may be a cheap and easy-to-understand way to catch some errors. Case in point: @cpitclaudel's example above, where some postcondition of your program does not hold, yet everything "looks" okay to you, because you're not thinking about the missing initialization. Regarding A1, the first argument for A0 does not apply, since the variable is ghost. However, the second argument for A0 still applies to A1. The argument for "not A2, not A3" is that it allows program to omit arbitrary decisions. For example, you can write if
case x <= y => return y;
case y <= x => return x; without having to decide which of the two alternatives should be executed when A2 is all about getting repeatable results. If the language had concurrency, this determinacy would matter less. A3 says ghost code gives deterministic results. This seems misguided to me. In fact, in proofs are often more beautifully written if you don't have to make certain choices. So, the only reason I can think of to keep A3 is that this is what we happened to do with |
From your comments and the above, I could imagine
with the idea that you cannot combine these flags. Since it's nice to have more flexibility in proofs and ghost things, I'm not so fond of either A3 or A1. But one could argue that it's more consistent to bundle A0+A1 together. If we do elect to change the language to make either A0 or A0+A1 the default, then we could also discuss repurposing the |
I feel that these two arguments are different scenario's in which the same confusion occurs. The confusion is that a programmer expects that a variable which has not been assigned but can be read, contains a particular default value. This expectation leads to a bug if there is no specification or test, and an unexpected verification error if there is a spec. I think A1 protects against the same confusion but then in ghost code (a third scenario), so I would argue A0 and A1 should be bundled.
Allowing Is your reason for I believe this PR currently has:
I suggest that we merge this PR as is, and cut an issue to remove A3 from |
Okay, sounds good. Let's cut this PR as is (with the small correction I remarked on in my PR review). Then, in separate PRs, let's
|
Co-authored-by: Rustan Leino <[email protected]>
Clement is on holiday and Rustan's review can cover for his
@RustanLeino I'm not sure what additional check A3 adds. The following is currently allowed with method Foo() {
ghost var z: int :| true;
} Is A3 already not a part of |
Fixes #3640 This PR fixes some issues with definite assignment and determinacy. Recently, we changed the default rules for definite assignment in Dafny (PR #3052). The main change was to insist that all local variables and out-parameters be definitely assigned before they are used. This was followed up by PR #3311, which started considering `:= *` assignments as fulfilling definite-assignment requirements. PR #3311 also fixed a point where #3052 had gone too far; with PR #3311, it is now again possible to allocate an array of an auto-init type without having to explicitly assign the array elements. There was one more point where PR #3052 had gone too far (reported in #3640), and the present PR fixes that point. The issue regards fields of a class. The present PR once again allows fields to be auto-initialized (provided their types allow that). This PR also makes `/definiteAssignment:4` available in the legacy CLI. (Previously, it had been added internally, but not exposed in the legacy CLI.) Finally, this PR reports an error in the `--enforce-determinism` mode for any class without a `constructor`. This was a latent omission in the previous checking. Together with once again allowing auto-initialization of fields, this change puts ``` dafny class Cell { // constructor-less class not allowed with --enforce-determinism var data: int } method NondeterministicReturn() returns (r: int) { var c := new Cell; return c.data; } ``` on the same footing as ``` dafny class Cell { var data: int constructor () { // with --enforce-determinism, this constructor must initialize .data } } method NondeterministicReturn() returns (r: int) { var c := new Cell(); return c.data; } ``` I argue this PR fixes the new definite-assignment mode in an important way and therefore should be included in Dafny 4.0. The documentation (see, e.g., #3584) may need to be updated. <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: Remy Willems <[email protected]>
Changes
--enforce-determinism
enforces assigning variables with an auto-init type as well.--strict-definite-assignment
option with the inverse--relax-definite-assignment
, which can only be used without--enforce-determinism
. Why?--enforce-determinism
and that requires strict definite assignment rules.--strict-definite-assignment
is useful, see the discussion for more information.Testing
--strict-definite-assignment
in tests--enforce-determinism
and--relax-definite-assignment
--relax-definite-assignment
By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.