diff --git a/.github/workflows/compfuzzci.yaml b/.github/workflows/compfuzzci.yaml new file mode 100644 index 00000000000..5541e4ed44f --- /dev/null +++ b/.github/workflows/compfuzzci.yaml @@ -0,0 +1,90 @@ +name: CompFuzzCI +on: + pull_request: + branches: + - master + types: [opened, closed, synchronize, reopened] + issues: + branches: + - master + types: [opened, closed, reopened] + +jobs: + FuzzOnPR: + if: github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'synchronize') && github.event.pull_request.base.ref == 'master' + runs-on: ubuntu-latest + steps: + - name: Trigger CompFuzzCI + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.COMPFUZZCI_PAT }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: 'CompFuzzCI', + repo: 'DafnyCompilerFuzzer', + workflow_id: 'fuzz.yaml', + ref: 'main', + inputs: { + commit: '${{ github.event.pull_request.head.sha }}', + main_commit: '${{github.event.pull_request.base.sha}}', + branch: '${{github.event.pull_request.head.ref}}', + duration: '3600', + instance: '5' + } + }) + UpdatePRClosed: + if: github.event_name == 'pull_request' && github.event.action == 'closed' + runs-on: ubuntu-latest + steps: + - name: Trigger CompFuzzCI + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.COMPFUZZCI_PAT }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: 'CompFuzzCI', + repo: 'DafnyCompilerFuzzer', + workflow_id: 'update_pr_close.yaml', + ref: 'main', + inputs: { + pr_number: '${{github.event.number}}' + } + }) + UpdateIssueOpened: + if: github.event_name == 'issues' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request.base.ref == 'master' + runs-on: ubuntu-latest + steps: + - name: Trigger CompFuzzCI + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.COMPFUZZCI_PAT }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: 'CompFuzzCI', + repo: 'DafnyCompilerFuzzer', + workflow_id: 'update_issue_open.yaml', + ref: 'main', + inputs: { + issue_number: '${{github.event.issue.number}}', + issuer: '${{github.event.issue.user.login}}', + commit: ${{ github.sha }} + } + }) + UpdateIssueClosed: + if: github.event_name == 'issues' && github.event.action == 'closed' + runs-on: ubuntu-latest + steps: + - name: Trigger CompFuzzCI + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.COMPFUZZCI_PAT }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: 'CompFuzzCI', + repo: 'DafnyCompilerFuzzer', + workflow_id: 'update_issue_close.yaml', + ref: 'main', + inputs: { + issue_number: '${{github.event.issue.number}}' + } + }) \ No newline at end of file diff --git a/Source/DafnyCore/AST/Grammar/Printer/Printer.Statement.cs b/Source/DafnyCore/AST/Grammar/Printer/Printer.Statement.cs index 26227fae09a..ef97d0be06f 100644 --- a/Source/DafnyCore/AST/Grammar/Printer/Printer.Statement.cs +++ b/Source/DafnyCore/AST/Grammar/Printer/Printer.Statement.cs @@ -363,7 +363,7 @@ public void PrintStatement(Statement stmt, int indent) { wr.Write(" "); } PrintUpdateRHS(s, indent); - wr.Write(";"); + PrintBy(s); } else if (stmt is CallStmt) { // Most calls are printed from their concrete syntax given in the input. However, recursive calls to @@ -395,8 +395,7 @@ public void PrintStatement(Statement stmt, int indent) { wr.Write(" "); PrintUpdateRHS(s.Update, indent); } - wr.Write(";"); - + PrintBy(s.Update); } else if (stmt is VarDeclPattern) { var s = (VarDeclPattern)stmt; if (s.tok is AutoGeneratedToken) { @@ -455,6 +454,20 @@ public void PrintStatement(Statement stmt, int indent) { } else { Contract.Assert(false); throw new cce.UnreachableException(); // unexpected statement } + + void PrintBy(ConcreteUpdateStatement statement) { + BlockStmt proof = statement switch { + UpdateStmt updateStmt => updateStmt.Proof, + AssignOrReturnStmt returnStmt => returnStmt.Proof, + _ => null + }; + if (proof != null) { + wr.Write(" by "); + PrintStatement(proof, indent); + } else { + wr.Write(";"); + } + } } private void PrintHideReveal(HideRevealStmt revealStmt) { diff --git a/Source/DafnyCore/AST/Modules/ModuleDefinition.cs b/Source/DafnyCore/AST/Modules/ModuleDefinition.cs index f165d89b5f3..c7b1b971f80 100644 --- a/Source/DafnyCore/AST/Modules/ModuleDefinition.cs +++ b/Source/DafnyCore/AST/Modules/ModuleDefinition.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.CommandLine; using System.Diagnostics.Contracts; using System.Linq; +using DafnyCore; +using DafnyCore.Options; using Microsoft.Dafny.Auditor; using Microsoft.Dafny.Compilers; using OmniSharp.Extensions.LanguageServer.Protocol.Models; @@ -25,6 +28,18 @@ public enum ImplementationKind { public record Implements(ImplementationKind Kind, ModuleQualifiedId Target); public class ModuleDefinition : RangeNode, IAttributeBearingDeclaration, ICloneable, IHasSymbolChildren { + + public static readonly Option LegacyModuleNames = new("--legacy-module-names", + @" +Generate module names in the older A_mB_mC style instead of the current A.B.C scheme".TrimStart()) { + IsHidden = true + }; + + static ModuleDefinition() { + DafnyOptions.RegisterLegacyUi(LegacyModuleNames, DafnyOptions.ParseBoolean, "Compilation options", legacyName: "legacyModuleNames", defaultValue: false); + OptionRegistry.RegisterOption(LegacyModuleNames, OptionScope.Translation); + } + public IToken BodyStartTok = Token.NoToken; public IToken TokenWithTrailingDocString = Token.NoToken; public string DafnyName => NameNode.StartToken.val; // The (not-qualified) name as seen in Dafny source code @@ -215,11 +230,15 @@ public string GetCompileName(DafnyOptions options) { if (IsBuiltinName) { compileName = Name; } else if (EnclosingModule is { TryToAvoidName: false }) { - // Include all names in the module tree path, to disambiguate when compiling - // a flat list of modules. - // Use an "underscore-escaped" character as a module name separator, since - // underscores are already used as escape characters in SanitizeName() - compileName = EnclosingModule.GetCompileName(options) + options.Backend.ModuleSeparator + NonglobalVariable.SanitizeName(Name); + if (options.Get(LegacyModuleNames)) { + compileName = SanitizedName; + } else { + // Include all names in the module tree path, to disambiguate when compiling + // a flat list of modules. + // Use an "underscore-escaped" character as a module name separator, since + // underscores are already used as escape characters in SanitizeName() + compileName = EnclosingModule.GetCompileName(options) + options.Backend.ModuleSeparator + NonglobalVariable.SanitizeName(Name); + } } else { compileName = NonglobalVariable.SanitizeName(Name); } diff --git a/Source/DafnyCore/AST/Statements/Assignment/AssignOrReturnStmt.cs b/Source/DafnyCore/AST/Statements/Assignment/AssignOrReturnStmt.cs index 0b6199ae2bb..a598f530af8 100644 --- a/Source/DafnyCore/AST/Statements/Assignment/AssignOrReturnStmt.cs +++ b/Source/DafnyCore/AST/Statements/Assignment/AssignOrReturnStmt.cs @@ -9,6 +9,7 @@ public class AssignOrReturnStmt : ConcreteUpdateStatement, ICloneable Rhss; public readonly AttributedToken KeywordToken; + public readonly BlockStmt Proof; [FilledInDuringResolution] public readonly List ResolvedStatements = new List(); public override IEnumerable SubStatements => ResolvedStatements; public override IToken Tok { @@ -22,7 +23,7 @@ public override IToken Tok { } } - public override IEnumerable Children => ResolvedStatements; + public override IEnumerable Children => ResolvedStatements.Concat(Proof?.Children ?? new List()); public override IEnumerable PreResolveSubStatements => Enumerable.Empty(); [ContractInvariantMethod] @@ -43,13 +44,14 @@ public AssignOrReturnStmt(Cloner cloner, AssignOrReturnStmt original) : base(clo Rhs = (ExprRhs)cloner.CloneRHS(original.Rhs); Rhss = original.Rhss.ConvertAll(cloner.CloneRHS); KeywordToken = cloner.AttributedTok(original.KeywordToken); + Proof = cloner.CloneBlockStmt(original.Proof); if (cloner.CloneResolvedFields) { ResolvedStatements = original.ResolvedStatements.Select(stmt => cloner.CloneStmt(stmt, false)).ToList(); } } - public AssignOrReturnStmt(RangeToken rangeToken, List lhss, ExprRhs rhs, AttributedToken keywordToken, List rhss) + public AssignOrReturnStmt(RangeToken rangeToken, List lhss, ExprRhs rhs, AttributedToken keywordToken, List rhss, BlockStmt proof = null) : base(rangeToken, lhss) { Contract.Requires(rangeToken != null); Contract.Requires(lhss != null); @@ -59,6 +61,7 @@ public AssignOrReturnStmt(RangeToken rangeToken, List lhss, ExprRhs Rhs = rhs; Rhss = rhss; KeywordToken = keywordToken; + Proof = proof; } public override IEnumerable PreResolveSubExpressions { @@ -190,6 +193,8 @@ public override void Resolve(ModuleResolver resolver, ResolutionContext resoluti return; } + ModuleResolver.ResolveByProof(resolver, Proof, resolutionContext); + Expression lhsExtract = null; if (expectExtract) { if (resolutionContext.CodeContext is Method caller && caller.Outs.Count == 0 && KeywordToken == null) { @@ -285,7 +290,7 @@ private void DesugarElephantStatement(bool expectExtract, Expression lhsExtract, } } // " temp, ... := MethodOrExpression, ...;" - UpdateStmt up = new UpdateStmt(RangeToken, lhss2, rhss2); + UpdateStmt up = new UpdateStmt(RangeToken, lhss2, rhss2, Proof); if (expectExtract) { up.OriginalInitialLhs = Lhss.Count == 0 ? null : Lhss[0]; } diff --git a/Source/DafnyCore/AST/Statements/Assignment/UpdateStmt.cs b/Source/DafnyCore/AST/Statements/Assignment/UpdateStmt.cs index cc3fe0507f1..0b61df6ab30 100644 --- a/Source/DafnyCore/AST/Statements/Assignment/UpdateStmt.cs +++ b/Source/DafnyCore/AST/Statements/Assignment/UpdateStmt.cs @@ -8,6 +8,7 @@ public class UpdateStmt : ConcreteUpdateStatement, ICloneable, ICanR public readonly List Rhss; public readonly bool CanMutateKnownState; public Expression OriginalInitialLhs = null; + public readonly BlockStmt Proof; public override IToken Tok { get { @@ -22,7 +23,7 @@ public override IToken Tok { } [FilledInDuringResolution] public List ResolvedStatements; - public override IEnumerable SubStatements => Children.OfType(); + public override IEnumerable SubStatements => Children.OfType().Concat(Proof != null ? Proof.SubStatements : new List()); public override IEnumerable NonSpecificationSubExpressions => ResolvedStatements == null ? Rhss.SelectMany(r => r.NonSpecificationSubExpressions) : Enumerable.Empty(); @@ -45,26 +46,29 @@ public UpdateStmt Clone(Cloner cloner) { public UpdateStmt(Cloner cloner, UpdateStmt original) : base(cloner, original) { Rhss = original.Rhss.Select(cloner.CloneRHS).ToList(); CanMutateKnownState = original.CanMutateKnownState; + Proof = cloner.CloneBlockStmt(original.Proof); if (cloner.CloneResolvedFields) { ResolvedStatements = original.ResolvedStatements.Select(stmt => cloner.CloneStmt(stmt, false)).ToList(); } } - public UpdateStmt(RangeToken rangeToken, List lhss, List rhss) + public UpdateStmt(RangeToken rangeToken, List lhss, List rhss, BlockStmt proof = null) : base(rangeToken, lhss) { Contract.Requires(cce.NonNullElements(lhss)); Contract.Requires(cce.NonNullElements(rhss)); Contract.Requires(lhss.Count != 0 || rhss.Count == 1); Rhss = rhss; CanMutateKnownState = false; + Proof = proof; } - public UpdateStmt(RangeToken rangeToken, List lhss, List rhss, bool mutate) + public UpdateStmt(RangeToken rangeToken, List lhss, List rhss, bool mutate, BlockStmt proof = null) : base(rangeToken, lhss) { Contract.Requires(cce.NonNullElements(lhss)); Contract.Requires(cce.NonNullElements(rhss)); Contract.Requires(lhss.Count != 0 || rhss.Count == 1); Rhss = rhss; CanMutateKnownState = mutate; + Proof = proof; } public override IEnumerable PreResolveSubExpressions { @@ -123,6 +127,9 @@ public override void Resolve(ModuleResolver resolver, ResolutionContext resoluti resolver.ResolveAttributes(rhs, resolutionContext); } + // resolve proof + ModuleResolver.ResolveByProof(resolver, Proof, resolutionContext); + // figure out what kind of UpdateStmt this is if (firstEffectfulRhs == null) { if (Lhss.Count == 0) { @@ -178,7 +185,7 @@ public override void Resolve(ModuleResolver resolver, ResolutionContext resoluti foreach (var ll in Lhss) { resolvedLhss.Add(ll.Resolved); } - CallStmt a = new CallStmt(RangeToken, resolvedLhss, methodCallInfo.Callee, methodCallInfo.ActualParameters, methodCallInfo.Tok); + CallStmt a = new CallStmt(RangeToken, resolvedLhss, methodCallInfo.Callee, methodCallInfo.ActualParameters, methodCallInfo.Tok, Proof); a.OriginalInitialLhs = OriginalInitialLhs; ResolvedStatements.Add(a); } diff --git a/Source/DafnyCore/AST/Statements/Methods/CallStmt.cs b/Source/DafnyCore/AST/Statements/Methods/CallStmt.cs index fc52debc2a6..41348dae006 100644 --- a/Source/DafnyCore/AST/Statements/Methods/CallStmt.cs +++ b/Source/DafnyCore/AST/Statements/Methods/CallStmt.cs @@ -25,11 +25,12 @@ void ObjectInvariant() { public readonly ActualBindings Bindings; public List Args => Bindings.Arguments; public Expression OriginalInitialLhs = null; + public readonly BlockStmt Proof; public Expression Receiver { get { return MethodSelect.Obj; } } public Method Method { get { return (Method)MethodSelect.Member; } } - public CallStmt(RangeToken rangeToken, List lhs, MemberSelectExpr memSel, List args, IToken overrideToken = null) + public CallStmt(RangeToken rangeToken, List lhs, MemberSelectExpr memSel, List args, IToken overrideToken = null, BlockStmt proof = null) : base(rangeToken) { Contract.Requires(rangeToken != null); Contract.Requires(cce.NonNullElements(lhs)); @@ -41,6 +42,7 @@ public CallStmt(RangeToken rangeToken, List lhs, MemberSelectExpr me this.MethodSelect = memSel; this.overrideToken = overrideToken; this.Bindings = new ActualBindings(args); + Proof = proof; } public CallStmt Clone(Cloner cloner) { @@ -52,14 +54,15 @@ public CallStmt(Cloner cloner, CallStmt original) : base(cloner, original) { Lhs = original.Lhs.Select(cloner.CloneExpr).ToList(); Bindings = new ActualBindings(cloner, original.Bindings); overrideToken = original.overrideToken; + Proof = cloner.CloneBlockStmt(original.Proof); } /// /// This constructor is intended to be used when constructing a resolved CallStmt. The "args" are expected /// to be already resolved, and are all given positionally. /// - public CallStmt(RangeToken rangeToken, List lhs, MemberSelectExpr memSel, List args) - : this(rangeToken, lhs, memSel, args.ConvertAll(e => new ActualBinding(null, e))) { + public CallStmt(RangeToken rangeToken, List lhs, MemberSelectExpr memSel, List args, BlockStmt proof = null) + : this(rangeToken, lhs, memSel, args.ConvertAll(e => new ActualBinding(null, e)), proof: proof) { Bindings.AcceptArgumentExpressionsAsExactParameterList(); } diff --git a/Source/DafnyCore/AST/Statements/Verification/AssertStmt.cs b/Source/DafnyCore/AST/Statements/Verification/AssertStmt.cs index 295803e9e6e..306f1d4815b 100644 --- a/Source/DafnyCore/AST/Statements/Verification/AssertStmt.cs +++ b/Source/DafnyCore/AST/Statements/Verification/AssertStmt.cs @@ -89,16 +89,7 @@ public override void GenResolve(INewOrOldResolver resolver, ResolutionContext co base.GenResolve(resolver, context); - if (Proof != null) { - // clear the labels for the duration of checking the proof body, because break statements are not allowed to leave a the proof body - var prevLblStmts = resolver.EnclosingStatementLabels; - var prevLoopStack = resolver.LoopStack; - resolver.EnclosingStatementLabels = new Scope(resolver.Options); - resolver.LoopStack = new List(); - resolver.ResolveStatement(Proof, context); - resolver.EnclosingStatementLabels = prevLblStmts; - resolver.LoopStack = prevLoopStack; - } + ModuleResolver.ResolveByProof(resolver, Proof, context); } public bool HasAssertOnlyAttribute(out AssertOnlyKind assertOnlyKind) { diff --git a/Source/DafnyCore/Dafny.atg b/Source/DafnyCore/Dafny.atg index 7c9eaa7c000..d069d01cb73 100644 --- a/Source/DafnyCore/Dafny.atg +++ b/Source/DafnyCore/Dafny.atg @@ -2297,6 +2297,7 @@ UpdateStmt List rhss = new List(); Expression e; AssignmentRhs r; + BlockStmt proof = null; IToken x = Token.NoToken; IToken endTok = Token.NoToken; IToken startToken = Token.NoToken; @@ -2334,10 +2335,16 @@ UpdateStmt { "," Rhs (. rhss.Add(r); .) } ) - ";" (. endTok = t; .) + ( + "by" BlockStmt + | ";" + ) | ":" (. SemErr(ErrorId.p_invalid_colon, new RangeToken(startToken, t), "invalid statement beginning here (is a 'label' keyword missing? or a 'const' or 'var' keyword?)"); .) | { Attribute } (. endToken = t; .) - ";" (. endTok = t; rhss.Add(new ExprRhs(e, attrs) { RangeToken = new RangeToken(e.StartToken, endToken) }); .) + ( + "by" BlockStmt + | ";" + ) (. endTok = t; rhss.Add(new ExprRhs(e, attrs) { RangeToken = new RangeToken(e.StartToken, endToken) }); .) | { Attribute } (. endToken = t; .) (. endTok = t; rhss.Add(new ExprRhs(e, attrs) { RangeToken = new RangeToken(e.StartToken, endToken) }); SemErr(ErrorId.p_missing_semicolon, new RangeToken(startToken, t), "missing semicolon at end of statement"); @@ -2352,18 +2359,21 @@ UpdateStmt { Attribute } (. exceptionRhs = new ExprRhs(exceptionExpr, attrs); .) { "," Rhs (. rhss.Add(r); .) } - ";" (. endTok = t; .) + ( + "by" BlockStmt + | ";" (. endTok = t; .) + ) ) (. var rangeToken = new RangeToken(startToken, t); if (suchThat != null) { s = new AssignSuchThatStmt(rangeToken, lhss, suchThat, suchThatAssume, null); } else if (exceptionRhs != null) { - s = new AssignOrReturnStmt(rangeToken, lhss, exceptionRhs, keywordToken, rhss); + s = new AssignOrReturnStmt(rangeToken, lhss, exceptionRhs, keywordToken, rhss, proof); } else { if (lhss.Count == 0 && rhss.Count == 0) { s = new BlockStmt(rangeToken, new List()); // error, give empty statement } else { - s = new UpdateStmt(rangeToken, lhss, rhss); + s = new UpdateStmt(rangeToken, lhss, rhss, proof); } } .) @@ -2463,6 +2473,7 @@ VarDeclStatement<.out Statement/*!*/ s.> ExprRhs exceptionRhs = null; Attributes attrs = null; Attributes tokenAttrs = null; + BlockStmt proof = null; IToken endTok; IToken startToken = null; s = dummyStmt; @@ -2499,7 +2510,10 @@ VarDeclStatement<.out Statement/*!*/ s.> { "," Rhs (. rhss.Add(r); .) } ] - SYNC ";" (. endTok = t; .) + SYNC ( "by" + BlockStmt + | ";" (. endTok = t; .) + ) (. ConcreteUpdateStatement update; var lhsExprs = new List(); if (isGhost || (rhss.Count == 0 && exceptionRhs == null && suchThat == null)) { // explicitly ghost or no init @@ -2516,11 +2530,11 @@ VarDeclStatement<.out Statement/*!*/ s.> if (suchThat != null) { update = new AssignSuchThatStmt(updateRangeToken, lhsExprs, suchThat, suchThatAssume, attrs); } else if (exceptionRhs != null) { - update = new AssignOrReturnStmt(updateRangeToken, lhsExprs, exceptionRhs, keywordToken, rhss); + update = new AssignOrReturnStmt(updateRangeToken, lhsExprs, exceptionRhs, keywordToken, rhss, proof); } else if (rhss.Count == 0) { update = null; } else { - update = new UpdateStmt(updateRangeToken, lhsExprs, rhss); + update = new UpdateStmt(updateRangeToken, lhsExprs, rhss, proof); } s = new VarDeclStmt(rangeToken, lhss, update); .) diff --git a/Source/DafnyCore/Options/DafnyCommands.cs b/Source/DafnyCore/Options/DafnyCommands.cs index 74acb94d988..d3d3eadbe7a 100644 --- a/Source/DafnyCore/Options/DafnyCommands.cs +++ b/Source/DafnyCore/Options/DafnyCommands.cs @@ -58,6 +58,7 @@ static DafnyCommands() { CommonOptionBag.AddCompileSuffix, CommonOptionBag.SystemModule, IExecutableBackend.TranslationRecords, + ModuleDefinition.LegacyModuleNames }.Concat(VerificationOptions).ToList(); public static readonly IReadOnlyList