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

Fix: Recursive constant initialization was not checked if in constructor #2862

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0b34697
Fix: Recursive constant initialization was not checked if in constructor
MikaelMayer Oct 7, 2022
dd2ec2e
Update RELEASE_NOTES.md
MikaelMayer Oct 7, 2022
75344fb
Added review comment use case and fixed the code
MikaelMayer Oct 7, 2022
b03a40a
Merge branch 'fix-2727-soundness-constant' of https://github.com/dafn…
MikaelMayer Oct 7, 2022
4f7300f
Merge branch 'master' into fix-2727-soundness-constant
MikaelMayer Oct 7, 2022
f7e90a2
Better handling of alternatives
MikaelMayer Oct 11, 2022
04eeb43
Refactoring
MikaelMayer Oct 11, 2022
a4d3a27
Merge branch 'fix-2727-soundness-constant' of https://github.com/dafn…
MikaelMayer Oct 11, 2022
56dbf3b
Merge branch 'master' into fix-2727-soundness-constant
MikaelMayer Oct 11, 2022
5568664
Fix CI
MikaelMayer Oct 11, 2022
bac8625
Merge branch 'master' into fix-2727-soundness-constant
MikaelMayer Oct 12, 2022
bd717d1
Create 2862.fix
MikaelMayer Oct 12, 2022
e956e87
Merge branch 'fix-2727-soundness-constant' of https://github.com/dafn…
MikaelMayer Oct 12, 2022
0c20291
Merge branch 'master' into fix-2727-soundness-constant
MikaelMayer Oct 14, 2022
baa398f
Merge branch 'master' into fix-2727-soundness-constant
MikaelMayer Oct 14, 2022
2e98a55
Prevent constants to be assigned twice
MikaelMayer Oct 14, 2022
f18254b
Merge branch 'master' into fix-2727-soundness-constant
MikaelMayer Oct 21, 2022
d7fd892
Merge branch 'master' into fix-2727-soundness-constant
MikaelMayer Oct 24, 2022
b53829c
Merge branch 'master' into fix-2727-soundness-constant
MikaelMayer Oct 24, 2022
3ac8137
One more soundness fix
MikaelMayer Oct 26, 2022
5555cde
One more soundness fix
MikaelMayer Oct 26, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Upcoming

- fix: Constant initialization checked for recursion (https://github.com/dafny-lang/dafny/pull/2862)
- fix: Compiled lambdas now close only on non-ghost variables (https://github.com/dafny-lang/dafny/pull/2854)
- fix: Crash in the LSP in some code that does not parse (https://github.com/dafny-lang/dafny/pull/2833)

Expand Down
2 changes: 1 addition & 1 deletion Source/DafnyCore/DafnyCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<Target Name="RunCoco" BeforeTargets="PreBuildEvent" Outputs="$(ProjectDir)Parser.cs;$(ProjectDir)Scanner.cs" Inputs="$(ProjectDir)Dafny.atg">
<Exec Command="dotnet tool restore" />
<Exec Command="dotnet --info" />
<Exec Command="dotnet tool run coco $(ProjectDir)Dafny.atg -namespace Microsoft.Dafny -frames &quot;$(ProjectDir)Coco&quot;" />
<Exec Command="dotnet tool run coco &quot;$(ProjectDir)Dafny.atg&quot; -namespace Microsoft.Dafny -frames &quot;$(ProjectDir)Coco&quot;" />
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 has nothing to do with this PR, but I tried having dafny in a folder with space in its name and I had to fix this to make it work.

<!-- Recompute files to build according to https://stackoverflow.com/a/44829863/93197 -->
<ItemGroup>
<Compile Include="**/*$(DefaultLanguageSourceExtension)" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder);$(BaseIntermediateOutputPath)**;$(BaseOutputPath)**;@(Compile)" />
Expand Down
59 changes: 58 additions & 1 deletion Source/DafnyCore/Resolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2846,7 +2846,7 @@ public class ModuleBindings {
}

// ---------------------------------- Pass 1 ----------------------------------
// This pass:
// This pass or phase:
Copy link
Member Author

Choose a reason for hiding this comment

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

I keep forgetting that these are called "pass", I'm always looking for "phase". Next time, if I forget again, I will be able to find them.

// * checks that type inference was able to determine all types
// * check that shared destructors in datatypes are in agreement
// * fills in the .ResolvedOp field of binary expressions
Expand Down Expand Up @@ -13768,8 +13768,65 @@ private class ClonerKeepLocalVariablesIfTypeNotSet : Cloner {
var div = (DividedBlockStmt)blockStmt;
Contract.Assert(currentMethod is Constructor); // divided bodies occur only in class constructors
Contract.Assert(!resolutionContext.InFirstPhaseConstructor); // divided bodies are never nested
// In the first phase of the constructor, we keep track of constants which are fully determined.
// We prevent the reading of constants which have dependencies that are yet unassigned constants
var constantsAlreadyAssigned = new HashSet<ConstantField>();
var constantsWithErrors = new HashSet<ConstantField>();

// Returns an error if the expression cannot be fully determined
(string, ConstantField)? GetErrorIfNotFullyDetermined(Expression expr, List<ConstantField> visited) {
if (expr is MemberSelectExpr { Member: ConstantField field } memberSelectExpr && Expression.AsThis(memberSelectExpr.Obj) != null) {
if (visited.IndexOf(field) is var index && index >= 0) {
var msg = "Please break this constant initialization cycle: " + visited[index].Name;
for (var i = index + 1; i < visited.Count; i++) {
msg += " -> " + visited[i].Name;
}
msg += " -> " + field.Name;
return (msg, field);
}
if (field.Rhs == null && !constantsAlreadyAssigned.Contains(field)) {
var msg = "Missing initialization of field ";
for (var i = 0; i < visited.Count; i++) {
msg += (i == 0 ? "through the dependency " : "") + visited[i].Name + " -> ";
}
msg += field.Name + ", which needs to be assigned at this point.";
return (msg, field);
}
if (field.Rhs != null) {
foreach (var subExpr in field.Rhs.SubExpressions) {
if (GetErrorIfNotFullyDetermined(subExpr, visited.Append(field).ToList()) is var msgField && msgField != null) {
return msgField;
}
}
}
}
foreach (var subExpr in expr.SubExpressions) {
if (GetErrorIfNotFullyDetermined(subExpr, visited) is var msgField && msgField != null) {
return msgField;
}
}

return null;
}
foreach (Statement ss in div.BodyInit) {
ResolveStatementWithLabels(ss, resolutionContext with { InFirstPhaseConstructor = true });
var subExpressions = (ss is UpdateStmt updateStmt
? updateStmt.Rhss.SelectMany(rhs => rhs.SubExpressions).ToList()
: Microsoft.Dafny.Triggers.ExprExtensions.AllSubExpressions(ss, false, false));
foreach (var rhs in subExpressions) {
if (GetErrorIfNotFullyDetermined(rhs, new List<ConstantField>()) is var msgField && msgField != null &&
!constantsWithErrors.Contains(msgField.Value.Item2)) {
reporter.Error(MessageSource.Resolver, rhs, "Constant not initialized yet. " + msgField.Value.Item1);
constantsWithErrors.Add(msgField.Value.Item2);
}
}
if (ss is UpdateStmt updateStmt2) {
foreach (var lhs in updateStmt2.Lhss) {
if (lhs is MemberSelectExpr { Member: ConstantField { IsMutable: false } field } memberSelectExpr && Expression.AsThis(memberSelectExpr.Obj) != null) {
constantsAlreadyAssigned.Add(field);
}
}
}
}
foreach (Statement ss in div.BodyProper) {
ResolveStatementWithLabels(ss, resolutionContext);
Expand Down
34 changes: 34 additions & 0 deletions Test/git-issues/git-issue-2727.dfy
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// RUN: %dafny_0 -compile:0 "%s" > "%t"
// RUN: %diff "%s.expect" "%t"

class C {
const a := b + b
const b: int

constructor (x: int) {
var k := a;
print a, "\n";
b := x;
assert k == a;
print a, "\n";
if k != a {
var y := 5 / 0; // this can crash
}
}
}

class D {
const a: int := b
const b: int

constructor () {
b := a + 1;
new;
assert false;
}
}

method Main() {
var c := new C(5);
var d := new D();
}
3 changes: 3 additions & 0 deletions Test/git-issues/git-issue-2727.dfy.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
git-issue-2727.dfy(9,13): Error: Constant not initialized yet. Missing initialization of field through the dependency a -> b, which needs to be assigned at this point.
git-issue-2727.dfy(25,11): Error: Constant not initialized yet. Missing initialization of field through the dependency a -> b, which needs to be assigned at this point.
2 resolution/type errors detected in git-issue-2727.dfy