diff --git a/.gitignore b/.gitignore index eecb6d98b8b..42db0cde19e 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,8 @@ Source/packages Source/Dafny/Parser.cs.old Source/Dafny/Scanner.cs.old +Source/DafnyRuntime/MSBuild_Logs/ + Docs/OnlineTutorial/DocumentationTransducer.exe Docs/OnlineTutorial/DocumentationTransducer.pdb Docs/OnlineTutorial/DocumentationTransducer/obj diff --git a/Source/Dafny/AST/DafnyAst.cs b/Source/Dafny/AST/DafnyAst.cs index a250dccadc6..513e2fd96bd 100644 --- a/Source/Dafny/AST/DafnyAst.cs +++ b/Source/Dafny/AST/DafnyAst.cs @@ -6192,7 +6192,6 @@ public class Function : MemberDecl, TypeParameter.ParentType, ICallable { public Method/*?*/ ByMethodDecl; // filled in by resolution, if ByMethodBody is non-null public bool SignatureIsOmitted { get { return SignatureEllipsis != null; } } // is "false" for all Function objects that survive into resolution public readonly IToken SignatureEllipsis; - public bool IsBuiltin; public Function OverriddenFunction; public Function Original => OverriddenFunction == null ? this : OverriddenFunction.Original; public override bool IsOverrideThatAddsBody => base.IsOverrideThatAddsBody && Body != null; diff --git a/Source/Dafny/DafnyMain.cs b/Source/Dafny/DafnyMain.cs index af006b6880a..1de91962b1b 100644 --- a/Source/Dafny/DafnyMain.cs +++ b/Source/Dafny/DafnyMain.cs @@ -9,8 +9,8 @@ using System.IO; using System.Collections.Generic; using System.Diagnostics.Contracts; -using Bpl = Microsoft.Boogie; using System.Reflection; +using Microsoft.Boogie; namespace Microsoft.Dafny { @@ -121,10 +121,10 @@ public static string Parse(IList files, string programName, ErrorRepo foreach (DafnyFile dafnyFile in files) { Contract.Assert(dafnyFile != null); - if (Bpl.CommandLineOptions.Clo.XmlSink != null && Bpl.CommandLineOptions.Clo.XmlSink.IsOpen && !dafnyFile.UseStdin) { - Bpl.CommandLineOptions.Clo.XmlSink.WriteFileFragment(dafnyFile.FilePath); + if (CommandLineOptions.Clo.XmlSink != null && CommandLineOptions.Clo.XmlSink.IsOpen && !dafnyFile.UseStdin) { + CommandLineOptions.Clo.XmlSink.WriteFileFragment(dafnyFile.FilePath); } - if (Bpl.CommandLineOptions.Clo.Trace) { + if (CommandLineOptions.Clo.Trace) { Console.WriteLine("Parsing " + dafnyFile.FilePath); } @@ -155,7 +155,7 @@ public static string Parse(IList files, string programName, ErrorRepo } public static string Resolve(Program program, ErrorReporter reporter) { - if (Bpl.CommandLineOptions.Clo.NoResolve || Bpl.CommandLineOptions.Clo.NoTypecheck) { return null; } + if (CommandLineOptions.Clo.NoResolve || CommandLineOptions.Clo.NoTypecheck) { return null; } Dafny.Resolver r = new Dafny.Resolver(program); r.ResolveProgram(program); @@ -221,15 +221,102 @@ private static string ParseFile(DafnyFile dafnyFile, Include include, ModuleDecl try { int errorCount = Dafny.Parser.Parse(dafnyFile.UseStdin, dafnyFile.SourceFileName, include, module, builtIns, errs, verifyThisFile, compileThisFile); if (errorCount != 0) { - return string.Format("{0} parse errors detected in {1}", errorCount, fn); + return $"{errorCount} parse errors detected in {fn}"; } } catch (IOException e) { - Bpl.IToken tok = include == null ? Bpl.Token.NoToken : include.tok; + IToken tok = include == null ? Token.NoToken : include.tok; errs.SemErr(tok, "Unable to open included file"); - return string.Format("Error opening file \"{0}\": {1}", fn, e.Message); + return $"Error opening file \"{fn}\": {e.Message}"; } return null; // Success } + public static bool BoogieOnce(string baseFile, string moduleName, Microsoft.Boogie.Program boogieProgram, string programId, + out PipelineStatistics stats, out PipelineOutcome oc) { + if (programId == null) { + programId = "main_program_id"; + } + programId += "_" + moduleName; + + string bplFilename; + if (CommandLineOptions.Clo.PrintFile != null) { + bplFilename = CommandLineOptions.Clo.PrintFile; + } else { + string baseName = cce.NonNull(Path.GetFileName(baseFile)); + baseName = cce.NonNull(Path.ChangeExtension(baseName, "bpl")); + bplFilename = Path.Combine(Path.GetTempPath(), baseName); + } + + bplFilename = BoogieProgramSuffix(bplFilename, moduleName); + stats = null; + oc = BoogiePipelineWithRerun(boogieProgram, bplFilename, out stats, 1 < Dafny.DafnyOptions.Clo.VerifySnapshots ? programId : null); + return IsBoogieVerified(oc, stats); + } + + public static string BoogieProgramSuffix(string printFile, string suffix) { + var baseName = Path.GetFileNameWithoutExtension(printFile); + var dirName = Path.GetDirectoryName(printFile); + + return Path.Combine(dirName, baseName + "_" + suffix + Path.GetExtension(printFile)); + } + + public static bool IsBoogieVerified(PipelineOutcome outcome, PipelineStatistics statistics) { + return (outcome == PipelineOutcome.Done || outcome == PipelineOutcome.VerificationCompleted) + && statistics.ErrorCount == 0 + && statistics.InconclusiveCount == 0 + && statistics.TimeoutCount == 0 + && statistics.OutOfResourceCount == 0 + && statistics.OutOfMemoryCount == 0; + } + + /// + /// Resolve, type check, infer invariants for, and verify the given Boogie program. + /// The intention is that this Boogie program has been produced by translation from something + /// else. Hence, any resolution errors and type checking errors are due to errors in + /// the translation. + /// The method prints errors for resolution and type checking errors, but still returns + /// their error code. + /// + static PipelineOutcome BoogiePipelineWithRerun(Microsoft.Boogie.Program/*!*/ program, string/*!*/ bplFileName, + out PipelineStatistics stats, string programId) { + Contract.Requires(program != null); + Contract.Requires(bplFileName != null); + Contract.Ensures(0 <= Contract.ValueAtReturn(out stats).InconclusiveCount && 0 <= Contract.ValueAtReturn(out stats).TimeoutCount); + + stats = new PipelineStatistics(); + CivlTypeChecker ctc; + PipelineOutcome oc = ExecutionEngine.ResolveAndTypecheck(program, bplFileName, out ctc); + switch (oc) { + case PipelineOutcome.Done: + return oc; + + case PipelineOutcome.ResolutionError: + case PipelineOutcome.TypeCheckingError: { + ExecutionEngine.PrintBplFile(bplFileName, program, false, false, CommandLineOptions.Clo.PrettyPrint); + Console.WriteLine(); + Console.WriteLine("*** Encountered internal translation error - re-running Boogie to get better debug information"); + Console.WriteLine(); + + List/*!*/ fileNames = new List(); + fileNames.Add(bplFileName); + var reparsedProgram = ExecutionEngine.ParseBoogieProgram(fileNames, true); + if (reparsedProgram != null) { + ExecutionEngine.ResolveAndTypecheck(reparsedProgram, bplFileName, out ctc); + } + } + return oc; + + case PipelineOutcome.ResolvedAndTypeChecked: + ExecutionEngine.EliminateDeadVariables(program); + ExecutionEngine.CollectModSets(program); + ExecutionEngine.CoalesceBlocks(program); + ExecutionEngine.Inline(program); + return ExecutionEngine.InferAndVerify(program, stats, programId); + + default: + Contract.Assert(false); throw new cce.UnreachableException(); // unexpected outcome + } + } + } } \ No newline at end of file diff --git a/Source/Dafny/DafnyOptions.cs b/Source/Dafny/DafnyOptions.cs index f377e325213..4564302b1a0 100644 --- a/Source/Dafny/DafnyOptions.cs +++ b/Source/Dafny/DafnyOptions.cs @@ -17,6 +17,9 @@ public class DafnyOptions : Bpl.CommandLineOptions { public DafnyOptions(ErrorReporter errorReporter = null) : base("Dafny", "Dafny program verifier") { this.errorReporter = errorReporter; + Prune = true; + NormalizeNames = true; + EmitDebugInformation = false; } public override string VersionNumber { diff --git a/Source/Dafny/DafnyPrelude.bpl b/Source/Dafny/DafnyPrelude.bpl index f0dd74bfce5..456b0acc30e 100644 --- a/Source/Dafny/DafnyPrelude.bpl +++ b/Source/Dafny/DafnyPrelude.bpl @@ -17,37 +17,61 @@ axiom $$Language$Dafny; // coming from a Dafny program. type Ty; type Bv0 = int; -const unique TBool : Ty; -const unique TChar : Ty; -const unique TInt : Ty; -const unique TReal : Ty; -const unique TORDINAL : Ty; -function TBitvector(int) : Ty; -function TSet(Ty) : Ty; -function TISet(Ty) : Ty; -function TMultiSet(Ty) : Ty; -function TSeq(Ty) : Ty; -function TMap(Ty, Ty) : Ty; -function TIMap(Ty, Ty) : Ty; +const unique TBool : Ty uses { + axiom Tag(TBool) == TagBool; +} +const unique TChar : Ty uses { + axiom Tag(TChar) == TagChar; +} +const unique TInt : Ty uses { + axiom Tag(TInt) == TagInt; +} +const unique TReal : Ty uses { + axiom Tag(TReal) == TagReal; +} +const unique TORDINAL : Ty uses { + axiom Tag(TORDINAL) == TagORDINAL; +} +// See for which axioms we can make use of the trigger to determine the connection. +function TBitvector(int) : Ty uses { + axiom (forall w: int :: { TBitvector(w) } Inv0_TBitvector(TBitvector(w)) == w); +} +function TSet(Ty) : Ty uses { + axiom (forall t: Ty :: { TSet(t) } Inv0_TSet(TSet(t)) == t); + axiom (forall t: Ty :: { TSet(t) } Tag(TSet(t)) == TagSet); +} +function TISet(Ty) : Ty uses { + axiom (forall t: Ty :: { TISet(t) } Inv0_TISet(TISet(t)) == t); + axiom (forall t: Ty :: { TISet(t) } Tag(TISet(t)) == TagISet); +} +function TMultiSet(Ty) : Ty uses { + axiom (forall t: Ty :: { TMultiSet(t) } Inv0_TMultiSet(TMultiSet(t)) == t); + axiom (forall t: Ty :: { TMultiSet(t) } Tag(TMultiSet(t)) == TagMultiSet); +} +function TSeq(Ty) : Ty uses { + axiom (forall t: Ty :: { TSeq(t) } Inv0_TSeq(TSeq(t)) == t); + axiom (forall t: Ty :: { TSeq(t) } Tag(TSeq(t)) == TagSeq); +} +function TMap(Ty, Ty) : Ty uses { + axiom (forall t, u: Ty :: { TMap(t,u) } Inv0_TMap(TMap(t,u)) == t); + axiom (forall t, u: Ty :: { TMap(t,u) } Inv1_TMap(TMap(t,u)) == u); + axiom (forall t, u: Ty :: { TMap(t,u) } Tag(TMap(t,u)) == TagMap); +} +function TIMap(Ty, Ty) : Ty uses { + axiom (forall t, u: Ty :: { TIMap(t,u) } Inv0_TIMap(TIMap(t,u)) == t); + axiom (forall t, u: Ty :: { TIMap(t,u) } Inv1_TIMap(TIMap(t,u)) == u); + axiom (forall t, u: Ty :: { TIMap(t,u) } Tag(TIMap(t,u)) == TagIMap); +} function Inv0_TBitvector(Ty) : int; -axiom (forall w: int :: { TBitvector(w) } Inv0_TBitvector(TBitvector(w)) == w); function Inv0_TSet(Ty) : Ty; -axiom (forall t: Ty :: { TSet(t) } Inv0_TSet(TSet(t)) == t); function Inv0_TISet(Ty) : Ty; -axiom (forall t: Ty :: { TISet(t) } Inv0_TISet(TISet(t)) == t); function Inv0_TSeq(Ty) : Ty; -axiom (forall t: Ty :: { TSeq(t) } Inv0_TSeq(TSeq(t)) == t); function Inv0_TMultiSet(Ty) : Ty; -axiom (forall t: Ty :: { TMultiSet(t) } Inv0_TMultiSet(TMultiSet(t)) == t); function Inv0_TMap(Ty) : Ty; function Inv1_TMap(Ty) : Ty; -axiom (forall t, u: Ty :: { TMap(t,u) } Inv0_TMap(TMap(t,u)) == t); -axiom (forall t, u: Ty :: { TMap(t,u) } Inv1_TMap(TMap(t,u)) == u); function Inv0_TIMap(Ty) : Ty; function Inv1_TIMap(Ty) : Ty; -axiom (forall t, u: Ty :: { TIMap(t,u) } Inv0_TIMap(TIMap(t,u)) == t); -axiom (forall t, u: Ty :: { TIMap(t,u) } Inv1_TIMap(TIMap(t,u)) == u); // -- Classes and Datatypes -- @@ -68,58 +92,54 @@ const unique TagMap : TyTag; const unique TagIMap : TyTag; const unique TagClass : TyTag; -axiom Tag(TBool) == TagBool; -axiom Tag(TChar) == TagChar; -axiom Tag(TInt) == TagInt; -axiom Tag(TReal) == TagReal; -axiom Tag(TORDINAL) == TagORDINAL; -axiom (forall t: Ty :: { TSet(t) } Tag(TSet(t)) == TagSet); -axiom (forall t: Ty :: { TISet(t) } Tag(TISet(t)) == TagISet); -axiom (forall t: Ty :: { TMultiSet(t) } Tag(TMultiSet(t)) == TagMultiSet); -axiom (forall t: Ty :: { TSeq(t) } Tag(TSeq(t)) == TagSeq); -axiom (forall t, u: Ty :: { TMap(t,u) } Tag(TMap(t,u)) == TagMap); -axiom (forall t, u: Ty :: { TIMap(t,u) } Tag(TIMap(t,u)) == TagIMap); - type TyTagFamily; function TagFamily(Ty): TyTagFamily; // --------------------------------------------------------------- // -- Literals --------------------------------------------------- // --------------------------------------------------------------- -function {:identity} Lit(x: T): T { x } -axiom (forall x: T :: { $Box(Lit(x)) } $Box(Lit(x)) == Lit($Box(x)) ); +function {:identity} Lit(x: T): T { x } uses { + axiom (forall x: T :: { $Box(Lit(x)) } $Box(Lit(x)) == Lit($Box(x)) ); +} // Specialize Lit to concrete types. // These aren't logically required, but on some examples improve // verification speed -function {:identity} LitInt(x: int): int { x } -axiom (forall x: int :: { $Box(LitInt(x)) } $Box(LitInt(x)) == Lit($Box(x)) ); -function {:identity} LitReal(x: real): real { x } -axiom (forall x: real :: { $Box(LitReal(x)) } $Box(LitReal(x)) == Lit($Box(x)) ); +function {:identity} LitInt(x: int): int { x } uses { + axiom (forall x: int :: { $Box(LitInt(x)) } $Box(LitInt(x)) == Lit($Box(x)) ); +} +function {:identity} LitReal(x: real): real { x } uses { + axiom (forall x: real :: { $Box(LitReal(x)) } $Box(LitReal(x)) == Lit($Box(x)) ); +} // --------------------------------------------------------------- // -- Characters ------------------------------------------------- // --------------------------------------------------------------- type char; -function char#FromInt(int): char; -function char#ToInt(char): int; // inverse of char#FromInt -axiom (forall ch: char :: - { char#ToInt(ch) } - char#FromInt(char#ToInt(ch)) == ch && - 0 <= char#ToInt(ch) && char#ToInt(ch) < 65536); -axiom (forall n: int :: - { char#FromInt(n) } - 0 <= n && n < 65536 ==> char#ToInt(char#FromInt(n)) == n); - -function char#Plus(char, char): char; -function char#Minus(char, char): char; -axiom (forall a: char, b: char :: - { char#Plus(a, b) } - char#Plus(a, b) == char#FromInt(char#ToInt(a) + char#ToInt(b))); -axiom (forall a: char, b: char :: - { char#Minus(a, b) } - char#Minus(a, b) == char#FromInt(char#ToInt(a) - char#ToInt(b))); +function char#FromInt(int): char uses { + axiom (forall n: int :: + { char#FromInt(n) } + 0 <= n && n < 65536 ==> char#ToInt(char#FromInt(n)) == n); +} + +function char#ToInt(char): int uses { + axiom (forall ch: char :: + { char#ToInt(ch) } + char#FromInt(char#ToInt(ch)) == ch && + 0 <= char#ToInt(ch) && char#ToInt(ch) < 65536); +} + +function char#Plus(char, char): char uses { + axiom (forall a: char, b: char :: + { char#Plus(a, b) } + char#Plus(a, b) == char#FromInt(char#ToInt(a) + char#ToInt(b))); +} +function char#Minus(char, char): char uses { + axiom (forall a: char, b: char :: + { char#Minus(a, b) } + char#Minus(a, b) == char#FromInt(char#ToInt(a) - char#ToInt(b))); +} // --------------------------------------------------------------- // -- References ------------------------------------------------- @@ -135,10 +155,15 @@ const null: ref; type Box; const $ArbitraryBoxValue: Box; -function $Box(T): Box; +function $Box(T): Box uses { + axiom (forall x : T :: { $Box(x) } $Unbox($Box(x)) == x); +} function $Unbox(Box): T; -axiom (forall x : T :: { $Box(x) } $Unbox($Box(x)) == x); +// Corresponding entries for boxes... +// This could probably be solved by having Box also inhabit Ty +function $IsBox(T,Ty): bool; +function $IsAllocBox(T,Ty,Heap): bool; axiom (forall bx : Box :: { $IsBox(bx, TInt) } @@ -191,113 +216,112 @@ axiom (forall v : T, t : Ty, h : Heap :: // Type-argument to $Is is the /representation type/, // the second value argument to $Is is the actual type. -function $Is(T,Ty): bool; // no heap for now -function $IsAlloc(T,Ty,Heap): bool; - -// Corresponding entries for boxes... -// This could probably be solved by having Box also inhabit Ty -function $IsBox(T,Ty): bool; -function $IsAllocBox(T,Ty,Heap): bool; - -axiom(forall v : int :: { $Is(v,TInt) } $Is(v,TInt)); -axiom(forall v : real :: { $Is(v,TReal) } $Is(v,TReal)); -axiom(forall v : bool :: { $Is(v,TBool) } $Is(v,TBool)); -axiom(forall v : char :: { $Is(v,TChar) } $Is(v,TChar)); -axiom(forall v : ORDINAL :: { $Is(v,TORDINAL) } $Is(v,TORDINAL)); - -axiom(forall h : Heap, v : int :: { $IsAlloc(v,TInt,h) } $IsAlloc(v,TInt,h)); -axiom(forall h : Heap, v : real :: { $IsAlloc(v,TReal,h) } $IsAlloc(v,TReal,h)); -axiom(forall h : Heap, v : bool :: { $IsAlloc(v,TBool,h) } $IsAlloc(v,TBool,h)); -axiom(forall h : Heap, v : char :: { $IsAlloc(v,TChar,h) } $IsAlloc(v,TChar,h)); -axiom(forall h : Heap, v : ORDINAL :: { $IsAlloc(v,TORDINAL,h) } $IsAlloc(v,TORDINAL,h)); - -// Since every bitvector type is a separate type in Boogie, the $Is/$IsAlloc axioms -// for bitvectors are generated programatically. Except, TBitvector(0) is given here. -axiom (forall v: Bv0 :: { $Is(v, TBitvector(0)) } $Is(v, TBitvector(0))); -axiom (forall v: Bv0, h: Heap :: { $IsAlloc(v, TBitvector(0), h) } $IsAlloc(v, TBitvector(0), h)); - -axiom (forall v: Set Box, t0: Ty :: { $Is(v, TSet(t0)) } - $Is(v, TSet(t0)) <==> - (forall bx: Box :: { v[bx] } - v[bx] ==> $IsBox(bx, t0))); -axiom (forall v: ISet Box, t0: Ty :: { $Is(v, TISet(t0)) } - $Is(v, TISet(t0)) <==> - (forall bx: Box :: { v[bx] } - v[bx] ==> $IsBox(bx, t0))); -axiom (forall v: MultiSet Box, t0: Ty :: { $Is(v, TMultiSet(t0)) } - $Is(v, TMultiSet(t0)) <==> - (forall bx: Box :: { v[bx] } - 0 < v[bx] ==> $IsBox(bx, t0))); -axiom (forall v: MultiSet Box, t0: Ty :: { $Is(v, TMultiSet(t0)) } - $Is(v, TMultiSet(t0)) ==> $IsGoodMultiSet(v)); -axiom (forall v: Seq Box, t0: Ty :: { $Is(v, TSeq(t0)) } - $Is(v, TSeq(t0)) <==> - (forall i : int :: { Seq#Index(v, i) } - 0 <= i && i < Seq#Length(v) ==> - $IsBox(Seq#Index(v, i), t0))); -axiom (forall v: Set Box, t0: Ty, h: Heap :: { $IsAlloc(v, TSet(t0), h) } - $IsAlloc(v, TSet(t0), h) <==> - (forall bx: Box :: { v[bx] } - v[bx] ==> $IsAllocBox(bx, t0, h))); -axiom (forall v: ISet Box, t0: Ty, h: Heap :: { $IsAlloc(v, TISet(t0), h) } - $IsAlloc(v, TISet(t0), h) <==> - (forall bx: Box :: { v[bx] } - v[bx] ==> $IsAllocBox(bx, t0, h))); -axiom (forall v: MultiSet Box, t0: Ty, h: Heap :: { $IsAlloc(v, TMultiSet(t0), h) } - $IsAlloc(v, TMultiSet(t0), h) <==> - (forall bx: Box :: { v[bx] } - 0 < v[bx] ==> $IsAllocBox(bx, t0, h))); -axiom (forall v: Seq Box, t0: Ty, h: Heap :: { $IsAlloc(v, TSeq(t0), h) } - $IsAlloc(v, TSeq(t0), h) <==> - (forall i : int :: { Seq#Index(v, i) } - 0 <= i && i < Seq#Length(v) ==> - $IsAllocBox(Seq#Index(v, i), t0, h))); - -axiom (forall v: Map Box Box, t0: Ty, t1: Ty :: - { $Is(v, TMap(t0, t1)) } - $Is(v, TMap(t0, t1)) - <==> (forall bx: Box :: - { Map#Elements(v)[bx] } { Map#Domain(v)[bx] } - Map#Domain(v)[bx] ==> - $IsBox(Map#Elements(v)[bx], t1) && - $IsBox(bx, t0))); -axiom (forall v: Map Box Box, t0: Ty, t1: Ty, h: Heap :: - { $IsAlloc(v, TMap(t0, t1), h) } - $IsAlloc(v, TMap(t0, t1), h) - <==> (forall bx: Box :: - { Map#Elements(v)[bx] } { Map#Domain(v)[bx] } - Map#Domain(v)[bx] ==> - $IsAllocBox(Map#Elements(v)[bx], t1, h) && - $IsAllocBox(bx, t0, h))); -axiom (forall v: Map Box Box, t0: Ty, t1: Ty :: - { $Is(v, TMap(t0, t1)) } - $Is(v, TMap(t0, t1)) ==> - $Is(Map#Domain(v), TSet(t0)) && - $Is(Map#Values(v), TSet(t1)) && - $Is(Map#Items(v), TSet(Tclass._System.Tuple2(t0, t1)))); - -axiom (forall v: IMap Box Box, t0: Ty, t1: Ty :: - { $Is(v, TIMap(t0, t1)) } - $Is(v, TIMap(t0, t1)) - <==> (forall bx: Box :: - { IMap#Elements(v)[bx] } { IMap#Domain(v)[bx] } - IMap#Domain(v)[bx] ==> - $IsBox(IMap#Elements(v)[bx], t1) && - $IsBox(bx, t0))); -axiom (forall v: IMap Box Box, t0: Ty, t1: Ty, h: Heap :: - { $IsAlloc(v, TIMap(t0, t1), h) } - $IsAlloc(v, TIMap(t0, t1), h) - <==> (forall bx: Box :: - { IMap#Elements(v)[bx] } { IMap#Domain(v)[bx] } - IMap#Domain(v)[bx] ==> - $IsAllocBox(IMap#Elements(v)[bx], t1, h) && - $IsAllocBox(bx, t0, h))); -axiom (forall v: IMap Box Box, t0: Ty, t1: Ty :: - { $Is(v, TIMap(t0, t1)) } - $Is(v, TIMap(t0, t1)) ==> - $Is(IMap#Domain(v), TISet(t0)) && - $Is(IMap#Values(v), TISet(t1)) && - $Is(IMap#Items(v), TISet(Tclass._System.Tuple2(t0, t1)))); +function $Is(T,Ty): bool uses { // no heap for now + axiom(forall v : int :: { $Is(v,TInt) } $Is(v,TInt)); + axiom(forall v : real :: { $Is(v,TReal) } $Is(v,TReal)); + axiom(forall v : bool :: { $Is(v,TBool) } $Is(v,TBool)); + axiom(forall v : char :: { $Is(v,TChar) } $Is(v,TChar)); + axiom(forall v : ORDINAL :: { $Is(v,TORDINAL) } $Is(v,TORDINAL)); + + // Since every bitvector type is a separate type in Boogie, the $Is/$IsAlloc axioms + // for bitvectors are generated programatically. Except, TBitvector(0) is given here. + axiom (forall v: Bv0 :: { $Is(v, TBitvector(0)) } $Is(v, TBitvector(0))); + + axiom (forall v: Set Box, t0: Ty :: { $Is(v, TSet(t0)) } + $Is(v, TSet(t0)) <==> + (forall bx: Box :: { v[bx] } + v[bx] ==> $IsBox(bx, t0))); + axiom (forall v: ISet Box, t0: Ty :: { $Is(v, TISet(t0)) } + $Is(v, TISet(t0)) <==> + (forall bx: Box :: { v[bx] } + v[bx] ==> $IsBox(bx, t0))); + axiom (forall v: MultiSet Box, t0: Ty :: { $Is(v, TMultiSet(t0)) } + $Is(v, TMultiSet(t0)) <==> + (forall bx: Box :: { v[bx] } + 0 < v[bx] ==> $IsBox(bx, t0))); + axiom (forall v: MultiSet Box, t0: Ty :: { $Is(v, TMultiSet(t0)) } + $Is(v, TMultiSet(t0)) ==> $IsGoodMultiSet(v)); + axiom (forall v: Seq Box, t0: Ty :: { $Is(v, TSeq(t0)) } + $Is(v, TSeq(t0)) <==> + (forall i : int :: { Seq#Index(v, i) } + 0 <= i && i < Seq#Length(v) ==> + $IsBox(Seq#Index(v, i), t0))); + + axiom (forall v: Map Box Box, t0: Ty, t1: Ty :: + { $Is(v, TMap(t0, t1)) } + $Is(v, TMap(t0, t1)) + <==> (forall bx: Box :: + { Map#Elements(v)[bx] } { Map#Domain(v)[bx] } + Map#Domain(v)[bx] ==> + $IsBox(Map#Elements(v)[bx], t1) && + $IsBox(bx, t0))); + + axiom (forall v: Map Box Box, t0: Ty, t1: Ty :: + { $Is(v, TMap(t0, t1)) } + $Is(v, TMap(t0, t1)) ==> + $Is(Map#Domain(v), TSet(t0)) && + $Is(Map#Values(v), TSet(t1)) && + $Is(Map#Items(v), TSet(Tclass._System.Tuple2(t0, t1)))); + axiom (forall v: IMap Box Box, t0: Ty, t1: Ty :: + { $Is(v, TIMap(t0, t1)) } + $Is(v, TIMap(t0, t1)) + <==> (forall bx: Box :: + { IMap#Elements(v)[bx] } { IMap#Domain(v)[bx] } + IMap#Domain(v)[bx] ==> + $IsBox(IMap#Elements(v)[bx], t1) && + $IsBox(bx, t0))); + axiom (forall v: IMap Box Box, t0: Ty, t1: Ty :: + { $Is(v, TIMap(t0, t1)) } + $Is(v, TIMap(t0, t1)) ==> + $Is(IMap#Domain(v), TISet(t0)) && + $Is(IMap#Values(v), TISet(t1)) && + $Is(IMap#Items(v), TISet(Tclass._System.Tuple2(t0, t1)))); +} +function $IsAlloc(T,Ty,Heap): bool uses { + axiom(forall h : Heap, v : int :: { $IsAlloc(v,TInt,h) } $IsAlloc(v,TInt,h)); + axiom(forall h : Heap, v : real :: { $IsAlloc(v,TReal,h) } $IsAlloc(v,TReal,h)); + axiom(forall h : Heap, v : bool :: { $IsAlloc(v,TBool,h) } $IsAlloc(v,TBool,h)); + axiom(forall h : Heap, v : char :: { $IsAlloc(v,TChar,h) } $IsAlloc(v,TChar,h)); + axiom(forall h : Heap, v : ORDINAL :: { $IsAlloc(v,TORDINAL,h) } $IsAlloc(v,TORDINAL,h)); + + axiom (forall v: Bv0, h: Heap :: { $IsAlloc(v, TBitvector(0), h) } $IsAlloc(v, TBitvector(0), h)); + + axiom (forall v: Set Box, t0: Ty, h: Heap :: { $IsAlloc(v, TSet(t0), h) } + $IsAlloc(v, TSet(t0), h) <==> + (forall bx: Box :: { v[bx] } + v[bx] ==> $IsAllocBox(bx, t0, h))); + axiom (forall v: ISet Box, t0: Ty, h: Heap :: { $IsAlloc(v, TISet(t0), h) } + $IsAlloc(v, TISet(t0), h) <==> + (forall bx: Box :: { v[bx] } + v[bx] ==> $IsAllocBox(bx, t0, h))); + axiom (forall v: MultiSet Box, t0: Ty, h: Heap :: { $IsAlloc(v, TMultiSet(t0), h) } + $IsAlloc(v, TMultiSet(t0), h) <==> + (forall bx: Box :: { v[bx] } + 0 < v[bx] ==> $IsAllocBox(bx, t0, h))); + axiom (forall v: Seq Box, t0: Ty, h: Heap :: { $IsAlloc(v, TSeq(t0), h) } + $IsAlloc(v, TSeq(t0), h) <==> + (forall i : int :: { Seq#Index(v, i) } + 0 <= i && i < Seq#Length(v) ==> + $IsAllocBox(Seq#Index(v, i), t0, h))); + + axiom (forall v: Map Box Box, t0: Ty, t1: Ty, h: Heap :: + { $IsAlloc(v, TMap(t0, t1), h) } + $IsAlloc(v, TMap(t0, t1), h) + <==> (forall bx: Box :: + { Map#Elements(v)[bx] } { Map#Domain(v)[bx] } + Map#Domain(v)[bx] ==> + $IsAllocBox(Map#Elements(v)[bx], t1, h) && + $IsAllocBox(bx, t0, h))); + + axiom (forall v: IMap Box Box, t0: Ty, t1: Ty, h: Heap :: + { $IsAlloc(v, TIMap(t0, t1), h) } + $IsAlloc(v, TIMap(t0, t1), h) + <==> (forall bx: Box :: + { IMap#Elements(v)[bx] } { IMap#Domain(v)[bx] } + IMap#Domain(v)[bx] ==> + $IsAllocBox(IMap#Elements(v)[bx], t1, h) && + $IsAllocBox(bx, t0, h))); +} // --------------------------------------------------------------- // -- Encoding of type names ------------------------------------- @@ -468,7 +492,9 @@ axiom (forall f : [LayerType]A, ly : LayerType :: { AtLayer(f,$LS(ly)) } AtLa type Field alpha; -function FDim(Field T): int; +function FDim(Field T): int uses { + axiom FDim(alloc) == 0; +} function IndexField(int): Field Box; axiom (forall i: int :: { IndexField(i) } FDim(IndexField(i)) == 1); @@ -486,13 +512,23 @@ axiom (forall f: Field Box, i: int :: { MultiIndexField(f,i) } function DeclType(Field T): ClassName; type NameFamily; -function DeclName(Field T): NameFamily; +function DeclName(Field T): NameFamily uses { + axiom DeclName(alloc) == allocName; +} function FieldOfDecl(ClassName, NameFamily): Field alpha; axiom (forall cl : ClassName, nm: NameFamily :: {FieldOfDecl(cl, nm): Field T} DeclType(FieldOfDecl(cl, nm): Field T) == cl && DeclName(FieldOfDecl(cl, nm): Field T) == nm); -function $IsGhostField(Field T): bool; +function $IsGhostField(Field T): bool uses { + axiom $IsGhostField(alloc); // treat as ghost field, since it is allowed to be changed by ghost code + + axiom (forall h: Heap, k: Heap :: { $HeapSuccGhost(h,k) } + $HeapSuccGhost(h,k) ==> + $HeapSucc(h,k) && + (forall o: ref, f: Field alpha :: { read(k, o, f) } + !$IsGhostField(f) ==> read(h, o, f) == read(k, o, f))); +} // --------------------------------------------------------------- // -- Allocatedness and Heap Succession -------------------------- @@ -501,10 +537,10 @@ function $IsGhostField(Field T): bool; // $IsAlloc and $IsAllocBox are monotonic -axiom(forall h, k : Heap, v : T, t : Ty :: +axiom (forall h, k : Heap, v : T, t : Ty :: { $HeapSucc(h, k), $IsAlloc(v, t, h) } $HeapSucc(h, k) ==> $IsAlloc(v, t, h) ==> $IsAlloc(v, t, k)); -axiom(forall h, k : Heap, bx : Box, t : Ty :: +axiom (forall h, k : Heap, bx : Box, t : Ty :: { $HeapSucc(h, k), $IsAllocBox(bx, t, h) } $HeapSucc(h, k) ==> $IsAllocBox(bx, t, h) ==> $IsAllocBox(bx, t, k)); @@ -512,16 +548,14 @@ axiom(forall h, k : Heap, bx : Box, t : Ty :: const unique alloc: Field bool; const unique allocName: NameFamily; -axiom FDim(alloc) == 0 && - DeclName(alloc) == allocName && - $IsGhostField(alloc); // treat as ghost field, since it is allowed to be changed by ghost code // --------------------------------------------------------------- // -- Arrays ----------------------------------------------------- // --------------------------------------------------------------- -function _System.array.Length(a: ref): int; -axiom (forall o: ref :: 0 <= _System.array.Length(o)); +function _System.array.Length(a: ref): int uses { + axiom (forall o: ref :: 0 <= _System.array.Length(o)); +} // --------------------------------------------------------------- // -- Reals ------------------------------------------------------ @@ -547,8 +581,9 @@ var $Heap: Heap where $IsGoodHeap($Heap) && $IsHeapAnchor($Heap); // The following is used as a reference heap in places where the translation needs a heap // but the expression generated is really one that is (at least in a correct program) // independent of the heap. -const $OneHeap: Heap; -axiom $IsGoodHeap($OneHeap); +const $OneHeap: Heap uses { + axiom $IsGoodHeap($OneHeap); +} function $HeapSucc(Heap, Heap): bool; axiom (forall h: Heap, r: ref, f: Field alpha, x: alpha :: { update(h, r, f, x) } @@ -560,11 +595,6 @@ axiom (forall h: Heap, k: Heap :: { $HeapSucc(h,k) } $HeapSucc(h,k) ==> (forall o: ref :: { read(k, o, alloc) } read(h, o, alloc) ==> read(k, o, alloc))); function $HeapSuccGhost(Heap, Heap): bool; -axiom (forall h: Heap, k: Heap :: { $HeapSuccGhost(h,k) } - $HeapSuccGhost(h,k) ==> - $HeapSucc(h,k) && - (forall o: ref, f: Field alpha :: { read(k, o, f) } - !$IsGhostField(f) ==> read(h, o, f) == read(k, o, f))); // --------------------------------------------------------------- // -- Non-determinism -------------------------------------------- @@ -693,7 +723,7 @@ axiom (forall a, b: Set T :: Set#Card(Set#Difference(a, b)) == Set#Card(a) - Set#Card(Set#Intersection(a, b))); function Set#Subset(Set T, Set T): bool; -axiom(forall a: Set T, b: Set T :: { Set#Subset(a,b) } +axiom (forall a: Set T, b: Set T :: { Set#Subset(a,b) } Set#Subset(a,b) <==> (forall o: T :: {a[o]} {b[o]} a[o] ==> b[o])); // axiom(forall a: Set T, b: Set T :: // { Set#Subset(a,b), Set#Card(a), Set#Card(b) } // very restrictive trigger @@ -701,9 +731,9 @@ axiom(forall a: Set T, b: Set T :: { Set#Subset(a,b) } function Set#Equal(Set T, Set T): bool; -axiom(forall a: Set T, b: Set T :: { Set#Equal(a,b) } +axiom (forall a: Set T, b: Set T :: { Set#Equal(a,b) } Set#Equal(a,b) <==> (forall o: T :: {a[o]} {b[o]} a[o] <==> b[o])); -axiom(forall a: Set T, b: Set T :: { Set#Equal(a,b) } // extensionality axiom for sets +axiom (forall a: Set T, b: Set T :: { Set#Equal(a,b) } // extensionality axiom for sets Set#Equal(a,b) ==> a == b); function Set#Disjoint(Set T, Set T): bool; @@ -764,13 +794,13 @@ axiom (forall a, b: ISet T, y: T :: { ISet#Difference(a, b), b[y] } b[y] ==> !ISet#Difference(a, b)[y] ); function ISet#Subset(ISet T, ISet T): bool; -axiom(forall a: ISet T, b: ISet T :: { ISet#Subset(a,b) } +axiom (forall a: ISet T, b: ISet T :: { ISet#Subset(a,b) } ISet#Subset(a,b) <==> (forall o: T :: {a[o]} {b[o]} a[o] ==> b[o])); function ISet#Equal(ISet T, ISet T): bool; -axiom(forall a: ISet T, b: ISet T :: { ISet#Equal(a,b) } +axiom (forall a: ISet T, b: ISet T :: { ISet#Equal(a,b) } ISet#Equal(a,b) <==> (forall o: T :: {a[o]} {b[o]} a[o] <==> b[o])); -axiom(forall a: ISet T, b: ISet T :: { ISet#Equal(a,b) } // extensionality axiom for sets +axiom (forall a: ISet T, b: ISet T :: { ISet#Equal(a,b) } // extensionality axiom for sets ISet#Equal(a,b) ==> a == b); function ISet#Disjoint(ISet T, ISet T): bool; @@ -863,14 +893,14 @@ axiom (forall a, b: MultiSet T :: // multiset subset means a must have at most as many of each element as b function MultiSet#Subset(MultiSet T, MultiSet T): bool; -axiom(forall a: MultiSet T, b: MultiSet T :: { MultiSet#Subset(a,b) } +axiom (forall a: MultiSet T, b: MultiSet T :: { MultiSet#Subset(a,b) } MultiSet#Subset(a,b) <==> (forall o: T :: {a[o]} {b[o]} a[o] <= b[o])); function MultiSet#Equal(MultiSet T, MultiSet T): bool; -axiom(forall a: MultiSet T, b: MultiSet T :: { MultiSet#Equal(a,b) } +axiom (forall a: MultiSet T, b: MultiSet T :: { MultiSet#Equal(a,b) } MultiSet#Equal(a,b) <==> (forall o: T :: {a[o]} {b[o]} a[o] == b[o])); // extensionality axiom for multisets -axiom(forall a: MultiSet T, b: MultiSet T :: { MultiSet#Equal(a,b) } +axiom (forall a: MultiSet T, b: MultiSet T :: { MultiSet#Equal(a,b) } MultiSet#Equal(a,b) ==> a == b); function MultiSet#Disjoint(MultiSet T, MultiSet T): bool; @@ -886,7 +916,9 @@ axiom (forall s: Set T :: { MultiSet#Card(MultiSet#FromSet(s)) } MultiSet#Card(MultiSet#FromSet(s)) == Set#Card(s)); // conversion to a multiset, from a sequence. -function MultiSet#FromSeq(Seq T): MultiSet T; +function MultiSet#FromSeq(Seq T): MultiSet T uses { + axiom (forall :: MultiSet#FromSeq(Seq#Empty(): Seq T) == MultiSet#Empty(): MultiSet T); +} // conversion produces a good map. axiom (forall s: Seq T :: { MultiSet#FromSeq(s) } $IsGoodMultiSet(MultiSet#FromSeq(s)) ); // cardinality axiom @@ -898,7 +930,6 @@ axiom (forall s: Seq T, v: T :: { MultiSet#FromSeq(Seq#Build(s, v)) } MultiSet#FromSeq(Seq#Build(s, v)) == MultiSet#UnionOne(MultiSet#FromSeq(s), v) ); -axiom (forall :: MultiSet#FromSeq(Seq#Empty(): Seq T) == MultiSet#Empty(): MultiSet T); // concatenation axiom axiom (forall a: Seq T, b: Seq T :: diff --git a/Source/Dafny/Verifier/Translator.ClassMembers.cs b/Source/Dafny/Verifier/Translator.ClassMembers.cs new file mode 100644 index 00000000000..0d4cfb9077f --- /dev/null +++ b/Source/Dafny/Verifier/Translator.ClassMembers.cs @@ -0,0 +1,1478 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Text; +using Microsoft.Boogie; +using Bpl = Microsoft.Boogie; +using static Microsoft.Dafny.Util; + +namespace Microsoft.Dafny { + public partial class Translator { + void AddClassMembers(TopLevelDeclWithMembers c, bool includeAllMethods) { + Contract.Requires(sink != null && predef != null); + Contract.Requires(c != null); + Contract.Ensures(fuelContext == Contract.OldValue(fuelContext)); + Contract.Assert(VisibleInScope(c)); + + sink.AddTopLevelDeclaration(GetClass(c)); + if (c is ArrayClassDecl) { + // classes.Add(c, predef.ClassDotArray); + AddAllocationAxiom(null, null, (ArrayClassDecl)c, true); + } + + // Add $Is and $IsAlloc for this class : + // axiom (forall p: ref, G: Ty :: + // { $Is(p, TClassA(G), h) } + // $Is(p, TClassA(G), h) <=> (p == null || dtype(p) == TClassA(G)); + // axiom (forall p: ref, h: Heap, G: Ty :: + // { $IsAlloc(p, TClassA(G), h) } + // $IsAlloc(p, TClassA(G), h) => (p == null || h[p, alloc]); + MapM(c is ClassDecl ? Bools : new List(), is_alloc => { + List tyexprs; + var vars = MkTyParamBinders(GetTypeParams(c), out tyexprs); + + var o = BplBoundVar("$o", predef.RefType, vars); + + Bpl.Expr body, is_o; + Bpl.Expr o_null = Bpl.Expr.Eq(o, predef.Null); + Bpl.Expr o_ty = ClassTyCon(c, tyexprs); + string name; + + if (is_alloc) { + name = c + ": Class $IsAlloc"; + var h = BplBoundVar("$h", predef.HeapType, vars); + // $IsAlloc(o, ..) + is_o = MkIsAlloc(o, o_ty, h); + body = BplIff(is_o, BplOr(o_null, IsAlloced(c.tok, h, o))); + } else { + name = c + ": Class $Is"; + // $Is(o, ..) + is_o = MkIs(o, o_ty); + Bpl.Expr rhs; + if (c == program.BuiltIns.ObjectDecl) { + rhs = Bpl.Expr.True; + } else if (c is TraitDecl) { + //generating $o == null || implements$J(dtype(x), typeArgs) + var t = (TraitDecl)c; + var dtypeFunc = FunctionCall(o.tok, BuiltinFunction.DynamicType, null, o); + var implementsJ_Arguments = new List { dtypeFunc }; // TODO: also needs type parameters + implementsJ_Arguments.AddRange(tyexprs); + Bpl.Expr implementsFunc = FunctionCall(t.tok, "implements$" + t.FullSanitizedName, Bpl.Type.Bool, implementsJ_Arguments); + rhs = BplOr(o_null, implementsFunc); + } else { + rhs = BplOr(o_null, DType(o, o_ty)); + } + body = BplIff(is_o, rhs); + } + + AddIncludeDepAxiom(new Boogie.Axiom(c.tok, BplForall(vars, BplTrigger(is_o), body), name)); + }); + + if (c is TraitDecl) { + //this adds: function implements$J(Ty, typeArgs): bool; + var arg_ref = new Bpl.Formal(c.tok, new Bpl.TypedIdent(c.tok, "ty", predef.Ty), true); + var vars = new List { arg_ref }; + vars.AddRange(MkTyParamFormals(GetTypeParams(c))); + var res = new Bpl.Formal(c.tok, new Bpl.TypedIdent(c.tok, Bpl.TypedIdent.NoName, Bpl.Type.Bool), false); + var implement_intr = new Bpl.Function(c.tok, "implements$" + c.FullSanitizedName, vars, res); + sink.AddTopLevelDeclaration(implement_intr); + } else if (c is ClassDecl classDecl) { + AddImplementsAxioms(classDecl); + } + + foreach (MemberDecl member in c.Members.FindAll(VisibleInScope)) { + Contract.Assert(isAllocContext == null); + currentDeclaration = member; + if (member is Field) { + Field f = (Field)member; + Boogie.Declaration fieldDeclaration; + if (f is ConstantField) { + // The following call has the side effect of idempotently creating and adding the function to the sink's top-level declarations + Contract.Assert(currentModule == null); + currentModule = f.EnclosingClass.EnclosingModuleDefinition; + var oldFuelContext = fuelContext; + fuelContext = FuelSetting.NewFuelContext(f); + fieldDeclaration = GetReadonlyField(f); + fuelContext = oldFuelContext; + currentModule = null; + } else { + if (f.IsMutable) { + fieldDeclaration = GetField(f); + sink.AddTopLevelDeclaration(fieldDeclaration); + } else { + fieldDeclaration = GetReadonlyField(f); + if (fieldDeclaration != predef.ArrayLength) { + sink.AddTopLevelDeclaration(fieldDeclaration); + } + } + } + AddAllocationAxiom(fieldDeclaration, f, c); + } else if (member is Function function) { + AddFunction_Top(function, includeAllMethods); + } else if (member is Method method) { + AddMethod_Top(method, false, includeAllMethods); + } else { + Contract.Assert(false); throw new cce.UnreachableException(); // unexpected member + } + } + } + + void AddFunction_Top(Function f, bool includeAllMethods) { + FuelContext oldFuelContext = this.fuelContext; + this.fuelContext = FuelSetting.NewFuelContext(f); + isAllocContext = new IsAllocContext(true); + + AddClassMember_Function(f); + + if (InVerificationScope(f)) { + AddWellformednessCheck(f); + if (f.OverriddenFunction != null) { //it means that f is overriding its associated parent function + AddFunctionOverrideCheckImpl(f); + } + } + if (f is ExtremePredicate cop) { + AddClassMember_Function(cop.PrefixPredicate); + // skip the well-formedness check, because it has already been done for the extreme predicate + } else if (f.ByMethodDecl != null) { + AddMethod_Top(f.ByMethodDecl, true, includeAllMethods); + } + + this.fuelContext = oldFuelContext; + isAllocContext = null; + } + + void AddMethod_Top(Method m, bool isByMethod, bool includeAllMethods) { + if (!includeAllMethods && !InVerificationScope(m) && !referencedMembers.Contains(m)) { + // do nothing + return; + } + + FuelContext oldFuelContext = this.fuelContext; + this.fuelContext = FuelSetting.NewFuelContext(m); + + // wellformedness check for method specification + if (m.EnclosingClass is IteratorDecl && m == ((IteratorDecl)m.EnclosingClass).Member_MoveNext) { + // skip the well-formedness check, because it has already been done for the iterator + } else { + if (!isByMethod) { + var proc = AddMethod(m, MethodTranslationKind.SpecWellformedness); + sink.AddTopLevelDeclaration(proc); + if (InVerificationScope(m)) { + AddMethodImpl(m, proc, true); + } + } + if (m.OverriddenMethod != null && InVerificationScope(m)) //method has overrided a parent method + { + var procOverrideChk = AddMethod(m, MethodTranslationKind.OverrideCheck); + sink.AddTopLevelDeclaration(procOverrideChk); + AddMethodOverrideCheckImpl(m, procOverrideChk); + } + } + // the method spec itself + if (!isByMethod) { + sink.AddTopLevelDeclaration(AddMethod(m, MethodTranslationKind.Call)); + } + if (m is ExtremeLemma) { + // Let the CoCall and Impl forms to use m.PrefixLemma signature and specification (and + // note that m.PrefixLemma.Body == m.Body. + m = ((ExtremeLemma)m).PrefixLemma; + sink.AddTopLevelDeclaration(AddMethod(m, MethodTranslationKind.CoCall)); + } + if (m.Body != null && InVerificationScope(m)) { + // ...and its implementation + assertionCount = 0; + var proc = AddMethod(m, MethodTranslationKind.Implementation); + sink.AddTopLevelDeclaration(proc); + AddMethodImpl(m, proc, false); + } + Reset(); + this.fuelContext = oldFuelContext; + } + + /// + /// For a non-static field "f" in a class "c(G)", generate: + /// // type axiom: + /// // If "G" is empty, then TClassA(G) is omitted from trigger. + /// // If "c" is an array declaration, then the bound variables also include the index variables "ii" and "h[o, f]" has the form "h[o, Index(ii)]". + /// // If "f" is readonly, then "h[o, f]" has the form "f(o)" (for special fields) or "f(G,o)" (for programmer-declared const fields), + /// // so "h" and $IsHeap(h) are omitted. + /// axiom fh < FunctionContextHeight ==> + /// (forall o: ref, h: Heap, G : Ty :: + /// { h[o, f], TClassA(G) } // if "f" is a const, omit TClassA(G) from the trigger and just use { f(G,o) } + /// $IsHeap(h) && + /// o != null && $Is(o, TClassA(G)) // or dtype(o) = TClassA(G) + /// ==> + /// $Is(h[o, f], TT(PP))); + /// + /// // allocation axiom: + /// // As above for "G" and "ii", but "h" is included no matter what. + /// axiom fh < FunctionContextHeight ==> + /// (forall o: ref, h: Heap, G : Ty :: + /// { h[o, f], TClassA(G) } // if "f" is a const, use the trigger { f(G,o), h[o, alloc] }; for other readonly fields, use { f(o), h[o, alloc], TClassA(G) } + /// $IsHeap(h) && + /// o != null && $Is(o, TClassA(G)) && // or dtype(o) = TClassA(G) + /// h[o, alloc] + /// ==> + /// $IsAlloc(h[o, f], TT(PP), h)); + /// + /// For a static (necessarily "const") field "f" in a class "c(G)", the expression corresponding to "h[o, f]" or "f(G,o)" above is "f(G)", + /// so generate: + /// // type axiom: + /// axiom fh < FunctionContextHeight ==> + /// (forall G : Ty :: + /// { f(G) } + /// $Is(f(G), TT(PP))); + /// // Or in the case where G is empty: + /// axiom $Is(f(G), TT); + /// + /// // allocation axiom: + /// axiom fh < FunctionContextHeight ==> + /// (forall h: Heap, G : Ty :: + /// { $IsAlloc(f(G), TT(PP), h) } + /// $IsHeap(h) + /// ==> + /// $IsAlloc(f(G), TT(PP), h)); + /// + /// + /// The axioms above could be optimised to something along the lines of: + /// axiom fh < FunctionContextHeight ==> + /// (forall o: ref, h: Heap :: + /// { h[o, f] } + /// $IsHeap(h) && o != null && Tag(dtype(o)) = TagClass + /// ==> + /// (h[o, alloc] ==> $IsAlloc(h[o, f], TT(TClassA_Inv_i(dtype(o)),..), h)) && + /// $Is(h[o, f], TT(TClassA_Inv_i(dtype(o)),..), h); + /// + private void AddAllocationAxiom(Boogie.Declaration fieldDeclaration, Field f, TopLevelDeclWithMembers c, bool is_array = false) { + Contract.Requires(c != null); + // IFF you're adding the array axioms, then the field should be null + Contract.Requires(is_array == (f == null)); + Contract.Requires(sink != null && predef != null); + + Bpl.Expr heightAntecedent = Bpl.Expr.True; + if (f is ConstantField) { + var cf = (ConstantField)f; + AddWellformednessCheck(cf); + if (InVerificationScope(cf)) { + var etran = new ExpressionTranslator(this, predef, f.tok); + heightAntecedent = Bpl.Expr.Lt(Bpl.Expr.Literal(cf.EnclosingModule.CallGraph.GetSCCRepresentativePredecessorCount(cf)), etran.FunctionContextHeight()); + } + } + + var bvsTypeAxiom = new List(); + var bvsAllocationAxiom = new List(); + + // G + var tyvars = MkTyParamBinders(GetTypeParams(c), out var tyexprs); + bvsTypeAxiom.AddRange(tyvars); + bvsAllocationAxiom.AddRange(tyvars); + + if (f is ConstantField && f.IsStatic) { + AddStaticConstFieldAllocationAxiom(fieldDeclaration, f, c, tyexprs, bvsTypeAxiom, heightAntecedent, bvsAllocationAxiom); + } else { + // This is the typical case (that is, f is not a static const field) + + var hVar = BplBoundVar("$h", predef.HeapType, out var h); + var oVar = BplBoundVar("$o", TrType(Resolver.GetThisType(c.tok, c)), out var o); + + // TClassA(G) + Bpl.Expr o_ty = ClassTyCon(c, tyexprs); + + var isGoodHeap = FunctionCall(c.tok, BuiltinFunction.IsGoodHeap, null, h); + Bpl.Expr isalloc_o; + if (!(c is ClassDecl)) { + var udt = UserDefinedType.FromTopLevelDecl(c.tok, c); + isalloc_o = MkIsAlloc(o, udt, h); + } else if (RevealedInScope(c)) { + isalloc_o = IsAlloced(c.tok, h, o); + } else { + // c is only provided, not revealed, in the scope. Use the non-null type decl's internal synonym + var cl = (ClassDecl)c; + Contract.Assert(cl.NonNullTypeDecl != null); + var udt = UserDefinedType.FromTopLevelDecl(c.tok, cl.NonNullTypeDecl); + isalloc_o = MkIsAlloc(o, udt, h); + } + + Bpl.Expr indexBounds = Bpl.Expr.True; + Bpl.Expr oDotF; + if (is_array) { + // generate h[o,Index(ii)] + bvsTypeAxiom.Add(hVar); bvsTypeAxiom.Add(oVar); + bvsAllocationAxiom.Add(hVar); bvsAllocationAxiom.Add(oVar); + + var ac = (ArrayClassDecl)c; + var ixs = new List(); + for (int i = 0; i < ac.Dims; i++) { + Bpl.Expr e; Bpl.Variable v = BplBoundVar("$i" + i, Bpl.Type.Int, out e); + ixs.Add(e); + bvsTypeAxiom.Add(v); + bvsAllocationAxiom.Add(v); + } + + oDotF = ReadHeap(c.tok, h, o, GetArrayIndexFieldName(c.tok, ixs)); + + for (int i = 0; i < ac.Dims; i++) { + // 0 <= i && i < _System.array.Length(o) + var e1 = Bpl.Expr.Le(Bpl.Expr.Literal(0), ixs[i]); + var ff = GetReadonlyField((Field)(ac.Members[i])); + var e2 = Bpl.Expr.Lt(ixs[i], new Bpl.NAryExpr(c.tok, new Bpl.FunctionCall(ff), new List { o })); + indexBounds = BplAnd(indexBounds, BplAnd(e1, e2)); + } + } else if (f.IsMutable) { + // generate h[o,f] + oDotF = ReadHeap(c.tok, h, o, new Bpl.IdentifierExpr(c.tok, GetField(f))); + bvsTypeAxiom.Add(hVar); bvsTypeAxiom.Add(oVar); + bvsAllocationAxiom.Add(hVar); bvsAllocationAxiom.Add(oVar); + } else { + // generate f(G,o) + var args = new List { o }; + if (f is ConstantField) { + args = Concat(tyexprs, args); + } + oDotF = new Bpl.NAryExpr(c.tok, new Bpl.FunctionCall(GetReadonlyField(f)), args); + bvsTypeAxiom.Add(oVar); + bvsAllocationAxiom.Add(hVar); bvsAllocationAxiom.Add(oVar); + } + + // antecedent: some subset of: $IsHeap(h) && o != null && $Is(o, TClassA(G)) && indexBounds + Bpl.Expr ante = Bpl.Expr.True; + if (is_array || f.IsMutable) { + ante = BplAnd(ante, isGoodHeap); + // Note: for the allocation axiom, isGoodHeap is added back in for !f.IsMutable below + } + if (!(f is ConstantField)) { + Bpl.Expr is_o = BplAnd( + ReceiverNotNull(o), + c is TraitDecl ? MkIs(o, o_ty) : DType(o, o_ty)); // $Is(o, ..) or dtype(o) == o_ty + ante = BplAnd(ante, is_o); + } + ante = BplAnd(ante, indexBounds); + + // trigger + var t_es = new List(); + t_es.Add(oDotF); + if (tyvars.Count > 0 && (is_array || !(f is ConstantField))) { + t_es.Add(o_ty); + } + var tr = new Bpl.Trigger(c.tok, true, t_es); + + // Now for the conclusion of the axioms + Bpl.Expr is_hf, isalloc_hf = null; + if (is_array) { + is_hf = MkIs(oDotF, tyexprs[0], true); + if (CommonHeapUse || NonGhostsUseHeap) { + isalloc_hf = MkIsAlloc(oDotF, tyexprs[0], h, true); + } + } else { + is_hf = MkIs(oDotF, f.Type); // $Is(h[o, f], ..) + if (CommonHeapUse || (NonGhostsUseHeap && !f.IsGhost)) { + isalloc_hf = MkIsAlloc(oDotF, f.Type, h); // $IsAlloc(h[o, f], ..) + } + } + + Bpl.Expr ax = BplForall(bvsTypeAxiom, tr, BplImp(ante, is_hf)); + AddIncludeDepAxiom(new Bpl.Axiom(c.tok, BplImp(heightAntecedent, ax), string.Format("{0}.{1}: Type axiom", c, f))); + + if (isalloc_hf != null) { + if (!is_array && !f.IsMutable) { + // isGoodHeap wasn't added above, so add it now + ante = BplAnd(isGoodHeap, ante); + } + ante = BplAnd(ante, isalloc_o); + + // compute a different trigger + t_es = new List(); + t_es.Add(oDotF); + if (!is_array && !f.IsMutable) { + // since "h" is not part of oDotF, we add a separate term that mentions "h" + t_es.Add(isalloc_o); + } + if (!(f is ConstantField) && tyvars.Count > 0) { + t_es.Add(o_ty); + } + tr = new Bpl.Trigger(c.tok, true, t_es); + + ax = BplForall(bvsAllocationAxiom, tr, BplImp(ante, isalloc_hf)); + AddIncludeDepAxiom(new Boogie.Axiom(c.tok, BplImp(heightAntecedent, ax), $"{c}.{f}: Allocation axiom")); + } + } + } + + private void AddStaticConstFieldAllocationAxiom(Boogie.Declaration fieldDeclaration, Field f, TopLevelDeclWithMembers c, + List tyexprs, List bvsTypeAxiom, Expr heightAntecedent, List bvsAllocationAxiom) { + var oDotF = new Boogie.NAryExpr(c.tok, new Boogie.FunctionCall(GetReadonlyField(f)), tyexprs); + var is_hf = MkIs(oDotF, f.Type); // $Is(h[o, f], ..) + Boogie.Expr ax = bvsTypeAxiom.Count == 0 ? is_hf : BplForall(bvsTypeAxiom, BplTrigger(oDotF), is_hf); + var isAxiom = new Boogie.Axiom(c.tok, BplImp(heightAntecedent, ax), $"{c}.{f}: Type axiom"); + AddOtherDefinition(fieldDeclaration, isAxiom); + + if (CommonHeapUse || (NonGhostsUseHeap && !f.IsGhost)) { + Boogie.Expr h; + var hVar = BplBoundVar("$h", predef.HeapType, out h); + bvsAllocationAxiom.Add(hVar); + var isGoodHeap = FunctionCall(c.tok, BuiltinFunction.IsGoodHeap, null, h); + var isalloc_hf = MkIsAlloc(oDotF, f.Type, h); // $IsAlloc(h[o, f], ..) + ax = BplForall(bvsAllocationAxiom, BplTrigger(isalloc_hf), BplImp(isGoodHeap, isalloc_hf)); + var isAllocAxiom = new Boogie.Axiom(c.tok, BplImp(heightAntecedent, ax), $"{c}.{f}: Allocation axiom"); + sink.AddTopLevelDeclaration(isAllocAxiom); + } + } + + private void AddImplementsAxioms(ClassDecl c) { + //this adds: axiom implements$J(class.C, typeInstantiations); + var vars = MkTyParamBinders(GetTypeParams(c), out var tyexprs); + + foreach (var parent in ((ClassDecl)c).ParentTraits) { + var trait = (TraitDecl)((NonNullTypeDecl)((UserDefinedType)parent).ResolvedClass).ViewAsClass; + var arg = ClassTyCon(c, tyexprs); + var args = new List { arg }; + foreach (var targ in parent.TypeArgs) { + args.Add(TypeToTy(targ)); + } + var expr = FunctionCall(c.tok, "implements$" + trait.FullSanitizedName, Bpl.Type.Bool, args); + var implements_axiom = new Bpl.Axiom(c.tok, BplForall(vars, null, expr)); + AddIncludeDepAxiom(implements_axiom); + } + } + + private void AddClassMember_Function(Function f) { + Contract.Ensures(currentModule == null && codeContext == null); + Contract.Ensures(currentModule == null && codeContext == null); + + currentModule = f.EnclosingClass.EnclosingModuleDefinition; + codeContext = f; + + // declare function + var boogieFunction = AddFunction(f); + // add synonym axiom + if (f.IsFuelAware()) { + AddLayerSynonymAxiom(f); + AddFuelSynonymAxiom(f); + } + // add frame axiom + if (AlwaysUseHeap || f.ReadsHeap) { + AddFrameAxiom(f); + } + // add consequence axiom + AddFunctionConsequenceAxiom(boogieFunction, f, f.Ens); + // add definition axioms, suitably specialized for literals + if (f.Body != null && RevealedInScope(f)) { + AddFunctionAxiom(boogieFunction, f, f.Body.Resolved); + } else { + // for body-less functions, at least generate its #requires function + var b = GetFunctionAxiom(f, null, null); + Contract.Assert(b == null); + } + // for a function in a class C that overrides a function in a trait J, add an axiom that connects J.F and C.F + if (f.OverriddenFunction != null) { + sink.AddTopLevelDeclaration(FunctionOverrideAxiom(f.OverriddenFunction, f)); + } + + // supply the connection between least/greatest predicates and prefix predicates + if (f is ExtremePredicate) { + AddPrefixPredicateAxioms(((ExtremePredicate)f).PrefixPredicate); + } + + Reset(); + } + + private void AddMethodImpl(Method m, Boogie.Procedure proc, bool wellformednessProc) { + Contract.Requires(m != null); + Contract.Requires(proc != null); + Contract.Requires(sink != null && predef != null); + Contract.Requires(wellformednessProc || m.Body != null); + Contract.Requires(currentModule == null && codeContext == null && _tmpIEs.Count == 0 && isAllocContext == null); + Contract.Ensures(currentModule == null && codeContext == null && _tmpIEs.Count == 0 && isAllocContext == null); + + currentModule = m.EnclosingClass.EnclosingModuleDefinition; + codeContext = m; + isAllocContext = new IsAllocContext(m.IsGhost); + + List inParams = Boogie.Formal.StripWhereClauses(proc.InParams); + List outParams = Boogie.Formal.StripWhereClauses(proc.OutParams); + + BoogieStmtListBuilder builder = new BoogieStmtListBuilder(this); + builder.Add(new CommentCmd("AddMethodImpl: " + m + ", " + proc)); + var etran = new ExpressionTranslator(this, predef, m.tok); + InitializeFuelConstant(m.tok, builder, etran); + var localVariables = new List(); + GenerateImplPrelude(m, wellformednessProc, inParams, outParams, builder, localVariables); + + if (UseOptimizationInZ3) { + // We ask Z3 to minimize all parameters of type 'nat'. + foreach (var f in m.Ins) { + var udt = f.Type.NormalizeExpandKeepConstraints() as UserDefinedType; + if (udt != null && udt.Name == "nat") { + builder.Add(optimizeExpr(true, new IdentifierExpr(f.tok, f), f.Tok, etran)); + } + } + } + + Boogie.StmtList stmts; + if (!wellformednessProc) { + var inductionVars = ApplyInduction(m.Ins, m.Attributes); + if (inductionVars.Count != 0) { + // Let the parameters be this,x,y of the method M and suppose ApplyInduction returns y. + // Also, let Pre be the precondition and VF be the decreases clause. + // Then, insert into the method body what amounts to: + // assume case-analysis-on-parameter[[ y' ]]; + // forall (y' | Pre(this, x, y') && VF(this, x, y') << VF(this, x, y)) { + // this.M(x, y'); + // } + // Generate bound variables for the forall statement, and a substitution for the Pre and VF + + // assume case-analysis-on-parameter[[ y' ]]; + foreach (var inFormal in m.Ins) { + var dt = inFormal.Type.AsDatatype; + if (dt != null) { + var funcID = new Boogie.FunctionCall(new Boogie.IdentifierExpr(inFormal.tok, "$IsA#" + dt.FullSanitizedName, Boogie.Type.Bool)); + var f = new Boogie.IdentifierExpr(inFormal.tok, inFormal.AssignUniqueName(m.IdGenerator), TrType(inFormal.Type)); + builder.Add(TrAssumeCmd(inFormal.tok, new Boogie.NAryExpr(inFormal.tok, funcID, new List { f }))); + } + } + + var parBoundVars = new List(); + var parBounds = new List(); + var substMap = new Dictionary(); + Expression receiverSubst = null; + foreach (var iv in inductionVars) { + BoundVar bv; + if (iv == null) { + // this corresponds to "this" + Contract.Assert(!m.IsStatic); // if "m" is static, "this" should never have gone into the _induction attribute + Contract.Assert(receiverSubst == null); // we expect at most one + var receiverType = Resolver.GetThisType(m.tok, (TopLevelDeclWithMembers)m.EnclosingClass); + bv = new BoundVar(m.tok, CurrentIdGenerator.FreshId("$ih#this"), receiverType); // use this temporary variable counter, but for a Dafny name (the idea being that the number and the initial "_" in the name might avoid name conflicts) + var ie = new IdentifierExpr(m.tok, bv.Name); + ie.Var = bv; // resolve here + ie.Type = bv.Type; // resolve here + receiverSubst = ie; + } else { + IdentifierExpr ie; + CloneVariableAsBoundVar(iv.tok, iv, "$ih#" + iv.Name, out bv, out ie); + substMap.Add(iv, ie); + } + parBoundVars.Add(bv); + parBounds.Add(new ComprehensionExpr.SpecialAllocIndependenceAllocatedBoundedPool()); // record that we don't want alloc antecedents for these variables + } + + // Generate a CallStmt for the recursive call + Expression recursiveCallReceiver; + List recursiveCallArgs; + RecursiveCallParameters(m.tok, m, m.TypeArgs, m.Ins, substMap, out recursiveCallReceiver, out recursiveCallArgs); + var methodSel = new MemberSelectExpr(m.tok, recursiveCallReceiver, m.Name); + methodSel.Member = m; // resolve here + methodSel.TypeApplication_AtEnclosingClass = m.EnclosingClass.TypeArgs.ConvertAll(tp => (Type)new UserDefinedType(tp.tok, tp)); + methodSel.TypeApplication_JustMember = m.TypeArgs.ConvertAll(tp => (Type)new UserDefinedType(tp.tok, tp)); + methodSel.Type = new InferredTypeProxy(); + var recursiveCall = new CallStmt(m.tok, m.tok, new List(), methodSel, recursiveCallArgs); + recursiveCall.IsGhost = m.IsGhost; // resolve here + + Expression parRange = new LiteralExpr(m.tok, true); + parRange.Type = Type.Bool; // resolve here + foreach (var pre in m.Req) { + parRange = Expression.CreateAnd(parRange, Substitute(pre.E, receiverSubst, substMap)); + } + // construct an expression (generator) for: VF' << VF + ExpressionConverter decrCheck = delegate (Dictionary decrSubstMap, ExpressionTranslator exprTran) { + var decrToks = new List(); + var decrTypes = new List(); + var decrCallee = new List(); + var decrCaller = new List(); + foreach (var ee in m.Decreases.Expressions) { + decrToks.Add(ee.tok); + decrTypes.Add(ee.Type.NormalizeExpand()); + decrCaller.Add(exprTran.TrExpr(ee)); + Expression es = Substitute(ee, receiverSubst, substMap); + es = Substitute(es, null, decrSubstMap); + decrCallee.Add(exprTran.TrExpr(es)); + } + return DecreasesCheck(decrToks, decrTypes, decrTypes, decrCallee, decrCaller, null, null, false, true); + }; + +#if VERIFY_CORRECTNESS_OF_TRANSLATION_FORALL_STATEMENT_RANGE + var definedness = new BoogieStmtListBuilder(this); + var exporter = new BoogieStmtListBuilder(this); + TrForallStmtCall(m.tok, parBoundVars, parRange, decrCheck, null, recursiveCall, definedness, exporter, localVariables, etran); + // All done, so put the two pieces together + builder.Add(new Bpl.IfCmd(m.tok, null, definedness.Collect(m.tok), null, exporter.Collect(m.tok))); +#else + TrForallStmtCall(m.tok, parBoundVars, parBounds, parRange, decrCheck, null, recursiveCall, null, builder, localVariables, etran); +#endif + } + // translate the body of the method + Contract.Assert(m.Body != null); // follows from method precondition and the if guard + + // $_reverifyPost := false; + builder.Add(Boogie.Cmd.SimpleAssign(m.tok, new Boogie.IdentifierExpr(m.tok, "$_reverifyPost", Boogie.Type.Bool), Boogie.Expr.False)); + // register output parameters with definite-assignment trackers + Contract.Assert(definiteAssignmentTrackers.Count == 0); + m.Outs.Iter(p => AddExistingDefiniteAssignmentTracker(p, m.IsGhost)); + // translate the body + TrStmt(m.Body, builder, localVariables, etran); + m.Outs.Iter(p => CheckDefiniteAssignmentReturn(m.BodyEndTok, p, builder)); + stmts = builder.Collect(m.Body.Tok); + // tear down definite-assignment trackers + m.Outs.Iter(RemoveDefiniteAssignmentTracker); + Contract.Assert(definiteAssignmentTrackers.Count == 0); + } else { + // check well-formedness of any default-value expressions (before assuming preconditions) + foreach (var formal in m.Ins.Where(formal => formal.DefaultValue != null)) { + var e = formal.DefaultValue; + CheckWellformed(e, new WFOptions(null, false, false, true), localVariables, builder, etran); + builder.Add(new Boogie.AssumeCmd(e.tok, CanCallAssumption(e, etran))); + CheckSubrange(e.tok, etran.TrExpr(e), e.Type, formal.Type, builder); + + if (formal.IsOld) { + Boogie.Expr wh = GetWhereClause(e.tok, etran.TrExpr(e), e.Type, etran.Old, ISALLOC, true); + if (wh != null) { + builder.Add(Assert(e.tok, wh, "default value must be allocated in the two-state lemma's previous state")); + } + } + } + // check well-formedness of the preconditions, and then assume each one of them + foreach (AttributedExpression p in m.Req) { + CheckWellformedAndAssume(p.E, new WFOptions(), localVariables, builder, etran); + } + // check well-formedness of the modifies clauses + CheckFrameWellFormed(new WFOptions(), m.Mod.Expressions, localVariables, builder, etran); + // check well-formedness of the decreases clauses + foreach (Expression p in m.Decreases.Expressions) { + CheckWellformed(p, new WFOptions(), localVariables, builder, etran); + } + + if (!(m is TwoStateLemma)) { + // play havoc with the heap according to the modifies clause + builder.Add(new Boogie.HavocCmd(m.tok, new List { (Boogie.IdentifierExpr/*TODO: this cast is rather dubious*/)etran.HeapExpr })); + // assume the usual two-state boilerplate information + foreach (BoilerplateTriple tri in GetTwoStateBoilerplate(m.tok, m.Mod.Expressions, m.IsGhost, m.AllowsAllocation, etran.Old, etran, etran.Old)) { + if (tri.IsFree) { + builder.Add(TrAssumeCmd(m.tok, tri.Expr)); + } + } + } + + // also play havoc with the out parameters + if (outParams.Count != 0) { // don't create an empty havoc statement + List outH = new List(); + foreach (Boogie.Variable b in outParams) { + Contract.Assert(b != null); + outH.Add(new Boogie.IdentifierExpr(b.tok, b)); + } + builder.Add(new Boogie.HavocCmd(m.tok, outH)); + } + // mark the end of the modifles/out-parameter havocking with a CaptureState; make its location be the first ensures clause, if any (and just + // omit the CaptureState if there's no ensures clause) + if (m.Ens.Count != 0) { + builder.AddCaptureState(m.Ens[0].E.tok, false, "post-state"); + } + + // check wellformedness of postconditions + foreach (AttributedExpression p in m.Ens) { + CheckWellformedAndAssume(p.E, new WFOptions(), localVariables, builder, etran); + } + + stmts = builder.Collect(m.tok); + } + + if (EmitImplementation(m.Attributes)) { + // emit impl only when there are proof obligations. + QKeyValue kv = etran.TrAttributes(m.Attributes, null); + Boogie.Implementation impl = new Boogie.Implementation(m.tok, proc.Name, + new List(), inParams, outParams, + localVariables, stmts, kv); + sink.AddTopLevelDeclaration(impl); + + if (InsertChecksums) { + InsertChecksum(m, impl); + } + } + + isAllocContext = null; + Reset(); + } + + private void AddMethodOverrideCheckImpl(Method m, Boogie.Procedure proc) { + Contract.Requires(m != null); + Contract.Requires(proc != null); + Contract.Requires(sink != null && predef != null); + Contract.Requires(m.OverriddenMethod != null); + Contract.Requires(m.Ins.Count == m.OverriddenMethod.Ins.Count); + Contract.Requires(m.Outs.Count == m.OverriddenMethod.Outs.Count); + //Contract.Requires(wellformednessProc || m.Body != null); + Contract.Requires(currentModule == null && codeContext == null && _tmpIEs.Count == 0 && isAllocContext == null); + Contract.Ensures(currentModule == null && codeContext == null && _tmpIEs.Count == 0 && isAllocContext == null); + + currentModule = m.EnclosingClass.EnclosingModuleDefinition; + codeContext = m; + isAllocContext = new IsAllocContext(m.IsGhost); + + List inParams = Boogie.Formal.StripWhereClauses(proc.InParams); + List outParams = Boogie.Formal.StripWhereClauses(proc.OutParams); + + var builder = new BoogieStmtListBuilder(this); + var etran = new ExpressionTranslator(this, predef, m.tok); + var localVariables = new List(); + + // assume traitTypeParameter == G(overrideTypeParameters); + AddOverrideCheckTypeArgumentInstantiations(m, builder, localVariables); + + if (m is TwoStateLemma) { + // $Heap := current$Heap; + var heap = (Boogie.IdentifierExpr /*TODO: this cast is somewhat dubious*/)new ExpressionTranslator(this, predef, m.tok).HeapExpr; + builder.Add(Boogie.Cmd.SimpleAssign(m.tok, heap, new Boogie.IdentifierExpr(m.tok, "current$Heap", predef.HeapType))); + } + + + var substMap = new Dictionary(); + for (int i = 0; i < m.Ins.Count; i++) { + //get corresponsing formal in the class + var ie = new IdentifierExpr(m.Ins[i].tok, m.Ins[i].AssignUniqueName(m.IdGenerator)); + ie.Var = m.Ins[i]; ie.Type = ie.Var.Type; + substMap.Add(m.OverriddenMethod.Ins[i], ie); + } + for (int i = 0; i < m.Outs.Count; i++) { + //get corresponsing formal in the class + var ie = new IdentifierExpr(m.Outs[i].tok, m.Outs[i].AssignUniqueName(m.IdGenerator)); + ie.Var = m.Outs[i]; ie.Type = ie.Var.Type; + substMap.Add(m.OverriddenMethod.Outs[i], ie); + } + + Boogie.StmtList stmts; + //adding assume Pre’; assert P; // this checks that Pre’ implies P + AddMethodOverrideReqsChk(m, builder, etran, substMap); + + //adding assert R <= Rank’; + AddOverrideTerminationChk(m, m.OverriddenMethod, builder, etran, substMap); + + //adding assert W <= Frame’ + AddMethodOverrideSubsetChk(m, builder, etran, localVariables, substMap); + + if (!(m is TwoStateLemma)) { + //change the heap at locations W + HavocMethodFrameLocations(m, builder, etran, localVariables); + } + + //adding assume Q; assert Post’; + AddMethodOverrideEnsChk(m, builder, etran, substMap); + + stmts = builder.Collect(m.tok); + + if (EmitImplementation(m.Attributes)) { + // emit the impl only when there are proof obligations. + QKeyValue kv = etran.TrAttributes(m.Attributes, null); + Boogie.Implementation impl = new Boogie.Implementation(m.tok, proc.Name, new List(), inParams, outParams, localVariables, stmts, kv); + sink.AddTopLevelDeclaration(impl); + + if (InsertChecksums) { + InsertChecksum(m, impl); + } + } + + isAllocContext = null; + Reset(); + } + + private void AddFunctionOverrideCheckImpl(Function f) { + Contract.Requires(f != null); + Contract.Requires(f.EnclosingClass is TopLevelDeclWithMembers); + Contract.Requires(sink != null && predef != null); + Contract.Requires(f.OverriddenFunction != null); + Contract.Requires(f.Formals.Count == f.OverriddenFunction.Formals.Count); + Contract.Requires(currentModule == null && codeContext == null && _tmpIEs.Count == 0 && isAllocContext != null); + Contract.Ensures(currentModule == null && codeContext == null && _tmpIEs.Count == 0 && isAllocContext != null); + + #region first procedure, no impl yet + //Function nf = new Function(f.tok, "OverrideCheck_" + f.Name, f.IsStatic, f.IsGhost, f.TypeArgs, f.OpenParen, f.Formals, f.ResultType, f.Req, f.Reads, f.Ens, f.Decreases, f.Body, f.Attributes, f.SignatureEllipsis); + //AddFunction(f); + currentModule = f.EnclosingClass.EnclosingModuleDefinition; + codeContext = f; + + Boogie.Expr prevHeap = null; + Boogie.Expr currHeap = null; + var ordinaryEtran = new ExpressionTranslator(this, predef, f.tok); + ExpressionTranslator etran; + var inParams_Heap = new List(); + if (f is TwoStateFunction) { + var prevHeapVar = new Boogie.Formal(f.tok, new Boogie.TypedIdent(f.tok, "previous$Heap", predef.HeapType), true); + inParams_Heap.Add(prevHeapVar); + prevHeap = new Boogie.IdentifierExpr(f.tok, prevHeapVar); + if (f.ReadsHeap) { + var currHeapVar = new Boogie.Formal(f.tok, new Boogie.TypedIdent(f.tok, "current$Heap", predef.HeapType), true); + inParams_Heap.Add(currHeapVar); + currHeap = new Boogie.IdentifierExpr(f.tok, currHeapVar); + } + etran = new ExpressionTranslator(this, predef, currHeap, prevHeap); + } else { + etran = ordinaryEtran; + } + + // parameters of the procedure + var typeInParams = MkTyParamFormals(GetTypeParams(f)); + var inParams = new List(); + var outParams = new List(); + if (!f.IsStatic) { + var th = new Boogie.IdentifierExpr(f.tok, "this", TrReceiverType(f)); + Boogie.Expr wh = Boogie.Expr.And( + ReceiverNotNull(th), + etran.GoodRef(f.tok, th, Resolver.GetReceiverType(f.tok, f))); + Boogie.Formal thVar = new Boogie.Formal(f.tok, new Boogie.TypedIdent(f.tok, "this", TrReceiverType(f), wh), true); + inParams.Add(thVar); + } + foreach (Formal p in f.Formals) { + Boogie.Type varType = TrType(p.Type); + Boogie.Expr wh = GetWhereClause(p.tok, new Boogie.IdentifierExpr(p.tok, p.AssignUniqueName(f.IdGenerator), varType), p.Type, etran, NOALLOC); + inParams.Add(new Boogie.Formal(p.tok, new Boogie.TypedIdent(p.tok, p.AssignUniqueName(f.IdGenerator), varType, wh), true)); + } + + Formal pOut = null; + if (f.Result != null || f.OverriddenFunction.Result != null) { + if (f.Result != null) { + pOut = f.Result; + Contract.Assert(!pOut.IsOld); + } else { + var pp = f.OverriddenFunction.Result; + Contract.Assert(!pp.IsOld); + pOut = new Formal(pp.tok, pp.Name, f.ResultType, false, pp.IsGhost, null); + } + var varType = TrType(pOut.Type); + var wh = GetWhereClause(pOut.tok, new Boogie.IdentifierExpr(pOut.tok, pOut.AssignUniqueName(f.IdGenerator), varType), pOut.Type, etran, NOALLOC); + outParams.Add(new Boogie.Formal(pOut.tok, new Boogie.TypedIdent(pOut.tok, pOut.AssignUniqueName(f.IdGenerator), varType, wh), true)); + } + // the procedure itself + var req = new List(); + // free requires mh == ModuleContextHeight && fh == FunctionContextHeight; + req.Add(Requires(f.tok, true, etran.HeightContext(f.OverriddenFunction), null, null)); + if (f is TwoStateFunction) { + // free requires prevHeap == Heap && HeapSucc(prevHeap, currHeap) && IsHeap(currHeap) + var a0 = Boogie.Expr.Eq(prevHeap, ordinaryEtran.HeapExpr); + var a1 = HeapSucc(prevHeap, currHeap); + var a2 = FunctionCall(f.tok, BuiltinFunction.IsGoodHeap, null, currHeap); + req.Add(Requires(f.tok, true, BplAnd(a0, BplAnd(a1, a2)), null, null)); + } + // modifies $Heap, $Tick + var mod = new List { + (Boogie.IdentifierExpr/*TODO: this cast is rather dubious*/)ordinaryEtran.HeapExpr, + etran.Tick() + }; + var ens = new List(); + + var proc = new Boogie.Procedure(f.tok, "OverrideCheck$$" + f.FullSanitizedName, new List(), + Util.Concat(Util.Concat(typeInParams, inParams_Heap), inParams), outParams, + req, mod, ens, etran.TrAttributes(f.Attributes, null)); + sink.AddTopLevelDeclaration(proc); + var implInParams = Boogie.Formal.StripWhereClauses(inParams); + var implOutParams = Boogie.Formal.StripWhereClauses(outParams); + + #endregion + + //List outParams = Bpl.Formal.StripWhereClauses(proc.OutParams); + + BoogieStmtListBuilder builder = new BoogieStmtListBuilder(this); + List localVariables = new List(); + + // assume traitTypeParameter == G(overrideTypeParameters); + AddOverrideCheckTypeArgumentInstantiations(f, builder, localVariables); + + if (f is TwoStateFunction) { + // $Heap := current$Heap; + var heap = (Boogie.IdentifierExpr /*TODO: this cast is somewhat dubious*/)ordinaryEtran.HeapExpr; + builder.Add(Boogie.Cmd.SimpleAssign(f.tok, heap, etran.HeapExpr)); + etran = ordinaryEtran; // we no longer need the special heap names + } + + var substMap = new Dictionary(); + for (int i = 0; i < f.Formals.Count; i++) { + //get corresponsing formal in the class + var ie = new IdentifierExpr(f.Formals[i].tok, f.Formals[i].AssignUniqueName(f.IdGenerator)); + ie.Var = f.Formals[i]; ie.Type = ie.Var.Type; + substMap.Add(f.OverriddenFunction.Formals[i], ie); + } + + if (f.OverriddenFunction.Result != null) { + Contract.Assert(pOut != null); + //get corresponsing formal in the class + var ie = new IdentifierExpr(pOut.tok, pOut.AssignUniqueName(f.IdGenerator)); + ie.Var = pOut; ie.Type = ie.Var.Type; + substMap.Add(f.OverriddenFunction.Result, ie); + } + + //adding assume Pre’; assert P; // this checks that Pre’ implies P + AddFunctionOverrideReqsChk(f, builder, etran, substMap); + + //adding assert R <= Rank’; + AddOverrideTerminationChk(f, f.OverriddenFunction, builder, etran, substMap); + + //adding assert W <= Frame’ + AddFunctionOverrideSubsetChk(f, builder, etran, localVariables, substMap); + + //adding assume Q; assert Post’; + //adding assume J.F(ins) == C.F(ins); + AddFunctionOverrideEnsChk(f, builder, etran, substMap, implInParams, implOutParams.Count == 0 ? null : implOutParams[0]); + + var stmts = builder.Collect(f.tok); + + if (EmitImplementation(f.Attributes)) { + // emit the impl only when there are proof obligations. + QKeyValue kv = etran.TrAttributes(f.Attributes, null); + + var impl = new Boogie.Implementation(f.tok, proc.Name, new List(), + Util.Concat(Util.Concat(typeInParams, inParams_Heap), implInParams), implOutParams, localVariables, stmts, kv); + sink.AddTopLevelDeclaration(impl); + } + + if (InsertChecksums) { + InsertChecksum(f, proc, true); + } + + Reset(); + } + + private void AddOverrideCheckTypeArgumentInstantiations(MemberDecl member, BoogieStmtListBuilder builder, List localVariables) { + Contract.Requires(member is Function || member is Method); + Contract.Requires(member.EnclosingClass is TopLevelDeclWithMembers); + Contract.Requires(builder != null); + Contract.Requires(localVariables != null); + + MemberDecl overriddenMember; + List overriddenTypeParameters; + if (member is Function) { + var o = ((Function)member).OverriddenFunction; + overriddenMember = o; + overriddenTypeParameters = o.TypeArgs; + } else { + var o = ((Method)member).OverriddenMethod; + overriddenMember = o; + overriddenTypeParameters = o.TypeArgs; + } + var typeMap = GetTypeArgumentSubstitutionMap(overriddenMember, member); + foreach (var tp in Util.Concat(overriddenMember.EnclosingClass.TypeArgs, overriddenTypeParameters)) { + var local = BplLocalVar(nameTypeParam(tp), predef.Ty, out var lhs); + localVariables.Add(local); + var rhs = TypeToTy(typeMap[tp]); + builder.Add(new Boogie.AssumeCmd(tp.tok, Boogie.Expr.Eq(lhs, rhs))); + } + } + + /// + /// Essentially, the function override axiom looks like: + /// axiom (forall $heap: HeapType, typeArgs: Ty, this: ref, x#0: int, fuel: LayerType :: + /// { J.F(fuel, $heap, G(typeArgs), this, x#0), C.F(fuel, $heap, typeArgs, this, x#0) } + /// { J.F(fuel, $heap, G(typeArgs), this, x#0), $Is(this, C) } + /// this != null && $Is(this, C) + /// ==> + /// J.F(fuel, $heap, G(typeArgs), this, x#0) == C.F(fuel, $heap, typeArgs, this, x#0)); + /// (without the other usual antecedents). Essentially, the override gives a part of the body of the + /// trait's function, so we call FunctionAxiom to generate a conditional axiom (that is, we pass in the "overridingFunction" + /// parameter to FunctionAxiom, which will add 'dtype(this) == class.C' as an additional antecedent) for a + /// body of 'C.F(this, x#0)'. + /// + private Boogie.Axiom FunctionOverrideAxiom(Function f, Function overridingFunction) { + Contract.Requires(f != null); + Contract.Requires(overridingFunction != null); + Contract.Requires(predef != null); + Contract.Requires(f.EnclosingClass != null); + Contract.Requires(!f.IsStatic); + Contract.Requires(overridingFunction.EnclosingClass is TopLevelDeclWithMembers); + Contract.Ensures(Contract.Result() != null); + + bool readsHeap = AlwaysUseHeap || f.ReadsHeap || overridingFunction.ReadsHeap; + + ExpressionTranslator etran; + Boogie.BoundVariable bvPrevHeap = null; + if (f is TwoStateFunction) { + bvPrevHeap = new Boogie.BoundVariable(f.tok, new Boogie.TypedIdent(f.tok, "$prevHeap", predef.HeapType)); + etran = new ExpressionTranslator(this, predef, + f.ReadsHeap ? new Boogie.IdentifierExpr(f.tok, predef.HeapVarName, predef.HeapType) : null, + new Boogie.IdentifierExpr(f.tok, bvPrevHeap)); + } else if (readsHeap) { + etran = new ExpressionTranslator(this, predef, f.tok); + } else { + etran = new ExpressionTranslator(this, predef, (Boogie.Expr)null); + } + + // "forallFormals" is built to hold the bound variables of the quantification + // argsJF are the arguments to J.F (the function in the trait) + // argsCF are the arguments to C.F (the overriding function) + var forallFormals = new List(); + var argsJF = new List(); + var argsCF = new List(); + + // Add type arguments + forallFormals.AddRange(MkTyParamBinders(GetTypeParams(overridingFunction), out _)); + argsJF.AddRange(GetTypeArguments(f, overridingFunction).ConvertAll(TypeToTy)); + argsCF.AddRange(GetTypeArguments(overridingFunction, null).ConvertAll(TypeToTy)); + + // Add the fuel argument + if (f.IsFuelAware()) { + Contract.Assert(overridingFunction.IsFuelAware()); // f.IsFuelAware() ==> overridingFunction.IsFuelAware() + var fuel = new Boogie.BoundVariable(f.tok, new Boogie.TypedIdent(f.tok, "$fuel", predef.LayerType)); + forallFormals.Add(fuel); + var ly = new Boogie.IdentifierExpr(f.tok, fuel); + argsJF.Add(ly); + argsCF.Add(ly); + } else if (overridingFunction.IsFuelAware()) { + // We can't use a bound variable $fuel, because then one of the triggers won't be mentioning this $fuel. + // Instead, we do the next best thing: use the literal $LZ. + var ly = new Boogie.IdentifierExpr(f.tok, "$LZ", predef.LayerType); // $LZ + argsCF.Add(ly); + } + + // Add heap arguments + if (f is TwoStateFunction) { + Contract.Assert(bvPrevHeap != null); + forallFormals.Add(bvPrevHeap); + argsJF.Add(etran.Old.HeapExpr); + argsCF.Add(etran.Old.HeapExpr); + } + if (AlwaysUseHeap || f.ReadsHeap || overridingFunction.ReadsHeap) { + var heap = new Boogie.BoundVariable(f.tok, new Boogie.TypedIdent(f.tok, predef.HeapVarName, predef.HeapType)); + forallFormals.Add(heap); + if (AlwaysUseHeap || f.ReadsHeap) { + argsJF.Add(new Boogie.IdentifierExpr(f.tok, heap)); + } + if (AlwaysUseHeap || overridingFunction.ReadsHeap) { + argsCF.Add(new Boogie.IdentifierExpr(overridingFunction.tok, heap)); + } + } + + // Add receiver parameter + Type thisType = Resolver.GetReceiverType(f.tok, overridingFunction); + var bvThis = new Boogie.BoundVariable(f.tok, new Boogie.TypedIdent(f.tok, etran.This, TrType(thisType))); + forallFormals.Add(bvThis); + var bvThisExpr = new Boogie.IdentifierExpr(f.tok, bvThis); + argsJF.Add(bvThisExpr); + argsCF.Add(bvThisExpr); + // $Is(this, C) + var isOfSubtype = GetWhereClause(overridingFunction.tok, bvThisExpr, thisType, f is TwoStateFunction ? etran.Old : etran, IsAllocType.NEVERALLOC); + + // Add other arguments + var typeMap = GetTypeArgumentSubstitutionMap(f, overridingFunction); + foreach (Formal p in f.Formals) { + var pType = Resolver.SubstType(p.Type, typeMap); + var bv = new Boogie.BoundVariable(p.tok, new Boogie.TypedIdent(p.tok, p.AssignUniqueName(currentDeclaration.IdGenerator), TrType(pType))); + forallFormals.Add(bv); + var jfArg = new Boogie.IdentifierExpr(p.tok, bv); + argsJF.Add(ModeledAsBoxType(p.Type) ? BoxIfUnboxed(jfArg, pType) : jfArg); + argsCF.Add(new Boogie.IdentifierExpr(p.tok, bv)); + } + + // useViaContext: (mh != ModuleContextHeight || fh != FunctionContextHeight) + ModuleDefinition mod = f.EnclosingClass.EnclosingModuleDefinition; + Boogie.Expr useViaContext = !InVerificationScope(overridingFunction) ? (Boogie.Expr)Boogie.Expr.True : + Boogie.Expr.Neq(Boogie.Expr.Literal(mod.CallGraph.GetSCCRepresentativePredecessorCount(overridingFunction)), etran.FunctionContextHeight()); + + Boogie.Expr funcAppl; + { + var funcID = new Boogie.IdentifierExpr(f.tok, f.FullSanitizedName, TrType(f.ResultType)); + funcAppl = new Boogie.NAryExpr(f.tok, new Boogie.FunctionCall(funcID), argsJF); + } + Boogie.Expr overridingFuncAppl; + { + var funcID = new Boogie.IdentifierExpr(overridingFunction.tok, overridingFunction.FullSanitizedName, TrType(overridingFunction.ResultType)); + overridingFuncAppl = new Boogie.NAryExpr(overridingFunction.tok, new Boogie.FunctionCall(funcID), argsCF); + } + + // Build the triggers + // { f(Succ(s), args), f'(Succ(s), args') } + Boogie.Trigger tr = BplTriggerHeap(this, overridingFunction.tok, + funcAppl, + readsHeap ? etran.HeapExpr : null, + overridingFuncAppl); + // { f(Succ(s), args), $Is(this, T') } + var exprs = new List() { funcAppl, isOfSubtype }; + if (readsHeap) { + exprs.Add(FunctionCall(overridingFunction.tok, BuiltinFunction.IsGoodHeap, null, etran.HeapExpr)); + } + tr = new Boogie.Trigger(overridingFunction.tok, true, exprs, tr); + + // The equality that is what it's all about + var synonyms = Boogie.Expr.Eq( + funcAppl, + ModeledAsBoxType(f.ResultType) ? BoxIfUnboxed(overridingFuncAppl, overridingFunction.ResultType) : overridingFuncAppl); + + // The axiom + Boogie.Expr ax = BplForall(f.tok, new List(), forallFormals, null, tr, + Boogie.Expr.Imp(Boogie.Expr.And(ReceiverNotNull(bvThisExpr), isOfSubtype), synonyms)); + var activate = AxiomActivation(f, etran); + string comment = "override axiom for " + f.FullSanitizedName + " in class " + overridingFunction.EnclosingClass.FullSanitizedName; + return new Boogie.Axiom(f.tok, Boogie.Expr.Imp(activate, ax), comment); + } + + /// + /// Return a type-parameter substitution map for function "f", as instantiated by the context of "overridingFunction". + /// + /// In more symbols, suppose "f" is declared as follows: + /// class/trait Tr[A,B] { + /// function f[C,D](...): ... + /// } + /// and "overridingFunction" is declared as follows: + /// class/trait Cl[G] extends Tr[X(G),Y(G)] { + /// function f[R,S](...): ... + /// } + /// Then, return the following map: + /// A -> X(G) + /// B -> Y(G) + /// C -> R + /// D -> S + /// + /// See also GetTypeArguments. + /// + private static Dictionary GetTypeArgumentSubstitutionMap(MemberDecl member, MemberDecl overridingMember) { + Contract.Requires(member is Function || member is Method); + Contract.Requires(overridingMember is Function || overridingMember is Method); + Contract.Requires(overridingMember.EnclosingClass is TopLevelDeclWithMembers); + Contract.Requires(((ICallable)member).TypeArgs.Count == ((ICallable)overridingMember).TypeArgs.Count); + + var typeMap = new Dictionary(); + + var cl = (TopLevelDeclWithMembers)overridingMember.EnclosingClass; + var classTypeMap = cl.ParentFormalTypeParametersToActuals; + member.EnclosingClass.TypeArgs.ForEach(tp => typeMap.Add(tp, classTypeMap[tp])); + + var origTypeArgs = ((ICallable)member).TypeArgs; + var overridingTypeArgs = ((ICallable)overridingMember).TypeArgs; + for (var i = 0; i < origTypeArgs.Count; i++) { + var otp = overridingTypeArgs[i]; + typeMap.Add(origTypeArgs[i], new UserDefinedType(otp.tok, otp)); + } + + return typeMap; + } + + private void AddMethodOverrideEnsChk(Method m, BoogieStmtListBuilder builder, ExpressionTranslator etran, Dictionary substMap) { + Contract.Requires(m != null); + Contract.Requires(builder != null); + Contract.Requires(etran != null); + Contract.Requires(substMap != null); + //generating class post-conditions + foreach (var en in m.Ens) { + builder.Add(TrAssumeCmd(m.tok, etran.TrExpr(en.E))); + } + //generating trait post-conditions with class variables + foreach (var en in m.OverriddenMethod.Ens) { + Expression postcond = Substitute(en.E, null, substMap); + bool splitHappened; // we actually don't care + foreach (var s in TrSplitExpr(postcond, etran, false, out splitHappened)) { + if (s.IsChecked) { + builder.Add(Assert(m.tok, s.E, "the method must provide an equal or more detailed postcondition than in its parent trait")); + } + } + } + } + + private void AddMethodOverrideReqsChk(Method m, BoogieStmtListBuilder builder, ExpressionTranslator etran, Dictionary substMap) { + Contract.Requires(m != null); + Contract.Requires(builder != null); + Contract.Requires(etran != null); + Contract.Requires(substMap != null); + //generating trait pre-conditions with class variables + foreach (var req in m.OverriddenMethod.Req) { + Expression precond = Substitute(req.E, null, substMap); + builder.Add(TrAssumeCmd(m.tok, etran.TrExpr(precond))); + } + //generating class pre-conditions + foreach (var req in m.Req) { + bool splitHappened; // we actually don't care + foreach (var s in TrSplitExpr(req.E, etran, false, out splitHappened)) { + if (s.IsChecked) { + builder.Add(Assert(m.tok, s.E, "the method must provide an equal or more permissive precondition than in its parent trait")); + } + } + } + } + + private void AddOverrideTerminationChk(ICallable original, ICallable overryd, BoogieStmtListBuilder builder, ExpressionTranslator etran, Dictionary substMap) { + Contract.Requires(original != null); + Contract.Requires(overryd != null); + Contract.Requires(builder != null); + Contract.Requires(etran != null); + Contract.Requires(substMap != null); + // Note, it is as if the trait's method is calling the class's method. + var contextDecreases = overryd.Decreases.Expressions; + var calleeDecreases = original.Decreases.Expressions; + // We want to check: calleeDecreases <= contextDecreases (note, we can allow equality, since there is a bounded, namely 1, number of dynamic dispatches) + if (Contract.Exists(contextDecreases, e => e is WildcardExpr)) { + // no check needed + return; + } + + int N = Math.Min(contextDecreases.Count, calleeDecreases.Count); + var toks = new List(); + var types0 = new List(); + var types1 = new List(); + var callee = new List(); + var caller = new List(); + + for (int i = 0; i < N; i++) { + Expression e0 = calleeDecreases[i]; + Expression e1 = Substitute(contextDecreases[i], null, substMap); + if (!CompatibleDecreasesTypes(e0.Type, e1.Type)) { + N = i; + break; + } + toks.Add(new NestedToken(original.Tok, e1.tok)); + types0.Add(e0.Type.NormalizeExpand()); + types1.Add(e1.Type.NormalizeExpand()); + callee.Add(etran.TrExpr(e0)); + caller.Add(etran.TrExpr(e1)); + } + + var decrCountT = contextDecreases.Count; + var decrCountC = calleeDecreases.Count; + // Generally, we want to produce a check "decrClass <= decrTrait", allowing (the common case where) they are equal. + // * If N < decrCountC && N < decrCountT, then "decrClass <= decrTrait" if the comparison ever gets beyond the + // parts that survived truncation. Thus, we compare with "allowNoChange" set to "false". + // Otherwise: + // * If decrCountC == decrCountT, then the truncation we did above had no effect and we pass in "allowNoChange" as "true". + // * If decrCountC > decrCountT, then we will have truncated decrClass above. Let x,y and x' denote decrClass and + // decrTrait, respectively, where x and x' have the same length. Considering how Dafny in effect pads the end of + // decreases tuples with a \top, we were supposed to evaluate (x,(y,\top)) <= (x',\top), which by lexicographic pairs + // we can expand to: + // x <= x' && (x == x' ==> (y,\top) <= \top) + // which is equivalent to just x <= x'. Thus, we called DecreasesCheck to compare x and x' and we pass in "allowNoChange" + // as "true". + // * If decrCountC < decrCountT, then we will have truncated decrTrait above. Let x and x',y' denote decrClass and + // decrTrait, respectively, where x and x' have the same length. We then want to check (x,\top) <= (x',(y',\top)), which + // expands to: + // x <= x' && (x == x' ==> \top <= (y',\top)) + // = { \top is strictly larger than a pair } + // x <= x' && (x == x' ==> false) + // = + // x < x' + // So we perform our desired check by calling DecreasesCheck to strictly compare x and x', so we pass in "allowNoChange" + // as "false". + bool allowNoChange = N == decrCountT && decrCountT <= decrCountC; + var decrChk = DecreasesCheck(toks, types0, types1, callee, caller, null, null, allowNoChange, false); + builder.Add(Assert(original.Tok, decrChk, string.Format("{0}'s decreases clause must be below or equal to that in the trait", original.WhatKind))); + } + + private void AddMethodOverrideSubsetChk(Method m, BoogieStmtListBuilder builder, ExpressionTranslator etran, List localVariables, Dictionary substMap) { + //getting framePrime + List traitFrameExps = new List(); + List classFrameExps = m.Mod != null ? m.Mod.Expressions : new List(); + if (m.OverriddenMethod.Mod != null) { + foreach (var e in m.OverriddenMethod.Mod.Expressions) { + var newE = Substitute(e.E, null, substMap); + FrameExpression fe = new FrameExpression(e.tok, newE, e.FieldName); + traitFrameExps.Add(fe); + } + } + + QKeyValue kv = etran.TrAttributes(m.Attributes, null); + + IToken tok = m.tok; + // Declare a local variable $_Frame: [ref, Field alpha]bool + Boogie.IdentifierExpr traitFrame = etran.TheFrame(m.OverriddenMethod.tok); // this is a throw-away expression, used only to extract the type and name of the $_Frame variable + traitFrame.Name = m.EnclosingClass.Name + "_" + traitFrame.Name; + Contract.Assert(traitFrame.Type != null); // follows from the postcondition of TheFrame + Boogie.LocalVariable frame = new Boogie.LocalVariable(tok, new Boogie.TypedIdent(tok, null ?? traitFrame.Name, traitFrame.Type)); + localVariables.Add(frame); + // $_Frame := (lambda $o: ref, $f: Field alpha :: $o != null && $Heap[$o,alloc] ==> ($o,$f) in Modifies/Reads-Clause); + Boogie.TypeVariable alpha = new Boogie.TypeVariable(tok, "alpha"); + Boogie.BoundVariable oVar = new Boogie.BoundVariable(tok, new Boogie.TypedIdent(tok, "$o", predef.RefType)); + Boogie.IdentifierExpr o = new Boogie.IdentifierExpr(tok, oVar); + Boogie.BoundVariable fVar = new Boogie.BoundVariable(tok, new Boogie.TypedIdent(tok, "$f", predef.FieldName(tok, alpha))); + Boogie.IdentifierExpr f = new Boogie.IdentifierExpr(tok, fVar); + Boogie.Expr ante = Boogie.Expr.And(Boogie.Expr.Neq(o, predef.Null), etran.IsAlloced(tok, o)); + Boogie.Expr consequent = InRWClause(tok, o, f, traitFrameExps, etran, null, null); + Boogie.Expr lambda = new Boogie.LambdaExpr(tok, new List { alpha }, new List { oVar, fVar }, null, + Boogie.Expr.Imp(ante, consequent)); + + //to initialize $_Frame variable to Frame' + builder.Add(Boogie.Cmd.SimpleAssign(tok, new Boogie.IdentifierExpr(tok, frame), lambda)); + + // emit: assert (forall o: ref, f: Field alpha :: o != null && $Heap[o,alloc] && (o,f) in subFrame ==> $_Frame[o,f]); + Boogie.Expr oInCallee = InRWClause(tok, o, f, classFrameExps, etran, null, null); + Boogie.Expr consequent2 = InRWClause(tok, o, f, traitFrameExps, etran, null, null); + Boogie.Expr q = new Boogie.ForallExpr(tok, new List { alpha }, new List { oVar, fVar }, + Boogie.Expr.Imp(Boogie.Expr.And(ante, oInCallee), consequent2)); + builder.Add(Assert(tok, q, "expression may modify an object not in the parent trait context's modifies clause", kv)); + } + + /// + /// This method is expected to be called at most once for each parameter combination, and in particular + /// at most once for each value of "kind". + /// + private Boogie.Procedure AddMethod(Method m, MethodTranslationKind kind) { + Contract.Requires(m != null); + Contract.Requires(m.EnclosingClass != null); + Contract.Requires(predef != null); + Contract.Requires(currentModule == null && codeContext == null && isAllocContext == null); + Contract.Ensures(currentModule == null && codeContext == null && isAllocContext == null); + Contract.Ensures(Contract.Result() != null); + Contract.Assert(VisibleInScope(m)); + + currentModule = m.EnclosingClass.EnclosingModuleDefinition; + codeContext = m; + isAllocContext = new IsAllocContext(m.IsGhost); + + Boogie.Expr prevHeap = null; + Boogie.Expr currHeap = null; + var ordinaryEtran = new ExpressionTranslator(this, predef, m.tok); + ExpressionTranslator etran; + var inParams = new List(); + if (m is TwoStateLemma) { + var prevHeapVar = new Boogie.Formal(m.tok, new Boogie.TypedIdent(m.tok, "previous$Heap", predef.HeapType), true); + var currHeapVar = new Boogie.Formal(m.tok, new Boogie.TypedIdent(m.tok, "current$Heap", predef.HeapType), true); + inParams.Add(prevHeapVar); + inParams.Add(currHeapVar); + prevHeap = new Boogie.IdentifierExpr(m.tok, prevHeapVar); + currHeap = new Boogie.IdentifierExpr(m.tok, currHeapVar); + etran = new ExpressionTranslator(this, predef, currHeap, prevHeap); + } else { + etran = ordinaryEtran; + } + + List outParams; + GenerateMethodParameters(m.tok, m, kind, etran, inParams, out outParams); + + var req = new List(); + var mod = new List(); + var ens = new List(); + // FREE PRECONDITIONS + if (kind == MethodTranslationKind.SpecWellformedness || kind == MethodTranslationKind.Implementation || kind == MethodTranslationKind.OverrideCheck) { // the other cases have no need for a free precondition + // free requires mh == ModuleContextHeight && fh == FunctionContextHeight; + req.Add(Requires(m.tok, true, etran.HeightContext(kind == MethodTranslationKind.OverrideCheck ? m.OverriddenMethod : m), null, null)); + if (m is TwoStateLemma) { + // free requires prevHeap == Heap && HeapSucc(prevHeap, currHeap) && IsHeap(currHeap) + var a0 = Boogie.Expr.Eq(prevHeap, ordinaryEtran.HeapExpr); + var a1 = HeapSucc(prevHeap, currHeap); + var a2 = FunctionCall(m.tok, BuiltinFunction.IsGoodHeap, null, currHeap); + req.Add(Requires(m.tok, true, BplAnd(a0, BplAnd(a1, a2)), null, null)); + } + } + if (m is TwoStateLemma) { + // Checked preconditions that old parameters really existed in previous state + var index = 0; + foreach (var formal in m.Ins) { + if (formal.IsOld) { + var dafnyFormalIdExpr = new IdentifierExpr(formal.tok, formal); + req.Add(Requires(formal.tok, false, MkIsAlloc(etran.TrExpr(dafnyFormalIdExpr), formal.Type, prevHeap), + string.Format("parameter{0} ('{1}') must be allocated in the two-state lemma's previous state", + m.Ins.Count == 1 ? "" : " " + index, formal.Name), null)); + } + index++; + } + } + mod.Add((Boogie.IdentifierExpr/*TODO: this cast is somewhat dubious*/)ordinaryEtran.HeapExpr); + mod.Add(etran.Tick()); + + var bodyKind = kind == MethodTranslationKind.SpecWellformedness || kind == MethodTranslationKind.Implementation; + + if (kind != MethodTranslationKind.SpecWellformedness && kind != MethodTranslationKind.OverrideCheck) { + // USER-DEFINED SPECIFICATIONS + var comment = "user-defined preconditions"; + foreach (var p in m.Req) { + string errorMessage = CustomErrorMessage(p.Attributes); + if (p.Label != null && kind == MethodTranslationKind.Implementation) { + // don't include this precondition here, but record it for later use + p.Label.E = (m is TwoStateLemma ? ordinaryEtran : etran.Old).TrExpr(p.E); + } else { + foreach (var s in TrSplitExprForMethodSpec(p.E, etran, kind)) { + if (s.IsOnlyChecked && bodyKind) { + // don't include in split + } else if (s.IsOnlyFree && !bodyKind) { + // don't include in split -- it would be ignored, anyhow + } else { + req.Add(Requires(s.E.tok, s.IsOnlyFree, s.E, errorMessage, comment)); + comment = null; + // the free here is not linked to the free on the original expression (this is free things generated in the splitting.) + } + } + } + } + comment = "user-defined postconditions"; + foreach (var p in m.Ens) { + string errorMessage = CustomErrorMessage(p.Attributes); + AddEnsures(ens, Ensures(p.E.tok, true, CanCallAssumption(p.E, etran), errorMessage, comment)); + comment = null; + foreach (var s in TrSplitExprForMethodSpec(p.E, etran, kind)) { + var post = s.E; + if (kind == MethodTranslationKind.Implementation && RefinementToken.IsInherited(s.E.tok, currentModule)) { + // this postcondition was inherited into this module, so make it into the form "$_reverifyPost ==> s.E" + post = Boogie.Expr.Imp(new Boogie.IdentifierExpr(s.E.tok, "$_reverifyPost", Boogie.Type.Bool), post); + } + if (s.IsOnlyFree && bodyKind) { + // don't include in split -- it would be ignored, anyhow + } else if (s.IsOnlyChecked && !bodyKind) { + // don't include in split + } else { + AddEnsures(ens, Ensures(s.E.tok, s.IsOnlyFree, post, errorMessage, null)); + } + } + } + if (m is Constructor && kind == MethodTranslationKind.Call) { + var fresh = Boogie.Expr.Not(etran.Old.IsAlloced(m.tok, new Boogie.IdentifierExpr(m.tok, "this", TrReceiverType(m)))); + AddEnsures(ens, Ensures(m.tok, false, fresh, null, "constructor allocates the object")); + } + foreach (BoilerplateTriple tri in GetTwoStateBoilerplate(m.tok, m.Mod.Expressions, m.IsGhost, m.AllowsAllocation, ordinaryEtran.Old, ordinaryEtran, ordinaryEtran.Old)) { + AddEnsures(ens, Ensures(tri.tok, tri.IsFree, tri.Expr, tri.ErrorMessage, tri.Comment)); + } + + // add the fuel assumption for the reveal method of a opaque method + if (IsOpaqueRevealLemma(m)) { + List args = Attributes.FindExpressions(m.Attributes, "fuel"); + if (args != null) { + MemberSelectExpr selectExpr = args[0].Resolved as MemberSelectExpr; + if (selectExpr != null) { + Function f = selectExpr.Member as Function; + FuelConstant fuelConstant = this.functionFuel.Find(x => x.f == f); + if (fuelConstant != null) { + Boogie.Expr startFuel = fuelConstant.startFuel; + Boogie.Expr startFuelAssert = fuelConstant.startFuelAssert; + Boogie.Expr moreFuel_expr = fuelConstant.MoreFuel(sink, predef, f.IdGenerator); + Boogie.Expr layer = etran.layerInterCluster.LayerN(1, moreFuel_expr); + Boogie.Expr layerAssert = etran.layerInterCluster.LayerN(2, moreFuel_expr); + + AddEnsures(ens, Ensures(m.tok, true, Boogie.Expr.Eq(startFuel, layer), null, null)); + AddEnsures(ens, Ensures(m.tok, true, Boogie.Expr.Eq(startFuelAssert, layerAssert), null, null)); + + AddEnsures(ens, Ensures(m.tok, true, Boogie.Expr.Eq(FunctionCall(f.tok, BuiltinFunction.AsFuelBottom, null, moreFuel_expr), moreFuel_expr), null, "Shortcut to LZ")); + } + } + } + } + } + + var name = MethodName(m, kind); + var proc = new Boogie.Procedure(m.tok, name, new List(), inParams, outParams, req, mod, ens, etran.TrAttributes(m.Attributes, null)); + + if (InsertChecksums) { + InsertChecksum(m, proc, true); + } + + currentModule = null; + codeContext = null; + isAllocContext = null; + + return proc; + } + + private void InsertChecksum(Method m, Boogie.Declaration decl, bool specificationOnly = false) { + Contract.Requires(VisibleInScope(m)); + byte[] data; + using (var writer = new System.IO.StringWriter()) { + var printer = new Printer(writer); + printer.PrintAttributes(m.Attributes); + printer.PrintFormals(m.Ins, m); + if (m.Outs.Any()) { + writer.Write("returns "); + printer.PrintFormals(m.Outs, m); + } + printer.PrintSpec("", m.Req, 0); + printer.PrintFrameSpecLine("", m.Mod.Expressions, 0, null); + printer.PrintSpec("", m.Ens, 0); + printer.PrintDecreasesSpec(m.Decreases, 0); + writer.WriteLine(); + if (!specificationOnly && m.Body != null && RevealedInScope(m)) { + printer.PrintStatement(m.Body, 0); + } + data = Encoding.UTF8.GetBytes(writer.ToString()); + } + + InsertChecksum(decl, data); + } + } +} \ No newline at end of file diff --git a/Source/Dafny/Verifier/Translator.DataTypes.cs b/Source/Dafny/Verifier/Translator.DataTypes.cs new file mode 100644 index 00000000000..a62d5b1edd0 --- /dev/null +++ b/Source/Dafny/Verifier/Translator.DataTypes.cs @@ -0,0 +1,761 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Diagnostics.Contracts; +using Bpl = Microsoft.Boogie; +using BplParser = Microsoft.Boogie.Parser; +using System.Text; +using Microsoft.Boogie; +using static Microsoft.Dafny.Util; + +namespace Microsoft.Dafny { + partial class Translator { + void AddDatatype(DatatypeDecl dt) { + Contract.Requires(dt != null); + Contract.Requires(sink != null && predef != null); + + foreach (DatatypeCtor ctor in dt.Ctors) { + AddDataTypeConstructor(dt, ctor); + } + + AddDepthOneCaseSplitFunction(dt); + + // The axiom above ($IsA#Dt(d) <==> Dt.Ctor0?(d) || Dt.Ctor1?(d)) gets triggered only with $IsA#Dt(d). The $IsA#Dt(d) + // predicate is generated only where the translation inserts it; in other words, the user cannot write any assertion + // that causes the $IsA#Dt(d) predicate to be emitted. This is what we want, because making the RHS disjunction be + // available too often makes performance go down. However, we do want to allow the disjunction to be introduced if the + // user explicitly talks about one of its disjuncts. To make this useful, we introduce the following axiom. Note that + // the DtType(d) information is available everywhere. + // axiom (forall G: Ty, d: DatatypeType :: + // { Dt.Ctor0?(G,d) } + // { Dt.Ctor1?(G,d) } + // $Is(d, T(G)) ==> Dt.Ctor0?(G,d) || Dt.Ctor1?(G,d) || ...); + { + List tyexprs; + var tyvars = MkTyParamBinders(dt.TypeArgs, out tyexprs); + Bpl.Expr d; + var dVar = BplBoundVar("d", predef.DatatypeType, out d); + var d_is = MkIs(d, ClassTyCon(dt, tyexprs)); + Bpl.Expr cases_body = Bpl.Expr.False; + Bpl.Trigger tr = null; + foreach (DatatypeCtor ctor in dt.Ctors) { + var disj = FunctionCall(ctor.tok, ctor.QueryField.FullSanitizedName, Bpl.Type.Bool, d); + cases_body = BplOr(cases_body, disj); + tr = new Bpl.Trigger(ctor.tok, true, new List { disj, d_is }, tr); + } + var body = Bpl.Expr.Imp(d_is, cases_body); + var ax = BplForall(Snoc(tyvars, dVar), tr, body); + var axiom = new Bpl.Axiom(dt.tok, ax, "Questionmark data type disjunctivity"); + sink.AddTopLevelDeclaration(axiom); + } + + if (dt is IndDatatypeDecl indDatatypeDecl) { + AddInductiveDatatypeAxioms(indDatatypeDecl); + } + + if (dt is CoDatatypeDecl coDatatypeDecl) { + AddCoDatatypeDeclAxioms(coDatatypeDecl); + } + } + + private void AddInductiveDatatypeAxioms(IndDatatypeDecl dt) { + var dtEqualName = dt.FullSanitizedName + "#Equal"; + + // Add function Dt#Equal(DatatypeType, DatatypeType): bool; + // For each constructor Ctor(x: X, y: Y), add an axiom of the form + // forall a, b :: + // { Dt#Equal(a, b), Ctor?(a) } + // { Dt#Equal(a, b), Ctor?(b) } + // Ctor?(a) && Ctor?(b) + // ==> + // (Dt#Equal(a, b) <==> + // X#Equal(a.x, b.x) && + // Y#Equal(a.y, b.y) + // ) + // where X#Equal is the equality predicate for type X and a.x denotes Dtor#x(a), and similarly + // for Y and b. + // Except, in the event that the datatype has exactly one constructor, then instead generate: + // forall a, b :: + // { Dt#Equal(a, b) } + // true + // ==> + // ...as before + { + var args = new List(); + args.Add(new Bpl.Formal(dt.tok, new Bpl.TypedIdent(dt.tok, Bpl.TypedIdent.NoName, predef.DatatypeType), false)); + args.Add(new Bpl.Formal(dt.tok, new Bpl.TypedIdent(dt.tok, Bpl.TypedIdent.NoName, predef.DatatypeType), false)); + var ctorEqualResult = + new Bpl.Formal(dt.tok, new Bpl.TypedIdent(dt.tok, Bpl.TypedIdent.NoName, Bpl.Type.Bool), false); + sink.AddTopLevelDeclaration(new Bpl.Function(dt.tok, dtEqualName, args, ctorEqualResult, + "Datatype extensional equality declaration")); + + Bpl.Expr a; + var aVar = BplBoundVar("a", predef.DatatypeType, out a); + Bpl.Expr b; + var bVar = BplBoundVar("b", predef.DatatypeType, out b); + + var dtEqual = FunctionCall(dt.tok, dtEqualName, Bpl.Type.Bool, a, b); + + foreach (var ctor in dt.Ctors) { + Bpl.Trigger trigger; + Bpl.Expr ante; + if (dt.Ctors.Count == 1) { + ante = Bpl.Expr.True; + trigger = BplTrigger(dtEqual); + } else { + var ctorQ = GetReadonlyField(ctor.QueryField); + var ctorQa = FunctionCall(ctor.tok, ctorQ.Name, Bpl.Type.Bool, a); + var ctorQb = FunctionCall(ctor.tok, ctorQ.Name, Bpl.Type.Bool, b); + ante = BplAnd(ctorQa, ctorQb); + trigger = dt.Ctors.Count == 1 + ? BplTrigger(dtEqual) + : new Bpl.Trigger(ctor.tok, true, new List { dtEqual, ctorQa }, + new Bpl.Trigger(ctor.tok, true, new List { dtEqual, ctorQb })); + } + + Bpl.Expr eqs = Bpl.Expr.True; + for (var i = 0; i < ctor.Formals.Count; i++) { + var arg = ctor.Formals[i]; + var dtor = GetReadonlyField(ctor.Destructors[i]); + var dtorA = FunctionCall(ctor.tok, dtor.Name, TrType(arg.Type), a); + var dtorB = FunctionCall(ctor.tok, dtor.Name, TrType(arg.Type), b); + var eq = TypeSpecificEqual(ctor.tok, arg.Type, dtorA, dtorB); + eqs = BplAnd(eqs, eq); + } + + var ax = BplForall(new List { aVar, bVar }, trigger, Bpl.Expr.Imp(ante, Bpl.Expr.Iff(dtEqual, eqs))); + AddIncludeDepAxiom(new Bpl.Axiom(dt.tok, ax, $"Datatype extensional equality definition: {ctor.FullName}")); + } + } + + // Add extensionality axiom: forall a, b :: { Dt#Equal(a, b) } Dt#Equal(a, b) <==> a == b + { + Bpl.Expr a; + var aVar = BplBoundVar("a", predef.DatatypeType, out a); + Bpl.Expr b; + var bVar = BplBoundVar("b", predef.DatatypeType, out b); + + var lhs = FunctionCall(dt.tok, dtEqualName, Bpl.Type.Bool, a, b); + var rhs = Bpl.Expr.Eq(a, b); + + var ax = BplForall(new List { aVar, bVar }, BplTrigger(lhs), Bpl.Expr.Iff(lhs, rhs)); + sink.AddTopLevelDeclaration(new Bpl.Axiom(dt.tok, ax, $"Datatype extensionality axiom: {dt.FullName}")); + } + } + + private void AddCoDatatypeDeclAxioms(CoDatatypeDecl codecl) { + Func MinusOne = k => { + if (k == null) { + return null; + } else if (k.Type.IsInt) { + return Bpl.Expr.Sub(k, Bpl.Expr.Literal(1)); + } else { + return FunctionCall(k.tok, "ORD#Minus", k.Type, k, + FunctionCall(k.tok, "ORD#FromNat", k.Type, Bpl.Expr.Literal(1))); + } + }; + + Action, List>, List, List, List, + Bpl.Variable, Bpl.Expr, Bpl.Expr, Bpl.Expr, Bpl.Expr, Bpl.Expr, Bpl.Expr, Bpl.Expr, Bpl.Expr>> CoAxHelper = + (typeOfK, K) => { + Func> renew = s => + Map(codecl.TypeArgs, tp => + new TypeParameter(tp.tok, tp.Name + "#" + s, tp.PositionalIndex, tp.Parent)); + List typaramsL = renew("l"), typaramsR = renew("r"); + List lexprs; + var lvars = MkTyParamBinders(typaramsL, out lexprs); + List rexprs; + var rvars = MkTyParamBinders(typaramsR, out rexprs); + Func, List> Types = l => Map(l, tp => (Type)new UserDefinedType(tp)); + var tyargs = Tuple.Create(Types(typaramsL), Types(typaramsR)); + + var vars = Concat(lvars, rvars); + + Bpl.Expr k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit; + Bpl.Variable kVar; + if (typeOfK != null) { + kVar = BplBoundVar("k", typeOfK, out k); + vars.Add(kVar); + if (typeOfK.IsInt) { + kIsValid = Bpl.Expr.Le(Bpl.Expr.Literal(0), k); + kIsNonZero = Bpl.Expr.Neq(Bpl.Expr.Literal(0), k); + kHasSuccessor = Bpl.Expr.Lt(Bpl.Expr.Literal(0), k); + kIsLimit = Bpl.Expr.False; + } else { + kIsValid = Bpl.Expr.True; + kIsNonZero = Bpl.Expr.Neq(k, FunctionCall(k.tok, "ORD#FromNat", Bpl.Type.Int, Bpl.Expr.Literal(0))); + kHasSuccessor = Bpl.Expr.Lt(Bpl.Expr.Literal(0), FunctionCall(k.tok, "ORD#Offset", Bpl.Type.Int, k)); + kIsLimit = FunctionCall(k.tok, "ORD#IsLimit", Bpl.Type.Bool, k); + } + } else { + kVar = null; + k = null; + kIsValid = Bpl.Expr.True; + kIsNonZero = Bpl.Expr.True; + kHasSuccessor = Bpl.Expr.True; + kIsLimit = Bpl.Expr.True; + } + + var ly = BplBoundVar("ly", predef.LayerType, vars); + var d0 = BplBoundVar("d0", predef.DatatypeType, vars); + var d1 = BplBoundVar("d1", predef.DatatypeType, vars); + + K(tyargs, vars, lexprs, rexprs, kVar, k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit, ly, d0, d1); + }; + + Action AddAxioms = typeOfK => { + { + // Add two copies of the type parameter lists! + var args = MkTyParamFormals(Concat(GetTypeParams(codecl), GetTypeParams(codecl)), false); + if (typeOfK != null) { + args.Add(BplFormalVar(null, typeOfK, true)); + } + + args.Add(BplFormalVar(null, predef.LayerType, true)); + args.Add(BplFormalVar(null, predef.DatatypeType, true)); + args.Add(BplFormalVar(null, predef.DatatypeType, true)); + var r = BplFormalVar(null, Bpl.Type.Bool, false); + var fn_nm = typeOfK != null ? CoPrefixName(codecl) : CoEqualName(codecl); + var fn = new Bpl.Function(codecl.tok, fn_nm, args, r); + if (InsertChecksums) { + InsertChecksum(codecl, fn); + } + + sink.AddTopLevelDeclaration(fn); + } + + // axiom (forall G0,...,Gn : Ty, k: int, ly : Layer, d0, d1: DatatypeType :: + // { Eq(G0, .., Gn, S(ly), k, d0, d1) } + // Is(d0, T(G0, .., Gn)) && Is(d1, T(G0, ... Gn)) ==> + // (Eq(G0, .., Gn, S(ly), k, d0, d1) + // <==> + // (0 < k.Offset ==> + // (d0.Nil? && d1.Nil?) || + // (d0.Cons? && d1.Cons? && d0.head == d1.head && Eq(G0, .., Gn, ly, k-1, d0.tail, d1.tail))) && + // (k != 0 && k.IsLimit ==> // for prefix equality only + // FullEq(G0, .., Gn, ly, d0.tail, d1.tail))) // for prefix equality only + CoAxHelper(typeOfK, + (tyargs, vars, lexprs, rexprs, kVar, k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit, ly, d0, d1) => { + var eqDt = CoEqualCall(codecl, lexprs, rexprs, k, LayerSucc(ly), d0, d1); + var iss = BplAnd(MkIs(d0, ClassTyCon(codecl, lexprs)), MkIs(d1, ClassTyCon(codecl, rexprs))); + var body = BplImp( + iss, + BplIff(eqDt, + BplAnd( + BplImp(kHasSuccessor, + BplOr(CoPrefixEquality(codecl.tok, codecl, tyargs.Item1, tyargs.Item2, MinusOne(k), ly, d0, d1))), + k == null + ? Bpl.Expr.True + : BplImp(BplAnd(kIsNonZero, kIsLimit), + CoEqualCall(codecl, tyargs.Item1, tyargs.Item2, null, ly, d0, d1))))); + var ax = BplForall(vars, BplTrigger(eqDt), body); + AddIncludeDepAxiom(new Bpl.Axiom(codecl.tok, ax, "Layered co-equality axiom")); + }); + + // axiom (forall G0,...,Gn : Ty, k: int, ly : Layer, d0, d1: DatatypeType :: + // { Eq(G0, .., Gn, S(ly), k, d0, d1) } + // 0 < k ==> + // (Eq(G0, .., Gn, S(ly), k, d0, d1) <==> + // Eq(G0, .., Gn, ly, k, d0, d)) + CoAxHelper(typeOfK, + (tyargs, vars, lexprs, rexprs, kVar, k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit, ly, d0, d1) => { + var eqDtSL = CoEqualCall(codecl, lexprs, rexprs, k, LayerSucc(ly), d0, d1); + var eqDtL = CoEqualCall(codecl, lexprs, rexprs, k, ly, d0, d1); + var body = BplImp(kIsNonZero, BplIff(eqDtSL, eqDtL)); + var ax = BplForall(vars, BplTrigger(eqDtSL), body); + AddIncludeDepAxiom(new Bpl.Axiom(codecl.tok, ax, "Unbump layer co-equality axiom")); + }); + }; + + AddAxioms(null); // Add the above axioms for $Equal + + // axiom (forall d0, d1: DatatypeType, k: int :: { $Equal(d0, d1) } :: Equal(d0, d1) <==> d0 == d1); + CoAxHelper(null, (tyargs, vars, lexprs, rexprs, kVar, k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit, ly, d0, d1) => { + var Eq = CoEqualCall(codecl, lexprs, rexprs, k, LayerSucc(ly), d0, d1); + var equal = Bpl.Expr.Eq(d0, d1); + AddIncludeDepAxiom(new Axiom(codecl.tok, + BplForall(vars, BplTrigger(Eq), BplIff(Eq, equal)), + "Equality for codatatypes")); + }); + + Bpl.Type theTypeOfK = predef.BigOrdinalType; + AddAxioms(predef.BigOrdinalType); // Add the above axioms for $PrefixEqual + + // The connection between the full codatatype equality and its prefix version + // axiom (forall d0, d1: DatatypeType :: $Eq#Dt(d0, d1) <==> + // (forall k: int :: 0 <= k ==> $PrefixEqual#Dt(k, d0, d1))); + CoAxHelper(theTypeOfK, (tyargs, vars, lexprs, rexprs, kVar, k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit, ly, d0, d1) => { + var Eq = CoEqualCall(codecl, lexprs, rexprs, null, LayerSucc(ly), d0, d1); + var PEq = CoEqualCall(codecl, lexprs, rexprs, k, LayerSucc(ly), d0, d1); + vars.Remove(kVar); + AddIncludeDepAxiom(new Axiom(codecl.tok, + BplForall(vars, BplTrigger(Eq), BplIff(Eq, BplForall(kVar, BplTrigger(PEq), BplImp(kIsValid, PEq)))), + "Coequality and prefix equality connection")); + }); + // In addition, the following special case holds for $Eq#Dt: + // axiom (forall d0, d1: DatatypeType :: $Eq#Dt(d0, d1) <== + // (forall k: int :: 0 <= k ==> $PrefixEqual#Dt(ORD#FromNat(k), d0, d1))); + if (!theTypeOfK.IsInt) { + CoAxHelper(Bpl.Type.Int, + (tyargs, vars, lexprs, rexprs, kVar, k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit, ly, d0, d1) => { + var Eq = CoEqualCall(codecl, lexprs, rexprs, null, LayerSucc(ly), d0, d1); + var PEq = CoEqualCall(codecl, lexprs, rexprs, FunctionCall(k.tok, "ORD#FromNat", predef.BigOrdinalType, k), + LayerSucc(ly), d0, d1); + vars.Remove(kVar); + AddIncludeDepAxiom(new Axiom(codecl.tok, + BplForall(vars, BplTrigger(Eq), BplImp(BplForall(kVar, BplTrigger(PEq), BplImp(kIsValid, PEq)), Eq)), + "Coequality and prefix equality connection")); + }); + } + + // A consequence of the definition of prefix equalities is the following: + // axiom (forall k, m: int, d0, d1: DatatypeType :: 0 <= k <= m && $PrefixEq#Dt(m, d0, d1) ==> $PrefixEq#0#Dt(k, d0, d1)); + CoAxHelper(theTypeOfK, + (tyargs, vars, lexprs, rexprs, kVar, k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit, ly, d0, d1) => { + var m = BplBoundVar("m", k.Type, vars); + var PEqK = CoEqualCall(codecl, lexprs, rexprs, k, LayerSucc(ly), d0, d1); + var PEqM = CoEqualCall(codecl, lexprs, rexprs, m, LayerSucc(ly), d0, d1); + Bpl.Expr kLtM; + if (k.Type.IsInt) { + kLtM = Bpl.Expr.Lt(k, m); + } else { + kLtM = FunctionCall(codecl.tok, "ORD#Less", Bpl.Type.Bool, k, m); + } + AddIncludeDepAxiom(new Axiom(codecl.tok, + BplForall(vars, + new Bpl.Trigger(codecl.tok, true, new List { PEqK, PEqM }), + BplImp(BplAnd(BplAnd(kIsValid, kLtM), PEqM), PEqK)), + "Prefix equality consequence")); + }); + + // With the axioms above, going from d0==d1 to a prefix equality requires going via the full codatatype + // equality, which in turn requires the full codatatype equality to be present. The following axiom + // provides a shortcut: + // axiom (forall d0, d1: DatatypeType, k: int :: d0 == d1 && 0 <= k ==> $PrefixEqual#_module.Stream(k, d0, d1)); + CoAxHelper(theTypeOfK, + (tyargs, vars, lexprs, rexprs, kVar, k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit, ly, d0, d1) => { + var equal = Bpl.Expr.Eq(d0, d1); + var PEq = CoEqualCall(codecl, lexprs, rexprs, k, LayerSucc(ly), d0, d1); + var trigger = BplTrigger(PEq); + AddIncludeDepAxiom(new Axiom(codecl.tok, + BplForall(vars, trigger, BplImp(BplAnd(equal, kIsValid), PEq)), "Prefix equality shortcut")); + }); + } + + private void AddDepthOneCaseSplitFunction(DatatypeDecl dt) { + // Add: + // function $IsA#Dt(G: Ty,d: DatatypeType): bool { + // Dt.Ctor0?(G, d) || Dt.Ctor1?(G, d) || ... + // } + var cases_dBv = new Bpl.Formal(dt.tok, new Bpl.TypedIdent(dt.tok, Bpl.TypedIdent.NoName, predef.DatatypeType), true); + var cases_resType = new Bpl.Formal(dt.tok, new Bpl.TypedIdent(dt.tok, Bpl.TypedIdent.NoName, Bpl.Type.Bool), false); + var cases_fn = new Bpl.Function(dt.tok, "$IsA#" + dt.FullSanitizedName, + new List { cases_dBv }, + cases_resType, + "Depth-one case-split function"); + + if (InsertChecksums) { + InsertChecksum(dt, cases_fn); + } + + sink.AddTopLevelDeclaration(cases_fn); + // and here comes the actual axiom: + Bpl.Expr d; + var dVar = BplBoundVar("d", predef.DatatypeType, out d); + var lhs = FunctionCall(dt.tok, cases_fn.Name, Bpl.Type.Bool, d); + Bpl.Expr cases_body = Bpl.Expr.False; + foreach (DatatypeCtor ctor in dt.Ctors) { + var disj = FunctionCall(ctor.tok, ctor.QueryField.FullSanitizedName, Bpl.Type.Bool, d); + cases_body = BplOr(cases_body, disj); + } + + var ax = BplForall(new List { dVar }, BplTrigger(lhs), BplImp(lhs, cases_body)); + sink.AddTopLevelDeclaration(new Bpl.Axiom(dt.tok, ax, "Depth-one case-split axiom")); + } + + private Bpl.Function AddDataTypeConstructor(DatatypeDecl dt, DatatypeCtor ctor) { + // Add: function #dt.ctor(tyVars, paramTypes) returns (DatatypeType); + + List argTypes = new List(); + foreach (Formal arg in ctor.Formals) { + Bpl.Variable a = new Bpl.Formal(arg.tok, new Bpl.TypedIdent(arg.tok, Bpl.TypedIdent.NoName, TrType(arg.Type)), + true); + argTypes.Add(a); + } + + Bpl.Variable resType = new Bpl.Formal(ctor.tok, + new Bpl.TypedIdent(ctor.tok, Bpl.TypedIdent.NoName, predef.DatatypeType), false); + Bpl.Function fn; + if (dt is TupleTypeDecl ttd && ttd.Dims == 2 && ttd.NonGhostDims == 2) { + fn = predef.Tuple2Constructor; + } else { + fn = new Bpl.Function(ctor.tok, ctor.FullName, argTypes, resType, "Constructor function declaration"); + sink.AddTopLevelDeclaration(fn); + } + + if (InsertChecksums) { + InsertChecksum(dt, fn); + } + + + { + // Add: const unique ##dt.ctor: DtCtorId; + var definitionAxioms = new List(); + Bpl.Constant constructorId = new Bpl.Constant(ctor.tok, + new Bpl.TypedIdent(ctor.tok, "#" + ctor.FullName, predef.DtCtorId), true, + definitionAxioms: definitionAxioms); + Bpl.Expr constructorIdReference = new Bpl.IdentifierExpr(ctor.tok, constructorId); + var constructorIdentifierAxiom = CreateConstructorIdentifierAxiom(ctor, constructorIdReference); + AddOtherDefinition(fn, constructorIdentifierAxiom); + definitionAxioms.Add(constructorIdentifierAxiom); + sink.AddTopLevelDeclaration(constructorId); + + { + // Add: function dt.ctor?(this: DatatypeType): bool { DatatypeCtorId(this) == ##dt.ctor } + var queryField = GetReadonlyField(ctor.QueryField); + sink.AddTopLevelDeclaration(queryField); + + // and here comes the associated axiom: + + var thVar = BplBoundVar("d", predef.DatatypeType, out var th); + var queryPredicate = FunctionCall(ctor.tok, queryField.Name, Bpl.Type.Bool, th); + var ctorId = FunctionCall(ctor.tok, BuiltinFunction.DatatypeCtorId, null, th); + var rhs = Bpl.Expr.Eq(ctorId, constructorIdReference); + var body = Bpl.Expr.Iff(queryPredicate, rhs); + var tr = BplTrigger(queryPredicate); + var ax = BplForall(thVar, tr, body); + sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, ax, "Questionmark and identifier")); + } + + // check well-formedness of any default-value expressions + AddWellformednessCheck(ctor); + } + + + { + // Add: axiom (forall d: DatatypeType :: dt.ctor?(d) ==> (exists params :: d == #dt.ctor(params)); + CreateBoundVariables(ctor.Formals, out var bvs, out var args); + Bpl.Expr rhs = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); + var dBv = BplBoundVar("d", predef.DatatypeType, out var dId); + Bpl.Expr q = Bpl.Expr.Eq(dId, rhs); + if (bvs.Count != 0) { + q = new Bpl.ExistsExpr(ctor.tok, bvs, null /*always in a Skolemization context*/, q); + } + + Bpl.Expr dtq = FunctionCall(ctor.tok, ctor.QueryField.FullSanitizedName, Bpl.Type.Bool, dId); + var trigger = BplTrigger(dtq); + q = BplForall(dBv, trigger, BplImp(dtq, q)); + sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, q, "Constructor questionmark has arguments")); + } + + AddConstructorAxioms(dt, ctor, fn); + + if (dt is IndDatatypeDecl) { + // Add Lit axiom: + // axiom (forall p0, ..., pn :: #dt.ctor(Lit(p0), ..., Lit(pn)) == Lit(#dt.ctor(p0, .., pn))); + List bvs; + List args; + CreateBoundVariables(ctor.Formals, out bvs, out args); + var litargs = new List(); + foreach (Bpl.Expr arg in args) { + litargs.Add(Lit(arg)); + } + + Bpl.Expr lhs = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, litargs); + Bpl.Expr rhs = Lit(FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args), predef.DatatypeType); + Bpl.Expr q = BplForall(bvs, BplTrigger(lhs), Bpl.Expr.Eq(lhs, rhs)); + var constructorLiteralAxiom = new Bpl.Axiom(ctor.tok, q, "Constructor literal"); + AddOtherDefinition(fn, constructorLiteralAxiom); + } + + // Injectivity axioms for normal arguments + for (int i = 0; i < ctor.Formals.Count; i++) { + var arg = ctor.Formals[i]; + // function ##dt.ctor#i(DatatypeType) returns (Ti); + var sf = ctor.Destructors[i]; + Contract.Assert(sf != null); + fn = GetReadonlyField(sf); + if (fn == predef.Tuple2Destructors0 || fn == predef.Tuple2Destructors1) { + // the two destructors for 2-tuples are predefined in Prelude for use + // by the Map#Items axiom + } else if (sf.EnclosingCtors[0] != ctor) { + // this special field, which comes from a shared destructor, is being declared in a different iteration of this loop + } else { + sink.AddTopLevelDeclaration(fn); + } + + // axiom (forall params :: ##dt.ctor#i(#dt.ctor(params)) == params_i); + List bvs; + List args; + CreateBoundVariables(ctor.Formals, out bvs, out args); + var inner = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); + var outer = FunctionCall(ctor.tok, fn.Name, TrType(arg.Type), inner); + var q = BplForall(bvs, BplTrigger(inner), Bpl.Expr.Eq(outer, args[i])); + AddIncludeDepAxiom(new Bpl.Axiom(ctor.tok, q, "Constructor injectivity")); + + if (dt is IndDatatypeDecl) { + var argType = arg.Type.NormalizeExpandKeepConstraints(); // TODO: keep constraints -- really? Write a test case + if (argType.IsDatatype || argType.IsTypeParameter) { + // for datatype: axiom (forall params :: {#dt.ctor(params)} DtRank(params_i) < DtRank(#dt.ctor(params))); + // for type-parameter type: axiom (forall params :: {#dt.ctor(params)} BoxRank(params_i) < DtRank(#dt.ctor(params))); + CreateBoundVariables(ctor.Formals, out bvs, out args); + Bpl.Expr lhs = FunctionCall(ctor.tok, arg.Type.IsDatatype ? BuiltinFunction.DtRank : BuiltinFunction.BoxRank, + null, args[i]); + /* CHECK + Bpl.Expr lhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, + argType.IsDatatype ? args[i] : FunctionCall(ctor.tok, BuiltinFunction.Unbox, predef.DatatypeType, args[i])); + */ + Bpl.Expr ct = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); + var rhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ct); + var trigger = BplTrigger(ct); + q = new Bpl.ForallExpr(ctor.tok, bvs, trigger, Bpl.Expr.Lt(lhs, rhs)); + AddIncludeDepAxiom(new Bpl.Axiom(ctor.tok, q, "Inductive rank")); + } else if (argType is SeqType) { + // axiom (forall params, i: int {#dt.ctor(params)} :: 0 <= i && i < |arg| ==> DtRank(arg[i]) < DtRank(#dt.ctor(params))); + // that is: + // axiom (forall params, i: int {#dt.ctor(params)} :: 0 <= i && i < |arg| ==> DtRank(Unbox(Seq#Index(arg,i))) < DtRank(#dt.ctor(params))); + { + CreateBoundVariables(ctor.Formals, out bvs, out args); + Bpl.Variable iVar = new Bpl.BoundVariable(arg.tok, new Bpl.TypedIdent(arg.tok, "i", Bpl.Type.Int)); + bvs.Add(iVar); + Bpl.IdentifierExpr ie = new Bpl.IdentifierExpr(arg.tok, iVar); + Bpl.Expr ante = Bpl.Expr.And( + Bpl.Expr.Le(Bpl.Expr.Literal(0), ie), + Bpl.Expr.Lt(ie, FunctionCall(arg.tok, BuiltinFunction.SeqLength, null, args[i]))); + var seqIndex = FunctionCall(arg.tok, BuiltinFunction.SeqIndex, predef.DatatypeType, args[i], ie); + Bpl.Expr lhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, + FunctionCall(arg.tok, BuiltinFunction.Unbox, predef.DatatypeType, seqIndex)); + var ct = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); + var rhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ct); + q = new Bpl.ForallExpr(ctor.tok, bvs, new Trigger(lhs.tok, true, new List { seqIndex, ct }), + Bpl.Expr.Imp(ante, Bpl.Expr.Lt(lhs, rhs))); + sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, q, "Inductive seq element rank")); + } + + // axiom (forall params {#dt.ctor(params)} :: SeqRank(arg) < DtRank(#dt.ctor(params))); + { + CreateBoundVariables(ctor.Formals, out bvs, out args); + var lhs = FunctionCall(ctor.tok, BuiltinFunction.SeqRank, null, args[i]); + var ct = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); + var rhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ct); + var trigger = BplTrigger(ct); + q = new Bpl.ForallExpr(ctor.tok, bvs, trigger, Bpl.Expr.Lt(lhs, rhs)); + AddIncludeDepAxiom(new Bpl.Axiom(ctor.tok, q, "Inductive seq rank")); + } + } else if (argType is SetType) { + // axiom (forall params, d: Datatype {arg[d], #dt.ctor(params)} :: arg[d] ==> DtRank(d) < DtRank(#dt.ctor(params))); + // that is: + // axiom (forall params, d: Datatype {arg[Box(d)], #dt.ctor(params)} :: arg[Box(d)] ==> DtRank(d) < DtRank(#dt.ctor(params))); + CreateBoundVariables(ctor.Formals, out bvs, out args); + Bpl.Variable dVar = new Bpl.BoundVariable(arg.tok, new Bpl.TypedIdent(arg.tok, "d", predef.DatatypeType)); + bvs.Add(dVar); + Bpl.IdentifierExpr ie = new Bpl.IdentifierExpr(arg.tok, dVar); + Bpl.Expr inSet = Bpl.Expr.SelectTok(arg.tok, args[i], FunctionCall(arg.tok, BuiltinFunction.Box, null, ie)); + Bpl.Expr lhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ie); + var ct = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); + var rhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ct); + var trigger = new Bpl.Trigger(ctor.tok, true, new List { inSet, ct }); + q = new Bpl.ForallExpr(ctor.tok, bvs, trigger, Bpl.Expr.Imp(inSet, Bpl.Expr.Lt(lhs, rhs))); + sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, q, "Inductive set element rank")); + } else if (argType is MultiSetType) { + // axiom (forall params, d: Datatype {arg[d], #dt.ctor(params)} :: 0 < arg[d] ==> DtRank(d) < DtRank(#dt.ctor(params))); + // that is: + // axiom (forall params, d: Datatype {arg[Box(d)], #dt.ctor(params)} :: 0 < arg[Box(d)] ==> DtRank(d) < DtRank(#dt.ctor(params))); + CreateBoundVariables(ctor.Formals, out bvs, out args); + Bpl.Variable dVar = new Bpl.BoundVariable(arg.tok, new Bpl.TypedIdent(arg.tok, "d", predef.DatatypeType)); + bvs.Add(dVar); + Bpl.IdentifierExpr ie = new Bpl.IdentifierExpr(arg.tok, dVar); + var inMultiset = Bpl.Expr.SelectTok(arg.tok, args[i], FunctionCall(arg.tok, BuiltinFunction.Box, null, ie)); + Bpl.Expr ante = Bpl.Expr.Gt(inMultiset, Bpl.Expr.Literal(0)); + Bpl.Expr lhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ie); + var ct = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); + var rhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ct); + var trigger = new Bpl.Trigger(ctor.tok, true, new List { inMultiset, ct }); + q = new Bpl.ForallExpr(ctor.tok, bvs, trigger, Bpl.Expr.Imp(ante, Bpl.Expr.Lt(lhs, rhs))); + sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, q, "Inductive multiset element rank")); + } else if (argType is MapType) { + var finite = ((MapType)argType).Finite; + { + // axiom (forall params, d: DatatypeType + // { Map#Domain(arg)[$Box(d)], #dt.ctor(params) } + // Map#Domain(arg)[$Box(d)] ==> DtRank(d) < DtRank(#dt.ctor(params))); + CreateBoundVariables(ctor.Formals, out bvs, out args); + var dVar = new Bpl.BoundVariable(arg.tok, new Bpl.TypedIdent(arg.tok, "d", predef.DatatypeType)); + bvs.Add(dVar); + var ie = new Bpl.IdentifierExpr(arg.tok, dVar); + var f = finite ? BuiltinFunction.MapDomain : BuiltinFunction.IMapDomain; + var domain = FunctionCall(arg.tok, f, predef.MapType(arg.tok, finite, predef.BoxType, predef.BoxType), + args[i]); + var inDomain = Bpl.Expr.SelectTok(arg.tok, domain, FunctionCall(arg.tok, BuiltinFunction.Box, null, ie)); + var lhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ie); + var ct = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); + var rhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ct); + var trigger = new Bpl.Trigger(ctor.tok, true, new List { inDomain, ct }); + q = new Bpl.ForallExpr(ctor.tok, bvs, trigger, Bpl.Expr.Imp(inDomain, Bpl.Expr.Lt(lhs, rhs))); + sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, q, "Inductive map key rank")); + } + { + // axiom(forall params, bx: Box :: + // { Map#Elements(arg)[bx], #dt.ctor(params) } + // Map#Domain(arg)[bx] ==> DtRank($Unbox(Map#Elements(arg)[bx]): DatatypeType) < DtRank(#dt.ctor(params))); + CreateBoundVariables(ctor.Formals, out bvs, out args); + var bxVar = new Bpl.BoundVariable(arg.tok, new Bpl.TypedIdent(arg.tok, "bx", predef.BoxType)); + bvs.Add(bxVar); + var ie = new Bpl.IdentifierExpr(arg.tok, bxVar); + var f = finite ? BuiltinFunction.MapDomain : BuiltinFunction.IMapDomain; + var domain = FunctionCall(arg.tok, f, predef.MapType(arg.tok, finite, predef.BoxType, predef.BoxType), + args[i]); + var inDomain = Bpl.Expr.SelectTok(arg.tok, domain, ie); + var ef = finite ? BuiltinFunction.MapElements : BuiltinFunction.IMapElements; + var element = FunctionCall(arg.tok, ef, predef.MapType(arg.tok, finite, predef.BoxType, predef.BoxType), + args[i]); + var elmt = Bpl.Expr.SelectTok(arg.tok, element, ie); + var unboxElmt = FunctionCall(arg.tok, BuiltinFunction.Unbox, predef.DatatypeType, elmt); + var lhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, unboxElmt); + var ct = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); + var rhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ct); + var trigger = new Bpl.Trigger(ctor.tok, true, new List { inDomain, ct }); + q = new Bpl.ForallExpr(ctor.tok, bvs, trigger, Bpl.Expr.Imp(inDomain, Bpl.Expr.Lt(lhs, rhs))); + sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, q, "Inductive map value rank")); + } + } + } + } + + return fn; + } + + private void AddConstructorAxioms(DatatypeDecl dt, DatatypeCtor ctor, Bpl.Function ctorFunction) { + var tyvars = MkTyParamBinders(dt.TypeArgs, out var tyexprs); + CreateBoundVariables(ctor.Formals, out var bvs, out var args); + bvs.InsertRange(0, tyvars); + var c_params = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); + var c_ty = ClassTyCon(dt, tyexprs); + AddsIsConstructorAxiom(ctor, ctorFunction, args, bvs, c_params, c_ty); + AddIsAllocConstructorAxiom(dt, ctor, ctorFunction, args, bvs, c_params, c_ty); + AddDestructorAxiom(dt, ctor, ctorFunction, tyvars, c_ty); + } + + /* + (forall x0 : C0, ..., xn : Cn, G : Ty, H : Heap • + { $IsAlloc(C(G, x0,...,xn), T(G), H) } + IsGoodHeap(H) ==> + ($IsAlloc(C(G, x0,...,xn), T(G), H) <==> + $IsAlloc[Box](x0, C0(G), H) && ... && $IsAlloc[Box](xn, Cn(G), H))); + */ + private void AddIsAllocConstructorAxiom(DatatypeDecl dt, DatatypeCtor ctor, Bpl.Function ctorFunction, + List args, List bvs, NAryExpr c_params, Expr c_ty) { + var hVar = BplBoundVar("$h", predef.HeapType, out var h); + + Bpl.Expr conj = Bpl.Expr.True; + for (var i = 0; i < ctor.Formals.Count; i++) { + var arg = ctor.Formals[i]; + if (CommonHeapUse || (NonGhostsUseHeap && !arg.IsGhost)) { + conj = BplAnd(conj, MkIsAlloc(args[i], arg.Type, h)); + } + } + + if (CommonHeapUse || NonGhostsUseHeap) { + var isGoodHeap = FunctionCall(ctor.tok, BuiltinFunction.IsGoodHeap, null, h); + var c_alloc = MkIsAlloc(c_params, c_ty, h); + bvs.Add(hVar); + var constructorIsAllocAxiom = new Bpl.Axiom(ctor.tok, + BplForall(bvs, BplTrigger(c_alloc), + BplImp(isGoodHeap, BplIff(c_alloc, conj))), + "Constructor $IsAlloc"); + AddOtherDefinition(ctorFunction, constructorIsAllocAxiom); + } + } + + /* (forall d : DatatypeType, G : Ty, H : Heap • + { $IsAlloc[Box](Dtor(d), D(G), H) } + IsGoodHeap(H) && + C?(d) && + (exists G' : Ty :: $IsAlloc(d, T(G,G'), H)) + ==> + $IsAlloc[Box](Dtor(d), D(G), H)) + */ + private void AddDestructorAxiom(DatatypeDecl dt, DatatypeCtor ctor, Bpl.Function ctorFunction, List tyvars, Expr c_ty) { + if (!CommonHeapUse || AlwaysUseHeap) { + return; + } + + var hVar = BplBoundVar("$h", predef.HeapType, out var h); + for (int i = 0; i < ctor.Formals.Count; i++) { + var arg = ctor.Formals[i]; + var dtor = GetReadonlyField(ctor.Destructors[i]); + Bpl.Expr dId; + var dBv = BplBoundVar("d", predef.DatatypeType, out dId); + var isGoodHeap = FunctionCall(ctor.tok, BuiltinFunction.IsGoodHeap, null, h); + Bpl.Expr dtq = FunctionCall(ctor.tok, ctor.QueryField.FullSanitizedName, Bpl.Type.Bool, dId); + var c_alloc = MkIsAlloc(dId, c_ty, h); + var dtorD = FunctionCall(ctor.tok, dtor.Name, TrType(arg.Type), dId); + var d_alloc = MkIsAlloc(dtorD, arg.Type, h); + + // split tyvars into G,G' where G are the type variables that are used in the type of the destructor + var freeTypeVars = new HashSet(); + ComputeFreeTypeVariables_All(arg.Type, freeTypeVars); + var tyvarsG = new List(); + var tyvarsGprime = new List(); + Contract.Assert(dt.TypeArgs.Count == tyvars.Count); + for (int j = 0; j < dt.TypeArgs.Count; j++) { + var tv = tyvars[j]; + if (freeTypeVars.Contains(dt.TypeArgs[j])) { + tyvarsG.Add(tv); + } else { + tyvarsGprime.Add(tv); + } + } + + var bvs = new List(); + bvs.Add(dBv); + bvs.AddRange(tyvarsG); + bvs.Add(hVar); + if (tyvarsGprime.Count != 0) { + c_alloc = new Bpl.ExistsExpr(ctor.tok, tyvarsGprime, BplTrigger(c_alloc), c_alloc); + } + + var destructorAxiom = new Bpl.Axiom(ctor.tok, + BplForall(bvs, BplTrigger(d_alloc), + BplImp(BplAnd(isGoodHeap, BplAnd(dtq, c_alloc)), d_alloc)), + "Destructor $IsAlloc"); + AddOtherDefinition(ctorFunction, destructorAxiom); + } + } + + /* + (forall x0 : C0, ..., xn : Cn, G : Ty • + { $Is(C(x0,...,xn), T(G)) } + $Is(C(x0,...,xn), T(G)) <==> + $Is[Box](x0, C0(G)) && ... && $Is[Box](xn, Cn(G))); + */ + private void AddsIsConstructorAxiom(DatatypeCtor ctor, Bpl.Function ctorFunction, List args, List bvs, NAryExpr c_params, Expr c_ty) { + Bpl.Expr conj = Bpl.Expr.True; + for (var i = 0; i < ctor.Formals.Count; i++) { + var arg = ctor.Formals[i]; + conj = BplAnd(conj, MkIs(args[i], arg.Type)); + } + + var isCall = MkIs(c_params, c_ty); + var constructorIsAxiom = new Bpl.Axiom(ctor.tok, + BplForall(bvs, BplTrigger(isCall), BplIff(isCall, conj)), + "Constructor $Is"); + AddOtherDefinition(ctorFunction, constructorIsAxiom); + } + + private Axiom CreateConstructorIdentifierAxiom(DatatypeCtor ctor, Expr c) { + // Add: axiom (forall params :: DatatypeCtorId(#dt.ctor(params)) == ##dt.ctor); + List bvs; + List args; + CreateBoundVariables(ctor.Formals, out bvs, out args); + var constructor_call = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); + var lhs = FunctionCall(ctor.tok, BuiltinFunction.DatatypeCtorId, null, constructor_call); + Bpl.Expr q = Bpl.Expr.Eq(lhs, c); + var trigger = BplTrigger(constructor_call); + var axiom = new Bpl.Axiom(ctor.tok, BplForall(bvs, trigger, q), "Constructor identifier"); + return axiom; + } + } +} \ No newline at end of file diff --git a/Source/Dafny/Verifier/Translator.cs b/Source/Dafny/Verifier/Translator.cs index 79391218091..acae5afd9e3 100644 --- a/Source/Dafny/Verifier/Translator.cs +++ b/Source/Dafny/Verifier/Translator.cs @@ -22,6 +22,26 @@ public partial class Translator { // TODO(wuestholz): Enable this once Dafny's recommended Z3 version includes changeset 0592e765744497a089c42021990740f303901e67. public bool UseOptimizationInZ3 { get; set; } + void AddOtherDefinition(Bpl.Declaration declaration, Axiom axiom) { + + switch (declaration) { + case Boogie.Function boogieFunction: + boogieFunction.AddOtherDefinitionAxiom(axiom); + break; + case Boogie.Constant boogieConstant: + boogieConstant.DefinitionAxioms.Add(axiom); + break; + default: throw new ArgumentException("Declaration must be a function or constant"); + } + + sink.AddTopLevelDeclaration(axiom); + } + + void AddIncludeDepAxiom(Axiom axiom) { + axiom.AddAttribute("include_dep"); + sink.AddTopLevelDeclaration(axiom); + } + public class TranslatorFlags { public bool InsertChecksums = 0 < CommandLineOptions.Clo.VerifySnapshots; public string UniqueIdPrefix = null; @@ -186,6 +206,8 @@ public Bpl.Type BigOrdinalType { public readonly Bpl.Function IMapValues; public readonly Bpl.Function MapItems; public readonly Bpl.Function IMapItems; + public readonly Bpl.Function ObjectTypeConstructor; + public readonly Bpl.Function Tuple2TypeConstructor; public readonly Bpl.Function Tuple2Destructors0; public readonly Bpl.Function Tuple2Destructors1; public readonly Bpl.Function Tuple2Constructor; @@ -296,7 +318,8 @@ public PredefinedDecls(Bpl.TypeCtorDecl charType, Bpl.TypeCtorDecl refType, Bpl. Bpl.Function ORD_isLimit, Bpl.Function ORD_isSucc, Bpl.Function ORD_offset, Bpl.Function ORD_isNat, Bpl.Function mapDomain, Bpl.Function imapDomain, Bpl.Function mapValues, Bpl.Function imapValues, Bpl.Function mapItems, Bpl.Function imapItems, - Bpl.Function tuple2Destructors0, Bpl.Function tuple2Destructors1, Bpl.Function tuple2Constructor, + Bpl.Function objectTypeConstructor, + Bpl.Function tuple2Destructors0, Bpl.Function tuple2Destructors1, Bpl.Function tuple2Constructor, Bpl.Function tuple2TypeConstructor, Bpl.TypeCtorDecl seqTypeCtor, Bpl.TypeSynonymDecl bv0TypeDecl, Bpl.TypeCtorDecl fieldNameType, Bpl.TypeCtorDecl tyType, Bpl.TypeCtorDecl tyTagType, Bpl.TypeCtorDecl tyTagFamilyType, Bpl.GlobalVariable heap, Bpl.TypeCtorDecl classNameType, Bpl.TypeCtorDecl nameFamilyType, @@ -363,9 +386,11 @@ public PredefinedDecls(Bpl.TypeCtorDecl charType, Bpl.TypeCtorDecl refType, Bpl. this.IMapValues = imapValues; this.MapItems = mapItems; this.IMapItems = imapItems; + this.ObjectTypeConstructor = objectTypeConstructor; this.Tuple2Destructors0 = tuple2Destructors0; this.Tuple2Destructors1 = tuple2Destructors1; this.Tuple2Constructor = tuple2Constructor; + this.Tuple2TypeConstructor = tuple2TypeConstructor; this.seqTypeCtor = seqTypeCtor; this.Bv0Type = new Bpl.TypeSynonymAnnotation(Token.NoToken, bv0TypeDecl, new List()); this.fieldName = fieldNameType; @@ -409,6 +434,8 @@ static PredefinedDecls FindPredefinedDecls(Bpl.Program prog) { Bpl.Function imapValues = null; Bpl.Function mapItems = null; Bpl.Function imapItems = null; + Bpl.Function objectTypeConstructor = null; + Bpl.Function tuple2TypeConstructor = null; Bpl.Function tuple2Destructors0 = null; Bpl.Function tuple2Destructors1 = null; Bpl.Function tuple2Constructor = null; @@ -521,6 +548,10 @@ static PredefinedDecls FindPredefinedDecls(Bpl.Program prog) { tuple2Destructors1 = f; } else if (f.Name == "#_System._tuple#2._#Make2") { tuple2Constructor = f; + } else if (f.Name == "Tclass._System.Tuple2") { + tuple2TypeConstructor = f; + } else if (f.Name == "Tclass._System.object?") { + objectTypeConstructor = f; } } } @@ -600,6 +631,10 @@ static PredefinedDecls FindPredefinedDecls(Bpl.Program prog) { Console.WriteLine("Error: Dafny prelude is missing declaration of $Heap"); } else if (allocField == null) { Console.WriteLine("Error: Dafny prelude is missing declaration of constant alloc"); + } else if (tuple2TypeConstructor == null) { + Console.WriteLine("Error: Dafny prelude is missing declaration of tuple2TypeConstructor"); + } else if (objectTypeConstructor == null) { + Console.WriteLine("Error: Dafny prelude is missing declaration of objectTypeConstructor"); } else { return new PredefinedDecls(charType, refType, boxType, tickType, setTypeCtor, isetTypeCtor, multiSetTypeCtor, @@ -608,7 +643,8 @@ static PredefinedDecls FindPredefinedDecls(Bpl.Program prog) { ORDINAL_isLimit, ORDINAL_isSucc, ORDINAL_offset, ORDINAL_isNat, mapDomain, imapDomain, mapValues, imapValues, mapItems, imapItems, - tuple2Destructors0, tuple2Destructors1, tuple2Constructor, + objectTypeConstructor, + tuple2Destructors0, tuple2Destructors1, tuple2Constructor, tuple2TypeConstructor, seqTypeCtor, bv0TypeDecl, fieldNameType, tyType, tyTagType, tyTagFamilyType, heap, classNameType, nameFamilyType, @@ -1053,6 +1089,7 @@ private void AddTraitParentAxioms() { bvs.Add(oVar); var tr = BplTrigger(isC); var body = BplImp(BplAnd(oNotNull, isC), isJ); + sink.AddTopLevelDeclaration(new Bpl.Axiom(c.tok, new Bpl.ForallExpr(c.tok, bvs, tr, body))); // axiom (forall T: Ty, $Heap: Heap, $o: ref :: @@ -1205,6 +1242,7 @@ void AddTypeDecl(SubsetTypeDecl dd) { currentModule = null; this.fuelContext = oldFuelContext; } + void AddRedirectingTypeDeclAxioms(bool is_alloc, T dd, string fullName) where T : TopLevelDecl, RedirectingTypeDecl { Contract.Requires(dd != null); Contract.Requires(dd.Var != null && dd.Constraint != null); @@ -1251,664 +1289,10 @@ void AddRedirectingTypeDeclAxioms(bool is_alloc, T dd, string fullName) where body = BplIff(is_o, BplAnd(parentConstraint, constraint)); } - sink.AddTopLevelDeclaration(new Bpl.Axiom(dd.tok, BplForall(vars, BplTrigger(is_o), body), name)); + AddIncludeDepAxiom(new Bpl.Axiom(dd.tok, BplForall(vars, BplTrigger(is_o), body), name)); } - void AddDatatype(DatatypeDecl dt) { - Contract.Requires(dt != null); - Contract.Requires(sink != null && predef != null); - - foreach (DatatypeCtor ctor in dt.Ctors) { - // Add: function #dt.ctor(tyVars, paramTypes) returns (DatatypeType); - - List argTypes = new List(); - foreach (Formal arg in ctor.Formals) { - Bpl.Variable a = new Bpl.Formal(arg.tok, new Bpl.TypedIdent(arg.tok, Bpl.TypedIdent.NoName, TrType(arg.Type)), true); - argTypes.Add(a); - } - Bpl.Variable resType = new Bpl.Formal(ctor.tok, new Bpl.TypedIdent(ctor.tok, Bpl.TypedIdent.NoName, predef.DatatypeType), false); - Bpl.Function fn; - if (dt is TupleTypeDecl ttd && ttd.Dims == 2 && ttd.NonGhostDims == 2) { - fn = predef.Tuple2Constructor; - } else { - fn = new Bpl.Function(ctor.tok, ctor.FullName, argTypes, resType, "Constructor function declaration"); - sink.AddTopLevelDeclaration(fn); - } - if (InsertChecksums) { - InsertChecksum(dt, fn); - } - - List bvs; - List args; - - - { - // Add: const unique ##dt.ctor: DtCtorId; - Bpl.Constant cid = new Bpl.Constant(ctor.tok, new Bpl.TypedIdent(ctor.tok, "#" + ctor.FullName, predef.DtCtorId), true); - Bpl.Expr c = new Bpl.IdentifierExpr(ctor.tok, cid); - sink.AddTopLevelDeclaration(cid); - - { - // Add: axiom (forall params :: DatatypeCtorId(#dt.ctor(params)) == ##dt.ctor); - CreateBoundVariables(ctor.Formals, out bvs, out args); - var constructor_call = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); - var lhs = FunctionCall(ctor.tok, BuiltinFunction.DatatypeCtorId, null, constructor_call); - Bpl.Expr q = Bpl.Expr.Eq(lhs, c); - var trigger = BplTrigger(constructor_call); - sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, BplForall(bvs, trigger, q), "Constructor identifier")); - } - - { - // Add: function dt.ctor?(this: DatatypeType): bool { DatatypeCtorId(this) == ##dt.ctor } - fn = GetReadonlyField(ctor.QueryField); - sink.AddTopLevelDeclaration(fn); - - // and here comes the associated axiom: - - Bpl.Expr th; var thVar = BplBoundVar("d", predef.DatatypeType, out th); - var queryPredicate = FunctionCall(ctor.tok, fn.Name, Bpl.Type.Bool, th); - var ctorId = FunctionCall(ctor.tok, BuiltinFunction.DatatypeCtorId, null, th); - var rhs = Bpl.Expr.Eq(ctorId, c); - var body = Bpl.Expr.Iff(queryPredicate, rhs); - var tr = BplTrigger(queryPredicate); - var ax = BplForall(thVar, tr, body); - sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, ax, "Questionmark and identifier")); - } - - // check well-formedness of any default-value expressions - AddWellformednessCheck(ctor); - } - - - { - // Add: axiom (forall d: DatatypeType :: dt.ctor?(d) ==> (exists params :: d == #dt.ctor(params)); - CreateBoundVariables(ctor.Formals, out bvs, out args); - Bpl.Expr rhs = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); - Bpl.Expr dId; var dBv = BplBoundVar("d", predef.DatatypeType, out dId); - Bpl.Expr q = Bpl.Expr.Eq(dId, rhs); - if (bvs.Count != 0) { - q = new Bpl.ExistsExpr(ctor.tok, bvs, null/*always in a Skolemization context*/, q); - } - Bpl.Expr dtq = FunctionCall(ctor.tok, ctor.QueryField.FullSanitizedName, Bpl.Type.Bool, dId); - var trigger = BplTrigger(dtq); - q = BplForall(dBv, trigger, BplImp(dtq, q)); - sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, q, "Constructor questionmark has arguments")); - } - - MapM(Bools, is_alloc => { - /* - (forall x0 : C0, ..., xn : Cn, G : Ty • - { $Is(C(x0,...,xn), T(G)) } - $Is(C(x0,...,xn), T(G)) <==> - $Is[Box](x0, C0(G)) && ... && $Is[Box](xn, Cn(G))); - (forall x0 : C0, ..., xn : Cn, G : Ty, H : Heap • - { $IsAlloc(C(G, x0,...,xn), T(G), H) } - IsGoodHeap(H) ==> - ($IsAlloc(C(G, x0,...,xn), T(G), H) <==> - $IsAlloc[Box](x0, C0(G), H) && ... && $IsAlloc[Box](xn, Cn(G), H))); - */ - List tyexprs; - var tyvars = MkTyParamBinders(dt.TypeArgs, out tyexprs); - CreateBoundVariables(ctor.Formals, out bvs, out args); - Bpl.Expr h; - var hVar = BplBoundVar("$h", predef.HeapType, out h); - Bpl.Expr conj = Bpl.Expr.True; - for (var i = 0; i < ctor.Formals.Count; i++) { - var arg = ctor.Formals[i]; - if (is_alloc) { - if (CommonHeapUse || (NonGhostsUseHeap && !arg.IsGhost)) { - conj = BplAnd(conj, MkIsAlloc(args[i], arg.Type, h)); - } - } else { - conj = BplAnd(conj, MkIs(args[i], arg.Type)); - } - } - var c_params = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); - var c_ty = ClassTyCon((TopLevelDecl)dt, tyexprs); - bvs.InsertRange(0, tyvars); - if (!is_alloc) { - var c_is = MkIs(c_params, c_ty); - sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, - BplForall(bvs, BplTrigger(c_is), BplIff(c_is, conj)), - "Constructor $Is")); - } else if (is_alloc && (CommonHeapUse || NonGhostsUseHeap)) { - var isGoodHeap = FunctionCall(ctor.tok, BuiltinFunction.IsGoodHeap, null, h); - var c_alloc = MkIsAlloc(c_params, c_ty, h); - bvs.Add(hVar); - sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, - BplForall(bvs, BplTrigger(c_alloc), - BplImp(isGoodHeap, BplIff(c_alloc, conj))), - "Constructor $IsAlloc")); - } - if (is_alloc && CommonHeapUse && !AlwaysUseHeap) { - for (int i = 0; i < ctor.Formals.Count; i++) { - var arg = ctor.Formals[i]; - var dtor = GetReadonlyField(ctor.Destructors[i]); - /* (forall d : DatatypeType, G : Ty, H : Heap • - { $IsAlloc[Box](Dtor(d), D(G), H) } - IsGoodHeap(H) && - C?(d) && - (exists G' : Ty :: $IsAlloc(d, T(G,G'), H)) - ==> - $IsAlloc[Box](Dtor(d), D(G), H)) - */ - Bpl.Expr dId; var dBv = BplBoundVar("d", predef.DatatypeType, out dId); - var isGoodHeap = FunctionCall(ctor.tok, BuiltinFunction.IsGoodHeap, null, h); - Bpl.Expr dtq = FunctionCall(ctor.tok, ctor.QueryField.FullSanitizedName, Bpl.Type.Bool, dId); - var c_alloc = MkIsAlloc(dId, c_ty, h); - var dtorD = FunctionCall(ctor.tok, dtor.Name, TrType(arg.Type), dId); - var d_alloc = MkIsAlloc(dtorD, arg.Type, h); - - // split tyvars into G,G' where G are the type variables that are used in the type of the destructor - var freeTypeVars = new HashSet(); - ComputeFreeTypeVariables_All(arg.Type, freeTypeVars); - var tyvarsG = new List(); - var tyvarsGprime = new List(); - Contract.Assert(dt.TypeArgs.Count == tyvars.Count); - for (int j = 0; j < dt.TypeArgs.Count; j++) { - var tv = tyvars[j]; - if (freeTypeVars.Contains(dt.TypeArgs[j])) { - tyvarsG.Add(tv); - } else { - tyvarsGprime.Add(tv); - } - } - - bvs = new List(); - bvs.Add(dBv); - bvs.AddRange(tyvarsG); - bvs.Add(hVar); - if (tyvarsGprime.Count != 0) { - c_alloc = new Bpl.ExistsExpr(ctor.tok, tyvarsGprime, BplTrigger(c_alloc), c_alloc); - } - sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, - BplForall(bvs, BplTrigger(d_alloc), - BplImp(BplAnd(isGoodHeap, BplAnd(dtq, c_alloc)), d_alloc)), - "Destructor $IsAlloc")); - } - } - }); - - if (dt is IndDatatypeDecl) { - // Add Lit axiom: - // axiom (forall p0, ..., pn :: #dt.ctor(Lit(p0), ..., Lit(pn)) == Lit(#dt.ctor(p0, .., pn))); - CreateBoundVariables(ctor.Formals, out bvs, out args); - var litargs = new List(); - foreach (Bpl.Expr arg in args) { - litargs.Add(Lit(arg)); - } - Bpl.Expr lhs = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, litargs); - Bpl.Expr rhs = Lit(FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args), predef.DatatypeType); - Bpl.Expr q = BplForall(bvs, BplTrigger(lhs), Bpl.Expr.Eq(lhs, rhs)); - sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, q, "Constructor literal")); - } - - // Injectivity axioms for normal arguments - for (int i = 0; i < ctor.Formals.Count; i++) { - var arg = ctor.Formals[i]; - // function ##dt.ctor#i(DatatypeType) returns (Ti); - var sf = ctor.Destructors[i]; - Contract.Assert(sf != null); - fn = GetReadonlyField(sf); - if (fn == predef.Tuple2Destructors0 || fn == predef.Tuple2Destructors1) { - // the two destructors for 2-tuples are predefined in Prelude for use - // by the Map#Items axiom - } else if (sf.EnclosingCtors[0] != ctor) { - // this special field, which comes from a shared destructor, is being declared in a different iteration of this loop - } else { - sink.AddTopLevelDeclaration(fn); - } - // axiom (forall params :: ##dt.ctor#i(#dt.ctor(params)) == params_i); - CreateBoundVariables(ctor.Formals, out bvs, out args); - var inner = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); - var outer = FunctionCall(ctor.tok, fn.Name, TrType(arg.Type), inner); - var q = BplForall(bvs, BplTrigger(inner), Bpl.Expr.Eq(outer, args[i])); - sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, q, "Constructor injectivity")); - - if (dt is IndDatatypeDecl) { - var argType = arg.Type.NormalizeExpandKeepConstraints(); // TODO: keep constraints -- really? Write a test case - if (argType.IsDatatype || argType.IsTypeParameter) { - // for datatype: axiom (forall params :: {#dt.ctor(params)} DtRank(params_i) < DtRank(#dt.ctor(params))); - // for type-parameter type: axiom (forall params :: {#dt.ctor(params)} BoxRank(params_i) < DtRank(#dt.ctor(params))); - CreateBoundVariables(ctor.Formals, out bvs, out args); - Bpl.Expr lhs = FunctionCall(ctor.tok, arg.Type.IsDatatype ? BuiltinFunction.DtRank : BuiltinFunction.BoxRank, null, args[i]); - /* CHECK - Bpl.Expr lhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, - argType.IsDatatype ? args[i] : FunctionCall(ctor.tok, BuiltinFunction.Unbox, predef.DatatypeType, args[i])); - */ - Bpl.Expr ct = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); - var rhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ct); - var trigger = BplTrigger(ct); - q = new Bpl.ForallExpr(ctor.tok, bvs, trigger, Bpl.Expr.Lt(lhs, rhs)); - sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, q, "Inductive rank")); - } else if (argType is SeqType) { - // axiom (forall params, i: int {#dt.ctor(params)} :: 0 <= i && i < |arg| ==> DtRank(arg[i]) < DtRank(#dt.ctor(params))); - // that is: - // axiom (forall params, i: int {#dt.ctor(params)} :: 0 <= i && i < |arg| ==> DtRank(Unbox(Seq#Index(arg,i))) < DtRank(#dt.ctor(params))); - { - CreateBoundVariables(ctor.Formals, out bvs, out args); - Bpl.Variable iVar = new Bpl.BoundVariable(arg.tok, new Bpl.TypedIdent(arg.tok, "i", Bpl.Type.Int)); - bvs.Add(iVar); - Bpl.IdentifierExpr ie = new Bpl.IdentifierExpr(arg.tok, iVar); - Bpl.Expr ante = Bpl.Expr.And( - Bpl.Expr.Le(Bpl.Expr.Literal(0), ie), - Bpl.Expr.Lt(ie, FunctionCall(arg.tok, BuiltinFunction.SeqLength, null, args[i]))); - var seqIndex = FunctionCall(arg.tok, BuiltinFunction.SeqIndex, predef.DatatypeType, args[i], ie); - Bpl.Expr lhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, - FunctionCall(arg.tok, BuiltinFunction.Unbox, predef.DatatypeType, seqIndex)); - var ct = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); - var rhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ct); - q = new Bpl.ForallExpr(ctor.tok, bvs, new Trigger(lhs.tok, true, new List { seqIndex, ct }), Bpl.Expr.Imp(ante, Bpl.Expr.Lt(lhs, rhs))); - sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, q, "Inductive seq element rank")); - } - - // axiom (forall params {#dt.ctor(params)} :: SeqRank(arg) < DtRank(#dt.ctor(params))); - { - CreateBoundVariables(ctor.Formals, out bvs, out args); - var lhs = FunctionCall(ctor.tok, BuiltinFunction.SeqRank, null, args[i]); - var ct = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); - var rhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ct); - var trigger = BplTrigger(ct); - q = new Bpl.ForallExpr(ctor.tok, bvs, trigger, Bpl.Expr.Lt(lhs, rhs)); - sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, q, "Inductive seq rank")); - } - } else if (argType is SetType) { - // axiom (forall params, d: Datatype {arg[d], #dt.ctor(params)} :: arg[d] ==> DtRank(d) < DtRank(#dt.ctor(params))); - // that is: - // axiom (forall params, d: Datatype {arg[Box(d)], #dt.ctor(params)} :: arg[Box(d)] ==> DtRank(d) < DtRank(#dt.ctor(params))); - CreateBoundVariables(ctor.Formals, out bvs, out args); - Bpl.Variable dVar = new Bpl.BoundVariable(arg.tok, new Bpl.TypedIdent(arg.tok, "d", predef.DatatypeType)); - bvs.Add(dVar); - Bpl.IdentifierExpr ie = new Bpl.IdentifierExpr(arg.tok, dVar); - Bpl.Expr inSet = Bpl.Expr.SelectTok(arg.tok, args[i], FunctionCall(arg.tok, BuiltinFunction.Box, null, ie)); - Bpl.Expr lhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ie); - var ct = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); - var rhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ct); - var trigger = new Bpl.Trigger(ctor.tok, true, new List { inSet, ct }); - q = new Bpl.ForallExpr(ctor.tok, bvs, trigger, Bpl.Expr.Imp(inSet, Bpl.Expr.Lt(lhs, rhs))); - sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, q, "Inductive set element rank")); - } else if (argType is MultiSetType) { - // axiom (forall params, d: Datatype {arg[d], #dt.ctor(params)} :: 0 < arg[d] ==> DtRank(d) < DtRank(#dt.ctor(params))); - // that is: - // axiom (forall params, d: Datatype {arg[Box(d)], #dt.ctor(params)} :: 0 < arg[Box(d)] ==> DtRank(d) < DtRank(#dt.ctor(params))); - CreateBoundVariables(ctor.Formals, out bvs, out args); - Bpl.Variable dVar = new Bpl.BoundVariable(arg.tok, new Bpl.TypedIdent(arg.tok, "d", predef.DatatypeType)); - bvs.Add(dVar); - Bpl.IdentifierExpr ie = new Bpl.IdentifierExpr(arg.tok, dVar); - var inMultiset = Bpl.Expr.SelectTok(arg.tok, args[i], FunctionCall(arg.tok, BuiltinFunction.Box, null, ie)); - Bpl.Expr ante = Bpl.Expr.Gt(inMultiset, Bpl.Expr.Literal(0)); - Bpl.Expr lhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ie); - var ct = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); - var rhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ct); - var trigger = new Bpl.Trigger(ctor.tok, true, new List { inMultiset, ct }); - q = new Bpl.ForallExpr(ctor.tok, bvs, trigger, Bpl.Expr.Imp(ante, Bpl.Expr.Lt(lhs, rhs))); - sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, q, "Inductive multiset element rank")); - } else if (argType is MapType) { - var finite = ((MapType)argType).Finite; - { - // axiom (forall params, d: DatatypeType - // { Map#Domain(arg)[$Box(d)], #dt.ctor(params) } - // Map#Domain(arg)[$Box(d)] ==> DtRank(d) < DtRank(#dt.ctor(params))); - CreateBoundVariables(ctor.Formals, out bvs, out args); - var dVar = new Bpl.BoundVariable(arg.tok, new Bpl.TypedIdent(arg.tok, "d", predef.DatatypeType)); - bvs.Add(dVar); - var ie = new Bpl.IdentifierExpr(arg.tok, dVar); - var f = finite ? BuiltinFunction.MapDomain : BuiltinFunction.IMapDomain; - var domain = FunctionCall(arg.tok, f, predef.MapType(arg.tok, finite, predef.BoxType, predef.BoxType), args[i]); - var inDomain = Bpl.Expr.SelectTok(arg.tok, domain, FunctionCall(arg.tok, BuiltinFunction.Box, null, ie)); - var lhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ie); - var ct = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); - var rhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ct); - var trigger = new Bpl.Trigger(ctor.tok, true, new List { inDomain, ct }); - q = new Bpl.ForallExpr(ctor.tok, bvs, trigger, Bpl.Expr.Imp(inDomain, Bpl.Expr.Lt(lhs, rhs))); - sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, q, "Inductive map key rank")); - } - { - // axiom(forall params, bx: Box :: - // { Map#Elements(arg)[bx], #dt.ctor(params) } - // Map#Domain(arg)[bx] ==> DtRank($Unbox(Map#Elements(arg)[bx]): DatatypeType) < DtRank(#dt.ctor(params))); - CreateBoundVariables(ctor.Formals, out bvs, out args); - var bxVar = new Bpl.BoundVariable(arg.tok, new Bpl.TypedIdent(arg.tok, "bx", predef.BoxType)); - bvs.Add(bxVar); - var ie = new Bpl.IdentifierExpr(arg.tok, bxVar); - var f = finite ? BuiltinFunction.MapDomain : BuiltinFunction.IMapDomain; - var domain = FunctionCall(arg.tok, f, predef.MapType(arg.tok, finite, predef.BoxType, predef.BoxType), args[i]); - var inDomain = Bpl.Expr.SelectTok(arg.tok, domain, ie); - var ef = finite ? BuiltinFunction.MapElements : BuiltinFunction.IMapElements; - var element = FunctionCall(arg.tok, ef, predef.MapType(arg.tok, finite, predef.BoxType, predef.BoxType), args[i]); - var elmt = Bpl.Expr.SelectTok(arg.tok, element, ie); - var unboxElmt = FunctionCall(arg.tok, BuiltinFunction.Unbox, predef.DatatypeType, elmt); - var lhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, unboxElmt); - var ct = FunctionCall(ctor.tok, ctor.FullName, predef.DatatypeType, args); - var rhs = FunctionCall(ctor.tok, BuiltinFunction.DtRank, null, ct); - var trigger = new Bpl.Trigger(ctor.tok, true, new List { inDomain, ct }); - q = new Bpl.ForallExpr(ctor.tok, bvs, trigger, Bpl.Expr.Imp(inDomain, Bpl.Expr.Lt(lhs, rhs))); - sink.AddTopLevelDeclaration(new Bpl.Axiom(ctor.tok, q, "Inductive map value rank")); - } - } - } - } - } - - { - // Add: - // function $IsA#Dt(G: Ty,d: DatatypeType): bool { - // Dt.Ctor0?(G, d) || Dt.Ctor1?(G, d) || ... - // } - var cases_dBv = new Bpl.Formal(dt.tok, new Bpl.TypedIdent(dt.tok, Bpl.TypedIdent.NoName, predef.DatatypeType), true); - var cases_resType = new Bpl.Formal(dt.tok, new Bpl.TypedIdent(dt.tok, Bpl.TypedIdent.NoName, Bpl.Type.Bool), false); - var cases_fn = new Bpl.Function(dt.tok, "$IsA#" + dt.FullSanitizedName, - new List { cases_dBv }, - cases_resType, - "Depth-one case-split function"); - - if (InsertChecksums) { - InsertChecksum(dt, cases_fn); - } - - sink.AddTopLevelDeclaration(cases_fn); - // and here comes the actual axiom: - { - Bpl.Expr d; - var dVar = BplBoundVar("d", predef.DatatypeType, out d); - var lhs = FunctionCall(dt.tok, cases_fn.Name, Bpl.Type.Bool, d); - Bpl.Expr cases_body = Bpl.Expr.False; - foreach (DatatypeCtor ctor in dt.Ctors) { - var disj = FunctionCall(ctor.tok, ctor.QueryField.FullSanitizedName, Bpl.Type.Bool, d); - cases_body = BplOr(cases_body, disj); - } - var ax = BplForall(new List { dVar }, BplTrigger(lhs), BplImp(lhs, cases_body)); - sink.AddTopLevelDeclaration(new Bpl.Axiom(dt.tok, ax, "Depth-one case-split axiom")); - } - } - - // The axiom above ($IsA#Dt(d) <==> Dt.Ctor0?(d) || Dt.Ctor1?(d)) gets triggered only with $IsA#Dt(d). The $IsA#Dt(d) - // predicate is generated only where the translation inserts it; in other words, the user cannot write any assertion - // that causes the $IsA#Dt(d) predicate to be emitted. This is what we want, because making the RHS disjunction be - // available too often makes performance go down. However, we do want to allow the disjunction to be introduced if the - // user explicitly talks about one of its disjuncts. To make this useful, we introduce the following axiom. Note that - // the DtType(d) information is available everywhere. - // axiom (forall G: Ty, d: DatatypeType :: - // { Dt.Ctor0?(G,d) } - // { Dt.Ctor1?(G,d) } - // $Is(d, T(G)) ==> Dt.Ctor0?(G,d) || Dt.Ctor1?(G,d) || ...); - { - List tyexprs; - var tyvars = MkTyParamBinders(dt.TypeArgs, out tyexprs); - Bpl.Expr d; - var dVar = BplBoundVar("d", predef.DatatypeType, out d); - var d_is = MkIs(d, ClassTyCon(dt, tyexprs)); - Bpl.Expr cases_body = Bpl.Expr.False; - Bpl.Trigger tr = null; - foreach (DatatypeCtor ctor in dt.Ctors) { - var disj = FunctionCall(ctor.tok, ctor.QueryField.FullSanitizedName, Bpl.Type.Bool, d); - cases_body = BplOr(cases_body, disj); - tr = new Bpl.Trigger(ctor.tok, true, new List { disj, d_is }, tr); - } - var body = Bpl.Expr.Imp(d_is, cases_body); - var ax = BplForall(Snoc(tyvars, dVar), tr, body); - sink.AddTopLevelDeclaration(new Bpl.Axiom(dt.tok, ax, "Questionmark data type disjunctivity")); - } - - if (dt is IndDatatypeDecl) { - var dtEqualName = dt.FullSanitizedName + "#Equal"; - - // Add function Dt#Equal(DatatypeType, DatatypeType): bool; - // For each constructor Ctor(x: X, y: Y), add an axiom of the form - // forall a, b :: - // { Dt#Equal(a, b), Ctor?(a) } - // { Dt#Equal(a, b), Ctor?(b) } - // Ctor?(a) && Ctor?(b) - // ==> - // (Dt#Equal(a, b) <==> - // X#Equal(a.x, b.x) && - // Y#Equal(a.y, b.y) - // ) - // where X#Equal is the equality predicate for type X and a.x denotes Dtor#x(a), and similarly - // for Y and b. - // Except, in the event that the datatype has exactly one constructor, then instead generate: - // forall a, b :: - // { Dt#Equal(a, b) } - // true - // ==> - // ...as before - { - var args = new List(); - args.Add(new Bpl.Formal(dt.tok, new Bpl.TypedIdent(dt.tok, Bpl.TypedIdent.NoName, predef.DatatypeType), false)); - args.Add(new Bpl.Formal(dt.tok, new Bpl.TypedIdent(dt.tok, Bpl.TypedIdent.NoName, predef.DatatypeType), false)); - var ctorEqualResult = new Bpl.Formal(dt.tok, new Bpl.TypedIdent(dt.tok, Bpl.TypedIdent.NoName, Bpl.Type.Bool), false); - sink.AddTopLevelDeclaration(new Bpl.Function(dt.tok, dtEqualName, args, ctorEqualResult, "Datatype extensional equality declaration")); - - Bpl.Expr a; var aVar = BplBoundVar("a", predef.DatatypeType, out a); - Bpl.Expr b; var bVar = BplBoundVar("b", predef.DatatypeType, out b); - - var dtEqual = FunctionCall(dt.tok, dtEqualName, Bpl.Type.Bool, a, b); - - foreach (var ctor in dt.Ctors) { - Bpl.Trigger trigger; - Bpl.Expr ante; - if (dt.Ctors.Count == 1) { - ante = Bpl.Expr.True; - trigger = BplTrigger(dtEqual); - } else { - var ctorQ = GetReadonlyField(ctor.QueryField); - var ctorQa = FunctionCall(ctor.tok, ctorQ.Name, Bpl.Type.Bool, a); - var ctorQb = FunctionCall(ctor.tok, ctorQ.Name, Bpl.Type.Bool, b); - ante = BplAnd(ctorQa, ctorQb); - trigger = dt.Ctors.Count == 1 ? BplTrigger(dtEqual) : - new Bpl.Trigger(ctor.tok, true, new List { dtEqual, ctorQa }, - new Bpl.Trigger(ctor.tok, true, new List { dtEqual, ctorQb })); - } - - Bpl.Expr eqs = Bpl.Expr.True; - for (var i = 0; i < ctor.Formals.Count; i++) { - var arg = ctor.Formals[i]; - var dtor = GetReadonlyField(ctor.Destructors[i]); - var dtorA = FunctionCall(ctor.tok, dtor.Name, TrType(arg.Type), a); - var dtorB = FunctionCall(ctor.tok, dtor.Name, TrType(arg.Type), b); - var eq = TypeSpecificEqual(ctor.tok, arg.Type, dtorA, dtorB); - eqs = BplAnd(eqs, eq); - } - - var ax = BplForall(new List { aVar, bVar }, trigger, Bpl.Expr.Imp(ante, Bpl.Expr.Iff(dtEqual, eqs))); - sink.AddTopLevelDeclaration(new Bpl.Axiom(dt.tok, ax, string.Format("Datatype extensional equality definition: {0}", ctor.FullName))); - } - } - - // Add extensionality axiom: forall a, b :: { Dt#Equal(a, b) } Dt#Equal(a, b) <==> a == b - { - Bpl.Expr a; var aVar = BplBoundVar("a", predef.DatatypeType, out a); - Bpl.Expr b; var bVar = BplBoundVar("b", predef.DatatypeType, out b); - - var lhs = FunctionCall(dt.tok, dtEqualName, Bpl.Type.Bool, a, b); - var rhs = Bpl.Expr.Eq(a, b); - - var ax = BplForall(new List { aVar, bVar }, BplTrigger(lhs), Bpl.Expr.Iff(lhs, rhs)); - sink.AddTopLevelDeclaration(new Bpl.Axiom(dt.tok, ax, string.Format("Datatype extensionality axiom: {0}", dt.FullName))); - } - } - - if (dt is CoDatatypeDecl) { - var codecl = (CoDatatypeDecl)dt; - - Func MinusOne = k => { - if (k == null) { - return null; - } else if (k.Type.IsInt) { - return Bpl.Expr.Sub(k, Bpl.Expr.Literal(1)); - } else { - return FunctionCall(k.tok, "ORD#Minus", k.Type, k, FunctionCall(k.tok, "ORD#FromNat", k.Type, Bpl.Expr.Literal(1))); - }; - }; - - Action, List>, List, List, List, Bpl.Variable, Bpl.Expr, Bpl.Expr, Bpl.Expr, Bpl.Expr, Bpl.Expr, Bpl.Expr, Bpl.Expr, Bpl.Expr>> CoAxHelper = (typeOfK, K) => { - Func> renew = s => - Map(codecl.TypeArgs, tp => - new TypeParameter(tp.tok, tp.Name + "#" + s, tp.PositionalIndex, tp.Parent)); - List typaramsL = renew("l"), typaramsR = renew("r"); - List lexprs; var lvars = MkTyParamBinders(typaramsL, out lexprs); - List rexprs; var rvars = MkTyParamBinders(typaramsR, out rexprs); - Func, List> Types = l => Map(l, tp => (Type)new UserDefinedType(tp)); - var tyargs = Tuple.Create(Types(typaramsL), Types(typaramsR)); - - var vars = Concat(lvars, rvars); - - Bpl.Expr k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit; - Bpl.Variable kVar; - if (typeOfK != null) { - kVar = BplBoundVar("k", typeOfK, out k); vars.Add(kVar); - if (typeOfK.IsInt) { - kIsValid = Bpl.Expr.Le(Bpl.Expr.Literal(0), k); - kIsNonZero = Bpl.Expr.Neq(Bpl.Expr.Literal(0), k); - kHasSuccessor = Bpl.Expr.Lt(Bpl.Expr.Literal(0), k); - kIsLimit = Bpl.Expr.False; - } else { - kIsValid = Bpl.Expr.True; - kIsNonZero = Bpl.Expr.Neq(k, FunctionCall(k.tok, "ORD#FromNat", Bpl.Type.Int, Bpl.Expr.Literal(0))); - kHasSuccessor = Bpl.Expr.Lt(Bpl.Expr.Literal(0), FunctionCall(k.tok, "ORD#Offset", Bpl.Type.Int, k)); - kIsLimit = FunctionCall(k.tok, "ORD#IsLimit", Bpl.Type.Bool, k); - } - } else { - kVar = null; k = null; - kIsValid = Bpl.Expr.True; - kIsNonZero = Bpl.Expr.True; - kHasSuccessor = Bpl.Expr.True; - kIsLimit = Bpl.Expr.True; - } - var ly = BplBoundVar("ly", predef.LayerType, vars); - var d0 = BplBoundVar("d0", predef.DatatypeType, vars); - var d1 = BplBoundVar("d1", predef.DatatypeType, vars); - - K(tyargs, vars, lexprs, rexprs, kVar, k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit, ly, d0, d1); - }; - - Action AddAxioms = typeOfK => { - { - // Add two copies of the type parameter lists! - var args = MkTyParamFormals(Concat(GetTypeParams(dt), GetTypeParams(dt)), false); - if (typeOfK != null) { - args.Add(BplFormalVar(null, typeOfK, true)); - } - args.Add(BplFormalVar(null, predef.LayerType, true)); - args.Add(BplFormalVar(null, predef.DatatypeType, true)); - args.Add(BplFormalVar(null, predef.DatatypeType, true)); - var r = BplFormalVar(null, Bpl.Type.Bool, false); - var fn_nm = typeOfK != null ? CoPrefixName(codecl) : CoEqualName(codecl); - var fn = new Bpl.Function(dt.tok, fn_nm, args, r); - if (InsertChecksums) { - InsertChecksum(dt, fn); - } - sink.AddTopLevelDeclaration(fn); - } - - // axiom (forall G0,...,Gn : Ty, k: int, ly : Layer, d0, d1: DatatypeType :: - // { Eq(G0, .., Gn, S(ly), k, d0, d1) } - // Is(d0, T(G0, .., Gn)) && Is(d1, T(G0, ... Gn)) ==> - // (Eq(G0, .., Gn, S(ly), k, d0, d1) - // <==> - // (0 < k.Offset ==> - // (d0.Nil? && d1.Nil?) || - // (d0.Cons? && d1.Cons? && d0.head == d1.head && Eq(G0, .., Gn, ly, k-1, d0.tail, d1.tail))) && - // (k != 0 && k.IsLimit ==> // for prefix equality only - // FullEq(G0, .., Gn, ly, d0.tail, d1.tail))) // for prefix equality only - CoAxHelper(typeOfK, (tyargs, vars, lexprs, rexprs, kVar, k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit, ly, d0, d1) => { - var eqDt = CoEqualCall(codecl, lexprs, rexprs, k, LayerSucc(ly), d0, d1); - var iss = BplAnd(MkIs(d0, ClassTyCon(dt, lexprs)), MkIs(d1, ClassTyCon(dt, rexprs))); - var body = BplImp( - iss, - BplIff(eqDt, - BplAnd( - BplImp(kHasSuccessor, BplOr(CoPrefixEquality(dt.tok, codecl, tyargs.Item1, tyargs.Item2, MinusOne(k), ly, d0, d1))), - k == null ? Bpl.Expr.True : BplImp(BplAnd(kIsNonZero, kIsLimit), CoEqualCall(codecl, tyargs.Item1, tyargs.Item2, null, ly, d0, d1))))); - var ax = BplForall(vars, BplTrigger(eqDt), body); - sink.AddTopLevelDeclaration(new Bpl.Axiom(dt.tok, ax, "Layered co-equality axiom")); - }); - - // axiom (forall G0,...,Gn : Ty, k: int, ly : Layer, d0, d1: DatatypeType :: - // { Eq(G0, .., Gn, S(ly), k, d0, d1) } - // 0 < k ==> - // (Eq(G0, .., Gn, S(ly), k, d0, d1) <==> - // Eq(G0, .., Gn, ly, k, d0, d)) - CoAxHelper(typeOfK, (tyargs, vars, lexprs, rexprs, kVar, k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit, ly, d0, d1) => { - var eqDtSL = CoEqualCall(codecl, lexprs, rexprs, k, LayerSucc(ly), d0, d1); - var eqDtL = CoEqualCall(codecl, lexprs, rexprs, k, ly, d0, d1); - var body = BplImp(kIsNonZero, BplIff(eqDtSL, eqDtL)); - var ax = BplForall(vars, BplTrigger(eqDtSL), body); - sink.AddTopLevelDeclaration(new Bpl.Axiom(dt.tok, ax, "Unbump layer co-equality axiom")); - }); - }; - - AddAxioms(null); // Add the above axioms for $Equal - - // axiom (forall d0, d1: DatatypeType, k: int :: { $Equal(d0, d1) } :: Equal(d0, d1) <==> d0 == d1); - CoAxHelper(null, (tyargs, vars, lexprs, rexprs, kVar, k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit, ly, d0, d1) => { - var Eq = CoEqualCall(codecl, lexprs, rexprs, k, LayerSucc(ly), d0, d1); - var equal = Bpl.Expr.Eq(d0, d1); - sink.AddTopLevelDeclaration(new Axiom(dt.tok, - BplForall(vars, BplTrigger(Eq), BplIff(Eq, equal)), - "Equality for codatatypes")); - }); - - Bpl.Type theTypeOfK = predef.BigOrdinalType; - AddAxioms(predef.BigOrdinalType); // Add the above axioms for $PrefixEqual - - // The connection between the full codatatype equality and its prefix version - // axiom (forall d0, d1: DatatypeType :: $Eq#Dt(d0, d1) <==> - // (forall k: int :: 0 <= k ==> $PrefixEqual#Dt(k, d0, d1))); - CoAxHelper(theTypeOfK, (tyargs, vars, lexprs, rexprs, kVar, k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit, ly, d0, d1) => { - var Eq = CoEqualCall(codecl, lexprs, rexprs, null, LayerSucc(ly), d0, d1); - var PEq = CoEqualCall(codecl, lexprs, rexprs, k, LayerSucc(ly), d0, d1); - vars.Remove(kVar); - sink.AddTopLevelDeclaration(new Axiom(dt.tok, - BplForall(vars, BplTrigger(Eq), BplIff(Eq, BplForall(kVar, BplTrigger(PEq), BplImp(kIsValid, PEq)))), - "Coequality and prefix equality connection")); - }); - // In addition, the following special case holds for $Eq#Dt: - // axiom (forall d0, d1: DatatypeType :: $Eq#Dt(d0, d1) <== - // (forall k: int :: 0 <= k ==> $PrefixEqual#Dt(ORD#FromNat(k), d0, d1))); - if (!theTypeOfK.IsInt) { - CoAxHelper(Bpl.Type.Int, (tyargs, vars, lexprs, rexprs, kVar, k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit, ly, d0, d1) => { - var Eq = CoEqualCall(codecl, lexprs, rexprs, null, LayerSucc(ly), d0, d1); - var PEq = CoEqualCall(codecl, lexprs, rexprs, FunctionCall(k.tok, "ORD#FromNat", predef.BigOrdinalType, k), LayerSucc(ly), d0, d1); - vars.Remove(kVar); - sink.AddTopLevelDeclaration(new Axiom(dt.tok, - BplForall(vars, BplTrigger(Eq), BplImp(BplForall(kVar, BplTrigger(PEq), BplImp(kIsValid, PEq)), Eq)), - "Coequality and prefix equality connection")); - }); - } - - // A consequence of the definition of prefix equalities is the following: - // axiom (forall k, m: int, d0, d1: DatatypeType :: 0 <= k <= m && $PrefixEq#Dt(m, d0, d1) ==> $PrefixEq#0#Dt(k, d0, d1)); - CoAxHelper(theTypeOfK, (tyargs, vars, lexprs, rexprs, kVar, k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit, ly, d0, d1) => { - var m = BplBoundVar("m", k.Type, vars); - var PEqK = CoEqualCall(codecl, lexprs, rexprs, k, LayerSucc(ly), d0, d1); - var PEqM = CoEqualCall(codecl, lexprs, rexprs, m, LayerSucc(ly), d0, d1); - Bpl.Expr kLtM; - if (k.Type.IsInt) { - kLtM = Bpl.Expr.Lt(k, m); - } else { - kLtM = FunctionCall(dt.tok, "ORD#Less", Bpl.Type.Bool, k, m); - } - sink.AddTopLevelDeclaration(new Axiom(dt.tok, - BplForall(vars, - new Bpl.Trigger(dt.tok, true, new List { PEqK, PEqM }), - BplImp(BplAnd(BplAnd(kIsValid, kLtM), PEqM), PEqK)), - "Prefix equality consequence")); - }); - // With the axioms above, going from d0==d1 to a prefix equality requires going via the full codatatype - // equality, which in turn requires the full codatatype equality to be present. The following axiom - // provides a shortcut: - // axiom (forall d0, d1: DatatypeType, k: int :: d0 == d1 && 0 <= k ==> $PrefixEqual#_module.Stream(k, d0, d1)); - CoAxHelper(theTypeOfK, (tyargs, vars, lexprs, rexprs, kVar, k, kIsValid, kIsNonZero, kHasSuccessor, kIsLimit, ly, d0, d1) => { - var equal = Bpl.Expr.Eq(d0, d1); - var PEq = CoEqualCall(codecl, lexprs, rexprs, k, LayerSucc(ly), d0, d1); - var trigger = BplTrigger(PEq); - sink.AddTopLevelDeclaration(new Axiom(dt.tok, - BplForall(vars, trigger, BplImp(BplAnd(equal, kIsValid), PEq)), "Prefix equality shortcut")); - }); - } - } /// /// Return a sequence of expressions whose conjunction denotes a memberwise equality of "dt". Recursive @@ -2106,201 +1490,6 @@ public Bpl.Expr GetArrayIndexFieldName(IToken tok, List indices) { return fieldName; } - void AddClassMembers(TopLevelDeclWithMembers c, bool includeAllMethods) { - Contract.Requires(sink != null && predef != null); - Contract.Requires(c != null); - Contract.Ensures(fuelContext == Contract.OldValue(fuelContext)); - Contract.Assert(VisibleInScope(c)); - - sink.AddTopLevelDeclaration(GetClass(c)); - if (c is ArrayClassDecl) { - // classes.Add(c, predef.ClassDotArray); - AddAllocationAxiom(null, (ArrayClassDecl)c, true); - } - - // Add $Is and $IsAlloc for this class : - // axiom (forall p: ref, G: Ty :: - // { $Is(p, TClassA(G), h) } - // $Is(p, TClassA(G), h) <=> (p == null || dtype(p) == TClassA(G)); - // axiom (forall p: ref, h: Heap, G: Ty :: - // { $IsAlloc(p, TClassA(G), h) } - // $IsAlloc(p, TClassA(G), h) => (p == null || h[p, alloc]); - MapM(c is ClassDecl ? Bools : new List(), is_alloc => { - List tyexprs; - var vars = MkTyParamBinders(GetTypeParams(c), out tyexprs); - - var o = BplBoundVar("$o", predef.RefType, vars); - - Bpl.Expr body, is_o; - Bpl.Expr o_null = Bpl.Expr.Eq(o, predef.Null); - Bpl.Expr o_ty = ClassTyCon(c, tyexprs); - string name; - - if (is_alloc) { - name = c + ": Class $IsAlloc"; - var h = BplBoundVar("$h", predef.HeapType, vars); - // $IsAlloc(o, ..) - is_o = MkIsAlloc(o, o_ty, h); - body = BplIff(is_o, BplOr(o_null, IsAlloced(c.tok, h, o))); - } else { - name = c + ": Class $Is"; - // $Is(o, ..) - is_o = MkIs(o, o_ty); - Bpl.Expr rhs; - if (c == program.BuiltIns.ObjectDecl) { - rhs = Bpl.Expr.True; - } else if (c is TraitDecl) { - //generating $o == null || implements$J(dtype(x), typeArgs) - var t = (TraitDecl)c; - var dtypeFunc = FunctionCall(o.tok, BuiltinFunction.DynamicType, null, o); - var implementsJ_Arguments = new List { dtypeFunc }; // TODO: also needs type parameters - implementsJ_Arguments.AddRange(tyexprs); - Bpl.Expr implementsFunc = FunctionCall(t.tok, "implements$" + t.FullSanitizedName, Bpl.Type.Bool, implementsJ_Arguments); - rhs = BplOr(o_null, implementsFunc); - } else { - rhs = BplOr(o_null, DType(o, o_ty)); - } - body = BplIff(is_o, rhs); - } - - sink.AddTopLevelDeclaration(new Bpl.Axiom(c.tok, BplForall(vars, BplTrigger(is_o), body), name)); - }); - - if (c is TraitDecl) { - //this adds: function implements$J(Ty, typeArgs): bool; - var arg_ref = new Bpl.Formal(c.tok, new Bpl.TypedIdent(c.tok, "ty", predef.Ty), true); - var vars = new List { arg_ref }; - vars.AddRange(MkTyParamFormals(GetTypeParams(c))); - var res = new Bpl.Formal(c.tok, new Bpl.TypedIdent(c.tok, Bpl.TypedIdent.NoName, Bpl.Type.Bool), false); - var implement_intr = new Bpl.Function(c.tok, "implements$" + c.FullSanitizedName, vars, res); - sink.AddTopLevelDeclaration(implement_intr); - } else if (c is ClassDecl) { - //this adds: axiom implements$J(class.C, typeInstantiations); - var vars = MkTyParamBinders(GetTypeParams(c), out var tyexprs); - - foreach (var parent in ((ClassDecl)c).ParentTraits) { - var trait = (TraitDecl)((NonNullTypeDecl)((UserDefinedType)parent).ResolvedClass).ViewAsClass; - var arg = ClassTyCon(c, tyexprs); - var args = new List { arg }; - foreach (var targ in parent.TypeArgs) { - args.Add(TypeToTy(targ)); - } - var expr = FunctionCall(c.tok, "implements$" + trait.FullSanitizedName, Bpl.Type.Bool, args); - var implements_axiom = new Bpl.Axiom(c.tok, BplForall(vars, null, expr)); - sink.AddTopLevelDeclaration(implements_axiom); - } - } - - foreach (MemberDecl member in c.Members.FindAll(VisibleInScope)) { - Contract.Assert(isAllocContext == null); - currentDeclaration = member; - if (member is Field) { - Field f = (Field)member; - if (f is ConstantField) { - // The following call has the side effect of idempotently creating and adding the function to the sink's top-level declarations - Contract.Assert(currentModule == null); - currentModule = f.EnclosingClass.EnclosingModuleDefinition; - var oldFuelContext = fuelContext; - fuelContext = FuelSetting.NewFuelContext(f); - var boogieFunction = GetReadonlyField(f); - fuelContext = oldFuelContext; - currentModule = null; - AddAllocationAxiom(f, c); - } else { - if (f.IsMutable) { - Bpl.Constant fc = GetField(f); - sink.AddTopLevelDeclaration(fc); - } else { - Bpl.Function ff = GetReadonlyField(f); - if (ff != predef.ArrayLength) { - sink.AddTopLevelDeclaration(ff); - } - } - AddAllocationAxiom(f, c); - } - - } else if (member is Function function) { - AddFunction_Top(function, includeAllMethods); - } else if (member is Method method) { - AddMethod_Top(method, false, includeAllMethods); - } else { - Contract.Assert(false); throw new cce.UnreachableException(); // unexpected member - } - } - } - - void AddFunction_Top(Function f, bool includeAllMethods) { - FuelContext oldFuelContext = this.fuelContext; - this.fuelContext = FuelSetting.NewFuelContext(f); - isAllocContext = new IsAllocContext(true); - - AddClassMember_Function(f); - - if (!f.IsBuiltin && InVerificationScope(f)) { - AddWellformednessCheck(f); - if (f.OverriddenFunction != null) { //it means that f is overriding its associated parent function - AddFunctionOverrideCheckImpl(f); - } - } - if (f is ExtremePredicate cop) { - AddClassMember_Function(cop.PrefixPredicate); - // skip the well-formedness check, because it has already been done for the extreme predicate - } else if (f.ByMethodDecl != null) { - AddMethod_Top(f.ByMethodDecl, true, includeAllMethods); - } - - this.fuelContext = oldFuelContext; - isAllocContext = null; - } - - void AddMethod_Top(Method m, bool isByMethod, bool includeAllMethods) { - if (!includeAllMethods && !InVerificationScope(m) && !referencedMembers.Contains(m)) { - // do nothing - return; - } - - FuelContext oldFuelContext = this.fuelContext; - this.fuelContext = FuelSetting.NewFuelContext(m); - - // wellformedness check for method specification - if (m.EnclosingClass is IteratorDecl && m == ((IteratorDecl)m.EnclosingClass).Member_MoveNext) { - // skip the well-formedness check, because it has already been done for the iterator - } else { - if (!isByMethod) { - var proc = AddMethod(m, MethodTranslationKind.SpecWellformedness); - sink.AddTopLevelDeclaration(proc); - if (InVerificationScope(m)) { - AddMethodImpl(m, proc, true); - } - } - if (m.OverriddenMethod != null && InVerificationScope(m)) //method has overrided a parent method - { - var procOverrideChk = AddMethod(m, MethodTranslationKind.OverrideCheck); - sink.AddTopLevelDeclaration(procOverrideChk); - AddMethodOverrideCheckImpl(m, procOverrideChk); - } - } - // the method spec itself - if (!isByMethod) { - sink.AddTopLevelDeclaration(AddMethod(m, MethodTranslationKind.Call)); - } - if (m is ExtremeLemma) { - // Let the CoCall and Impl forms to use m.PrefixLemma signature and specification (and - // note that m.PrefixLemma.Body == m.Body. - m = ((ExtremeLemma)m).PrefixLemma; - sink.AddTopLevelDeclaration(AddMethod(m, MethodTranslationKind.CoCall)); - } - if (m.Body != null && InVerificationScope(m)) { - // ...and its implementation - assertionCount = 0; - var proc = AddMethod(m, MethodTranslationKind.Implementation); - sink.AddTopLevelDeclaration(proc); - AddMethodImpl(m, proc, false); - } - Reset(); - this.fuelContext = oldFuelContext; - } - /// /// Returns true if the body of function "f" is available in module "context". /// This happens when the following conditions all hold: @@ -2321,47 +1510,6 @@ static bool IsOpaqueRevealLemma(Method m) { return Attributes.Contains(m.Attributes, "opaque_reveal"); } - private void AddClassMember_Function(Function f) { - Contract.Ensures(currentModule == null && codeContext == null); - Contract.Ensures(currentModule == null && codeContext == null); - - currentModule = f.EnclosingClass.EnclosingModuleDefinition; - codeContext = f; - - // declare function - AddFunction(f); - // add synonym axiom - if (f.IsFuelAware()) { - AddLayerSynonymAxiom(f); - AddFuelSynonymAxiom(f); - } - // add frame axiom - if (AlwaysUseHeap || f.ReadsHeap) { - AddFrameAxiom(f); - } - // add consequence axiom - AddFunctionConsequenceAxiom(f, f.Ens); - // add definition axioms, suitably specialized for literals - if (f.Body != null && RevealedInScope(f)) { - AddFunctionAxiom(f, f.Body.Resolved); - } else { - // for body-less functions, at least generate its #requires function - var b = FunctionAxiom(f, null, null); - Contract.Assert(b == null); - } - // for a function in a class C that overrides a function in a trait J, add an axiom that connects J.F and C.F - if (f.OverriddenFunction != null) { - sink.AddTopLevelDeclaration(FunctionOverrideAxiom(f.OverriddenFunction, f)); - } - - // supply the connection between least/greatest predicates and prefix predicates - if (f is ExtremePredicate) { - AddPrefixPredicateAxioms(((ExtremePredicate)f).PrefixPredicate); - } - - Reset(); - } - void AddIteratorSpecAndBody(IteratorDecl iter) { Contract.Requires(iter != null); Contract.Ensures(fuelContext == Contract.OldValue(fuelContext)); @@ -2763,12 +1911,12 @@ public Specialization(IVariable formal, MatchCase mc, Specialization prev, Trans } } - void AddFunctionAxiom(Function f, Expression body) { + void AddFunctionAxiom(Bpl.Function boogieFunction, Function f, Expression body) { Contract.Requires(f != null); Contract.Requires(body != null); - var ax = FunctionAxiom(f, body, null); - sink.AddTopLevelDeclaration(ax); + var ax = GetFunctionAxiom(f, body, null); + AddOtherDefinition(boogieFunction, ax); // TODO(namin) Is checking f.Reads.Count==0 excluding Valid() of BinaryTree in the right way? // I don't see how this in the decreasing clause would help there. if (!(f is ExtremePredicate) && f.CoClusterTarget == Function.CoCallClusterInvolvement.None && f.Reads.Count == 0) { @@ -2800,16 +1948,16 @@ void AddFunctionAxiom(Function f, Expression body) { Contract.Assert(decs.Count <= allFormals.Count); if (0 < decs.Count && decs.Count < allFormals.Count) { - ax = FunctionAxiom(f, body, decs); - sink.AddTopLevelDeclaration(ax); + var decreasesAxiom = GetFunctionAxiom(f, body, decs); + AddOtherDefinition(boogieFunction, decreasesAxiom); } - ax = FunctionAxiom(f, body, allFormals); - sink.AddTopLevelDeclaration(ax); + var formalsAxiom = GetFunctionAxiom(f, body, allFormals); + AddOtherDefinition(boogieFunction, formalsAxiom); } } - void AddFunctionConsequenceAxiom(Function f, List ens) { + void AddFunctionConsequenceAxiom(Boogie.Function boogieFunction, Function f, List ens) { Contract.Requires(f != null); Contract.Requires(predef != null); Contract.Requires(f.EnclosingClass != null); @@ -2980,7 +2128,8 @@ void AddFunctionConsequenceAxiom(Function f, List ens) { Bpl.Expr ax = BplForall(f.tok, new List(), formals, null, tr, Bpl.Expr.Imp(ante, post)); var activate = AxiomActivation(f, etran); string comment = "consequence axiom for " + f.FullSanitizedName; - sink.AddTopLevelDeclaration(new Bpl.Axiom(f.tok, Bpl.Expr.Imp(activate, ax), comment)); + var consequenceAxiom = new Bpl.Axiom(f.tok, Bpl.Expr.Imp(activate, ax), comment); + AddOtherDefinition(boogieFunction, consequenceAxiom); if (CommonHeapUse && !readsHeap) { whr = GetWhereClause(f.tok, funcAppl, f.ResultType, etranHeap, NOALLOC, true); @@ -2990,7 +2139,8 @@ void AddFunctionConsequenceAxiom(Function f, List ens) { ante = BplAnd(ante, goodHeap); ax = BplForall(f.tok, new List(), formals, null, BplTrigger(whr), Bpl.Expr.Imp(ante, whr)); activate = AxiomActivation(f, etran); - sink.AddTopLevelDeclaration(new Bpl.Axiom(f.tok, Bpl.Expr.Imp(activate, ax))); + var heapConsequenceAxiom = new Bpl.Axiom(f.tok, Bpl.Expr.Imp(activate, ax)); + AddOtherDefinition(boogieFunction, heapConsequenceAxiom); } } } @@ -3013,11 +2163,11 @@ Bpl.Expr AxiomActivation(Function f, ExpressionTranslator etran) { /// The list of formals "lits" is allowed to contain an object of type ThisSurrogate, which indicates that /// the receiver parameter of the function should be included among the lit formals. /// - Bpl.Axiom FunctionAxiom(Function f, Expression/*?*/ body, List/*?*/ lits) { + private Axiom GetFunctionAxiom(Function f, Expression body, List lits) { Contract.Requires(f != null); Contract.Requires(predef != null); Contract.Requires(f.EnclosingClass != null); - Contract.Ensures((Contract.Result() == null) == (body == null)); // return null iff body is null + Contract.Ensures((Contract.Result() == null) == (body == null)); // return null iff body is null // This method generates the Definition Axiom, suitably modified according to the optional "lits". // @@ -3075,6 +2225,7 @@ Bpl.Axiom FunctionAxiom(Function f, Expression/*?*/ body, List/*?*/ lits foreach (AttributedExpression e in f.Req) { readsHeap = readsHeap || UsesHeap(e.E); } + if (body != null && UsesHeap(body)) { readsHeap = true; } @@ -3087,9 +2238,9 @@ Bpl.Axiom FunctionAxiom(Function f, Expression/*?*/ body, List/*?*/ lits f.ReadsHeap ? new Bpl.IdentifierExpr(f.tok, predef.HeapVarName, predef.HeapType) : null, new Bpl.IdentifierExpr(f.tok, bvPrevHeap)); } else { - etran = readsHeap ? - new ExpressionTranslator(this, predef, f.tok) : - new ExpressionTranslator(this, predef, (Bpl.Expr)null); + etran = readsHeap + ? new ExpressionTranslator(this, predef, f.tok) + : new ExpressionTranslator(this, predef, (Bpl.Expr)null); } // quantify over the type arguments, and add them first to the arguments @@ -3121,21 +2272,25 @@ Bpl.Axiom FunctionAxiom(Function f, Expression/*?*/ body, List/*?*/ lits // ante: $IsGoodHeap($prevHeap) && ante = BplAnd(ante, FunctionCall(f.tok, BuiltinFunction.IsGoodHeap, null, etran.Old.HeapExpr)); } + Bpl.Expr goodHeap = null; var bv = new Bpl.BoundVariable(f.tok, new Bpl.TypedIdent(f.tok, predef.HeapVarName, predef.HeapType)); if (AlwaysUseHeap || f.ReadsHeap) { funcFormals.Add(bv); } + if (AlwaysUseHeap || f.ReadsHeap) { args.Add(new Bpl.IdentifierExpr(f.tok, bv)); reqFuncArguments.Add(new Bpl.IdentifierExpr(f.tok, bv)); } + // ante: $IsGoodHeap($Heap) && $HeapSucc($prevHeap, $Heap) && this != null && formals-have-the-expected-types && if (readsHeap) { forallFormals.Add(bv); goodHeap = FunctionCall(f.tok, BuiltinFunction.IsGoodHeap, null, etran.HeapExpr); ante = BplAnd(ante, goodHeap); } + if (f is TwoStateFunction && f.ReadsHeap) { ante = BplAnd(ante, HeapSucc(etran.Old.HeapExpr, etran.HeapExpr)); } @@ -3157,6 +2312,7 @@ Bpl.Axiom FunctionAxiom(Function f, Expression/*?*/ body, List/*?*/ lits } else { args.Add(bvThisIdExpr); } + // add well-typedness conjunct to antecedent Type thisType = Resolver.GetReceiverType(f.tok, f); Bpl.Expr wh = Bpl.Expr.And( @@ -3166,11 +2322,12 @@ Bpl.Axiom FunctionAxiom(Function f, Expression/*?*/ body, List/*?*/ lits } var typeMap = new Dictionary(); - var anteReqAxiom = ante; // note that antecedent so far is the same for #requires axioms, even the receiver parameter of a two-state function + var anteReqAxiom = ante; // note that antecedent so far is the same for #requires axioms, even the receiver parameter of a two-state function var substMap = new Dictionary(); foreach (Formal p in f.Formals) { var pType = Resolver.SubstType(p.Type, typeMap); - bv = new Bpl.BoundVariable(p.tok, new Bpl.TypedIdent(p.tok, p.AssignUniqueName(currentDeclaration.IdGenerator), TrType(pType))); + bv = new Bpl.BoundVariable(p.tok, + new Bpl.TypedIdent(p.tok, p.AssignUniqueName(currentDeclaration.IdGenerator), TrType(pType))); forallFormals.Add(bv); funcFormals.Add(bv); reqFuncArguments.Add(new Bpl.IdentifierExpr(f.tok, bv)); @@ -3178,24 +2335,32 @@ Bpl.Axiom FunctionAxiom(Function f, Expression/*?*/ body, List/*?*/ lits if (lits != null && lits.Contains(p) && !substMap.ContainsKey(p)) { args.Add(Lit(formal)); var ie = new IdentifierExpr(p.tok, p.AssignUniqueName(f.IdGenerator)); - ie.Var = p; ie.Type = ie.Var.Type; + ie.Var = p; + ie.Type = ie.Var.Type; var l = new UnaryOpExpr(p.tok, UnaryOpExpr.Opcode.Lit, ie); l.Type = ie.Var.Type; substMap.Add(p, l); } else { args.Add(formal); } + // add well-typedness conjunct to antecedent Bpl.Expr wh = GetWhereClause(p.tok, formal, pType, p.IsOld ? etran.Old : etran, NOALLOC); - if (wh != null) { ante = BplAnd(ante, wh); } + if (wh != null) { + ante = BplAnd(ante, wh); + } + wh = GetWhereClause(p.tok, formal, pType, etran, NOALLOC); - if (wh != null) { anteReqAxiom = BplAnd(anteReqAxiom, wh); } + if (wh != null) { + anteReqAxiom = BplAnd(anteReqAxiom, wh); + } } Bpl.Expr pre = Bpl.Expr.True; foreach (AttributedExpression req in f.Req) { pre = BplAnd(pre, etran.TrExpr(Substitute(req.E, receiverReplacement, substMap))); } + var preReqAxiom = pre; if (f is TwoStateFunction) { // Checked preconditions that old parameters really existed in previous state @@ -3206,8 +2371,10 @@ Bpl.Axiom FunctionAxiom(Function f, Expression/*?*/ body, List/*?*/ lits var dafnyFormalIdExpr = new IdentifierExpr(formal.tok, formal); preRA = BplAnd(preRA, MkIsAlloc(etran.TrExpr(dafnyFormalIdExpr), formal.Type, etran.Old.HeapExpr)); } + index++; } + preReqAxiom = BplAnd(preRA, pre); } @@ -3222,18 +2389,21 @@ Bpl.Axiom FunctionAxiom(Function f, Expression/*?*/ body, List/*?*/ lits var appl = FunctionCall(f.tok, RequiresName(f), Bpl.Type.Bool, reqFuncArguments); Bpl.Trigger trig = BplTriggerHeap(this, f.tok, appl, readsHeap ? etran.HeapExpr : null); // axiom (forall params :: { f#requires(params) } ante ==> f#requires(params) == pre); - sink.AddTopLevelDeclaration(new Axiom(f.tok, + AddIncludeDepAxiom(new Axiom(f.tok, BplForall(forallFormals, trig, BplImp(anteReqAxiom, Bpl.Expr.Eq(appl, preReqAxiom))), "#requires axiom for " + f.FullSanitizedName)); } + if (body == null || !RevealedInScope(f)) { return null; } // useViaContext: (mh != ModuleContextHeight || fh != FunctionContextHeight) ModuleDefinition mod = f.EnclosingClass.EnclosingModuleDefinition; - Bpl.Expr useViaContext = !InVerificationScope(f) ? (Bpl.Expr)Bpl.Expr.True : - Bpl.Expr.Neq(Bpl.Expr.Literal(mod.CallGraph.GetSCCRepresentativePredecessorCount(f)), etran.FunctionContextHeight()); + Bpl.Expr useViaContext = !InVerificationScope(f) + ? (Bpl.Expr)Bpl.Expr.True + : Bpl.Expr.Neq(Bpl.Expr.Literal(mod.CallGraph.GetSCCRepresentativePredecessorCount(f)), + etran.FunctionContextHeight()); // ante := (useViaContext && typeAnte && pre) ante = BplAnd(useViaContext, BplAnd(ante, pre)); @@ -3257,6 +2427,7 @@ Bpl.Axiom FunctionAxiom(Function f, Expression/*?*/ body, List/*?*/ lits // funcArgs.Add(ly); //} } + funcArgs.AddRange(args); funcAppl = new Bpl.NAryExpr(f.tok, new Bpl.FunctionCall(funcID), funcArgs); } @@ -3271,23 +2442,29 @@ Bpl.Axiom FunctionAxiom(Function f, Expression/*?*/ body, List/*?*/ lits var pp = (PrefixPredicate)f; bodyWithSubst = PrefixSubstitution(pp, bodyWithSubst); } + Bpl.Expr ly = null; if (layer != null) { ly = new Bpl.IdentifierExpr(f.tok, layer); - if (lits != null) { // Lit axiom doesn't consume any fuel + if (lits != null) { + // Lit axiom doesn't consume any fuel ly = LayerSucc(ly); } } + var etranBody = layer == null ? etran : etran.LimitedFunctions(f, ly); var trbody = etranBody.TrExpr(bodyWithSubst); tastyVegetarianOption = BplAnd(CanCallAssumption(bodyWithSubst, etranBody), BplAnd(TrFunctionSideEffect(bodyWithSubst, etranBody), Bpl.Expr.Eq(funcAppl, trbody))); } + QKeyValue kv = null; if (lits != null) { kv = new QKeyValue(f.tok, "weight", new List() { Bpl.Expr.Literal(3) }, null); } - Bpl.Expr ax = BplForall(f.tok, new List(), forallFormals, kv, tr, Bpl.Expr.Imp(ante, tastyVegetarianOption)); + + Bpl.Expr ax = BplForall(f.tok, new List(), forallFormals, kv, tr, + Bpl.Expr.Imp(ante, tastyVegetarianOption)); var activate = AxiomActivation(f, etran); string comment; comment = "definition axiom for " + f.FullSanitizedName; @@ -3298,172 +2475,28 @@ Bpl.Axiom FunctionAxiom(Function f, Expression/*?*/ body, List/*?*/ lits comment += " for decreasing-related literals"; } } + if (RevealedInScope(f)) { comment += " (revealed)"; } else { comment += " (opaque)"; } - return new Bpl.Axiom(f.tok, Bpl.Expr.Imp(activate, ax), comment); + return new Axiom(f.tok, Bpl.Expr.Imp(activate, ax), comment); } - /// - /// Essentially, the function override axiom looks like: - /// axiom (forall $heap: HeapType, typeArgs: Ty, this: ref, x#0: int, fuel: LayerType :: - /// { J.F(fuel, $heap, G(typeArgs), this, x#0), C.F(fuel, $heap, typeArgs, this, x#0) } - /// { J.F(fuel, $heap, G(typeArgs), this, x#0), $Is(this, C) } - /// this != null && $Is(this, C) - /// ==> - /// J.F(fuel, $heap, G(typeArgs), this, x#0) == C.F(fuel, $heap, typeArgs, this, x#0)); - /// (without the other usual antecedents). Essentially, the override gives a part of the body of the - /// trait's function, so we call FunctionAxiom to generate a conditional axiom (that is, we pass in the "overridingFunction" - /// parameter to FunctionAxiom, which will add 'dtype(this) == class.C' as an additional antecedent) for a - /// body of 'C.F(this, x#0)'. - /// - Bpl.Axiom FunctionOverrideAxiom(Function f, Function overridingFunction) { + Bpl.Type TrReceiverType(MemberDecl f) { Contract.Requires(f != null); - Contract.Requires(overridingFunction != null); - Contract.Requires(predef != null); - Contract.Requires(f.EnclosingClass != null); - Contract.Requires(!f.IsStatic); - Contract.Requires(overridingFunction.EnclosingClass is TopLevelDeclWithMembers); - Contract.Ensures(Contract.Result() != null); - - bool readsHeap = AlwaysUseHeap || f.ReadsHeap || overridingFunction.ReadsHeap; + return TrType(Resolver.GetReceiverType(f.tok, f)); + } - ExpressionTranslator etran; - Bpl.BoundVariable bvPrevHeap = null; - if (f is TwoStateFunction) { - bvPrevHeap = new Bpl.BoundVariable(f.tok, new Bpl.TypedIdent(f.tok, "$prevHeap", predef.HeapType)); - etran = new ExpressionTranslator(this, predef, - f.ReadsHeap ? new Bpl.IdentifierExpr(f.tok, predef.HeapVarName, predef.HeapType) : null, - new Bpl.IdentifierExpr(f.tok, bvPrevHeap)); - } else if (readsHeap) { - etran = new ExpressionTranslator(this, predef, f.tok); + Bpl.Expr ReceiverNotNull(Bpl.Expr th) { + Contract.Requires(th != null); + if (th.Type == predef.RefType) { + return Bpl.Expr.Neq(th, predef.Null); } else { - etran = new ExpressionTranslator(this, predef, (Bpl.Expr)null); + return Bpl.Expr.True; } - - // "forallFormals" is built to hold the bound variables of the quantification - // argsJF are the arguments to J.F (the function in the trait) - // argsCF are the arguments to C.F (the overriding function) - var forallFormals = new List(); - var argsJF = new List(); - var argsCF = new List(); - - // Add type arguments - forallFormals.AddRange(MkTyParamBinders(GetTypeParams(overridingFunction), out _)); - argsJF.AddRange(GetTypeArguments(f, overridingFunction).ConvertAll(TypeToTy)); - argsCF.AddRange(GetTypeArguments(overridingFunction, null).ConvertAll(TypeToTy)); - - // Add the fuel argument - if (f.IsFuelAware()) { - Contract.Assert(overridingFunction.IsFuelAware()); // f.IsFuelAware() ==> overridingFunction.IsFuelAware() - var fuel = new Bpl.BoundVariable(f.tok, new Bpl.TypedIdent(f.tok, "$fuel", predef.LayerType)); - forallFormals.Add(fuel); - var ly = new Bpl.IdentifierExpr(f.tok, fuel); - argsJF.Add(ly); - argsCF.Add(ly); - } else if (overridingFunction.IsFuelAware()) { - // We can't use a bound variable $fuel, because then one of the triggers won't be mentioning this $fuel. - // Instead, we do the next best thing: use the literal $LZ. - var ly = new Bpl.IdentifierExpr(f.tok, "$LZ", predef.LayerType); // $LZ - argsCF.Add(ly); - } - - // Add heap arguments - if (f is TwoStateFunction) { - Contract.Assert(bvPrevHeap != null); - forallFormals.Add(bvPrevHeap); - argsJF.Add(etran.Old.HeapExpr); - argsCF.Add(etran.Old.HeapExpr); - } - if (AlwaysUseHeap || f.ReadsHeap || overridingFunction.ReadsHeap) { - var heap = new Bpl.BoundVariable(f.tok, new Bpl.TypedIdent(f.tok, predef.HeapVarName, predef.HeapType)); - forallFormals.Add(heap); - if (AlwaysUseHeap || f.ReadsHeap) { - argsJF.Add(new Bpl.IdentifierExpr(f.tok, heap)); - } - if (AlwaysUseHeap || overridingFunction.ReadsHeap) { - argsCF.Add(new Bpl.IdentifierExpr(overridingFunction.tok, heap)); - } - } - - // Add receiver parameter - Type thisType = Resolver.GetReceiverType(f.tok, overridingFunction); - var bvThis = new Bpl.BoundVariable(f.tok, new Bpl.TypedIdent(f.tok, etran.This, TrType(thisType))); - forallFormals.Add(bvThis); - var bvThisExpr = new Bpl.IdentifierExpr(f.tok, bvThis); - argsJF.Add(bvThisExpr); - argsCF.Add(bvThisExpr); - // $Is(this, C) - var isOfSubtype = GetWhereClause(overridingFunction.tok, bvThisExpr, thisType, f is TwoStateFunction ? etran.Old : etran, IsAllocType.NEVERALLOC); - - // Add other arguments - var typeMap = GetTypeArgumentSubstitutionMap(f, overridingFunction); - foreach (Formal p in f.Formals) { - var pType = Resolver.SubstType(p.Type, typeMap); - var bv = new Bpl.BoundVariable(p.tok, new Bpl.TypedIdent(p.tok, p.AssignUniqueName(currentDeclaration.IdGenerator), TrType(pType))); - forallFormals.Add(bv); - var jfArg = new Bpl.IdentifierExpr(p.tok, bv); - argsJF.Add(ModeledAsBoxType(p.Type) ? BoxIfUnboxed(jfArg, pType) : jfArg); - argsCF.Add(new Bpl.IdentifierExpr(p.tok, bv)); - } - - // useViaContext: (mh != ModuleContextHeight || fh != FunctionContextHeight) - ModuleDefinition mod = f.EnclosingClass.EnclosingModuleDefinition; - Bpl.Expr useViaContext = !InVerificationScope(overridingFunction) ? (Bpl.Expr)Bpl.Expr.True : - Bpl.Expr.Neq(Bpl.Expr.Literal(mod.CallGraph.GetSCCRepresentativePredecessorCount(overridingFunction)), etran.FunctionContextHeight()); - - Bpl.Expr funcAppl; - { - var funcID = new Bpl.IdentifierExpr(f.tok, f.FullSanitizedName, TrType(f.ResultType)); - funcAppl = new Bpl.NAryExpr(f.tok, new Bpl.FunctionCall(funcID), argsJF); - } - Bpl.Expr overridingFuncAppl; - { - var funcID = new Bpl.IdentifierExpr(overridingFunction.tok, overridingFunction.FullSanitizedName, TrType(overridingFunction.ResultType)); - overridingFuncAppl = new Bpl.NAryExpr(overridingFunction.tok, new Bpl.FunctionCall(funcID), argsCF); - } - - // Build the triggers - // { f(Succ(s), args), f'(Succ(s), args') } - Bpl.Trigger tr = BplTriggerHeap(this, overridingFunction.tok, - funcAppl, - readsHeap ? etran.HeapExpr : null, - overridingFuncAppl); - // { f(Succ(s), args), $Is(this, T') } - var exprs = new List() { funcAppl, isOfSubtype }; - if (readsHeap) { - exprs.Add(FunctionCall(overridingFunction.tok, BuiltinFunction.IsGoodHeap, null, etran.HeapExpr)); - } - tr = new Bpl.Trigger(overridingFunction.tok, true, exprs, tr); - - // The equality that is what it's all about - var synonyms = Bpl.Expr.Eq( - funcAppl, - ModeledAsBoxType(f.ResultType) ? BoxIfUnboxed(overridingFuncAppl, overridingFunction.ResultType) : overridingFuncAppl); - - // The axiom - Bpl.Expr ax = BplForall(f.tok, new List(), forallFormals, null, tr, - Bpl.Expr.Imp(Bpl.Expr.And(ReceiverNotNull(bvThisExpr), isOfSubtype), synonyms)); - var activate = AxiomActivation(f, etran); - string comment = "override axiom for " + f.FullSanitizedName + " in class " + overridingFunction.EnclosingClass.FullSanitizedName; - return new Bpl.Axiom(f.tok, Bpl.Expr.Imp(activate, ax), comment); - } - - Bpl.Type TrReceiverType(MemberDecl f) { - Contract.Requires(f != null); - return TrType(Resolver.GetReceiverType(f.tok, f)); - } - - Bpl.Expr ReceiverNotNull(Bpl.Expr th) { - Contract.Requires(th != null); - if (th.Type == predef.RefType) { - return Bpl.Expr.Neq(th, predef.Null); - } else { - return Bpl.Expr.True; - } - } + } Expr TrFunctionSideEffect(Expression expr, ExpressionTranslator etran) { Expr e = Bpl.Expr.True; @@ -3703,7 +2736,7 @@ void AddLayerSynonymAxiom(Function f, bool forHandle = false) { Bpl.Trigger tr = new Bpl.Trigger(f.tok, true, new List { funcAppl1 }); Bpl.Expr ax = new Bpl.ForallExpr(f.tok, new List(), formals, null, tr, Bpl.Expr.Eq(funcAppl1, funcAppl0)); - sink.AddTopLevelDeclaration(new Bpl.Axiom(f.tok, ax, "layer synonym axiom")); + AddIncludeDepAxiom(new Bpl.Axiom(f.tok, ax, "layer synonym axiom")); } void AddFuelSynonymAxiom(Function f) { @@ -3769,7 +2802,7 @@ void AddFuelSynonymAxiom(Function f) { Bpl.Trigger tr = new Bpl.Trigger(f.tok, true, new List { funcAppl2 }); Bpl.Expr ax = new Bpl.ForallExpr(f.tok, new List(), formals, null, tr, Bpl.Expr.Eq(funcAppl1, funcAppl0)); - sink.AddTopLevelDeclaration(new Bpl.Axiom(f.tok, ax, "fuel synonym axiom")); + AddIncludeDepAxiom(new Bpl.Axiom(f.tok, ax, "fuel synonym axiom")); } /// @@ -3945,7 +2978,7 @@ void AddPrefixPredicateAxioms(PrefixPredicate pp) { var trigger2 = new Bpl.Trigger(tok, true, new List { prefixPred_K, prefixPred_M }); var monotonicity = new Bpl.ForallExpr(tok, moreBvs, trigger2, BplImp(smaller, direction)); - sink.AddTopLevelDeclaration(new Bpl.Axiom(tok, Bpl.Expr.Imp(activation, monotonicity), + AddRootAxiom(new Bpl.Axiom(tok, Bpl.Expr.Imp(activation, monotonicity), "prefix predicate monotonicity axiom")); #endif // A more targeted monotonicity axiom used to increase the power of automation for proving the limit case for @@ -3976,232 +3009,6 @@ void AddPrefixPredicateAxioms(PrefixPredicate pp) { } } - /// - /// For a non-static field "f" in a class "c(G)", generate: - /// // type axiom: - /// // If "G" is empty, then TClassA(G) is omitted from trigger. - /// // If "c" is an array declaration, then the bound variables also include the index variables "ii" and "h[o, f]" has the form "h[o, Index(ii)]". - /// // If "f" is readonly, then "h[o, f]" has the form "f(o)" (for special fields) or "f(G,o)" (for programmer-declared const fields), - /// // so "h" and $IsHeap(h) are omitted. - /// axiom fh < FunctionContextHeight ==> - /// (forall o: ref, h: Heap, G : Ty :: - /// { h[o, f], TClassA(G) } // if "f" is a const, omit TClassA(G) from the trigger and just use { f(G,o) } - /// $IsHeap(h) && - /// o != null && $Is(o, TClassA(G)) // or dtype(o) = TClassA(G) - /// ==> - /// $Is(h[o, f], TT(PP))); - /// - /// // allocation axiom: - /// // As above for "G" and "ii", but "h" is included no matter what. - /// axiom fh < FunctionContextHeight ==> - /// (forall o: ref, h: Heap, G : Ty :: - /// { h[o, f], TClassA(G) } // if "f" is a const, use the trigger { f(G,o), h[o, alloc] }; for other readonly fields, use { f(o), h[o, alloc], TClassA(G) } - /// $IsHeap(h) && - /// o != null && $Is(o, TClassA(G)) && // or dtype(o) = TClassA(G) - /// h[o, alloc] - /// ==> - /// $IsAlloc(h[o, f], TT(PP), h)); - /// - /// For a static (necessarily "const") field "f" in a class "c(G)", the expression corresponding to "h[o, f]" or "f(G,o)" above is "f(G)", - /// so generate: - /// // type axiom: - /// axiom fh < FunctionContextHeight ==> - /// (forall G : Ty :: - /// { f(G) } - /// $Is(f(G), TT(PP))); - /// // Or in the case where G is empty: - /// axiom $Is(f(G), TT); - /// - /// // allocation axiom: - /// axiom fh < FunctionContextHeight ==> - /// (forall h: Heap, G : Ty :: - /// { $IsAlloc(f(G), TT(PP), h) } - /// $IsHeap(h) - /// ==> - /// $IsAlloc(f(G), TT(PP), h)); - /// - /// - /// The axioms above could be optimised to something along the lines of: - /// axiom fh < FunctionContextHeight ==> - /// (forall o: ref, h: Heap :: - /// { h[o, f] } - /// $IsHeap(h) && o != null && Tag(dtype(o)) = TagClass - /// ==> - /// (h[o, alloc] ==> $IsAlloc(h[o, f], TT(TClassA_Inv_i(dtype(o)),..), h)) && - /// $Is(h[o, f], TT(TClassA_Inv_i(dtype(o)),..), h); - /// - void AddAllocationAxiom(Field f, TopLevelDeclWithMembers c, bool is_array = false) { - Contract.Requires(c != null); - // IFF you're adding the array axioms, then the field should be null - Contract.Requires(is_array == (f == null)); - Contract.Requires(sink != null && predef != null); - - Bpl.Expr heightAntecedent = Bpl.Expr.True; - if (f is ConstantField) { - var cf = (ConstantField)f; - AddWellformednessCheck(cf); - if (InVerificationScope(cf)) { - var etran = new ExpressionTranslator(this, predef, f.tok); - heightAntecedent = Bpl.Expr.Lt(Bpl.Expr.Literal(cf.EnclosingModule.CallGraph.GetSCCRepresentativePredecessorCount(cf)), etran.FunctionContextHeight()); - } - } - - var bvsTypeAxiom = new List(); - var bvsAllocationAxiom = new List(); - - // G - List tyexprs; - var tyvars = MkTyParamBinders(GetTypeParams(c), out tyexprs); - bvsTypeAxiom.AddRange(tyvars); - bvsAllocationAxiom.AddRange(tyvars); - - if (f is ConstantField && f.IsStatic) { - var oDotF = new Bpl.NAryExpr(c.tok, new Bpl.FunctionCall(GetReadonlyField(f)), tyexprs); - var is_hf = MkIs(oDotF, f.Type); // $Is(h[o, f], ..) - Bpl.Expr ax = bvsTypeAxiom.Count == 0 ? is_hf : BplForall(bvsTypeAxiom, BplTrigger(oDotF), is_hf); - sink.AddTopLevelDeclaration(new Bpl.Axiom(c.tok, BplImp(heightAntecedent, ax), string.Format("{0}.{1}: Type axiom", c, f))); - - if (CommonHeapUse || (NonGhostsUseHeap && !f.IsGhost)) { - Bpl.Expr h; - var hVar = BplBoundVar("$h", predef.HeapType, out h); - bvsAllocationAxiom.Add(hVar); - var isGoodHeap = FunctionCall(c.tok, BuiltinFunction.IsGoodHeap, null, h); - var isalloc_hf = MkIsAlloc(oDotF, f.Type, h); // $IsAlloc(h[o, f], ..) - ax = BplForall(bvsAllocationAxiom, BplTrigger(isalloc_hf), BplImp(isGoodHeap, isalloc_hf)); - sink.AddTopLevelDeclaration(new Bpl.Axiom(c.tok, BplImp(heightAntecedent, ax), string.Format("{0}.{1}: Allocation axiom", c, f))); - } - - } else { - // This is the typical case (that is, f is not a static const field) - - // h, o - Bpl.Expr h, o; - var hVar = BplBoundVar("$h", predef.HeapType, out h); - var oVar = BplBoundVar("$o", TrType(Resolver.GetThisType(c.tok, c)), out o); - - // TClassA(G) - Bpl.Expr o_ty = ClassTyCon(c, tyexprs); - - var isGoodHeap = FunctionCall(c.tok, BuiltinFunction.IsGoodHeap, null, h); - Bpl.Expr isalloc_o; - if (!(c is ClassDecl)) { - var udt = UserDefinedType.FromTopLevelDecl(c.tok, c); - isalloc_o = MkIsAlloc(o, udt, h); - } else if (RevealedInScope(c)) { - isalloc_o = IsAlloced(c.tok, h, o); - } else { - // c is only provided, not revealed, in the scope. Use the non-null type decl's internal synonym - var cl = (ClassDecl)c; - Contract.Assert(cl.NonNullTypeDecl != null); - var udt = UserDefinedType.FromTopLevelDecl(c.tok, cl.NonNullTypeDecl); - isalloc_o = MkIsAlloc(o, udt, h); - } - - Bpl.Expr indexBounds = Bpl.Expr.True; - Bpl.Expr oDotF; - if (is_array) { - // generate h[o,Index(ii)] - bvsTypeAxiom.Add(hVar); bvsTypeAxiom.Add(oVar); - bvsAllocationAxiom.Add(hVar); bvsAllocationAxiom.Add(oVar); - - var ac = (ArrayClassDecl)c; - var ixs = new List(); - for (int i = 0; i < ac.Dims; i++) { - Bpl.Expr e; Bpl.Variable v = BplBoundVar("$i" + i, Bpl.Type.Int, out e); - ixs.Add(e); - bvsTypeAxiom.Add(v); - bvsAllocationAxiom.Add(v); - } - - oDotF = ReadHeap(c.tok, h, o, GetArrayIndexFieldName(c.tok, ixs)); - - for (int i = 0; i < ac.Dims; i++) { - // 0 <= i && i < _System.array.Length(o) - var e1 = Bpl.Expr.Le(Bpl.Expr.Literal(0), ixs[i]); - var ff = GetReadonlyField((Field)(ac.Members[i])); - var e2 = Bpl.Expr.Lt(ixs[i], new Bpl.NAryExpr(c.tok, new Bpl.FunctionCall(ff), new List { o })); - indexBounds = BplAnd(indexBounds, BplAnd(e1, e2)); - } - } else if (f.IsMutable) { - // generate h[o,f] - oDotF = ReadHeap(c.tok, h, o, new Bpl.IdentifierExpr(c.tok, GetField(f))); - bvsTypeAxiom.Add(hVar); bvsTypeAxiom.Add(oVar); - bvsAllocationAxiom.Add(hVar); bvsAllocationAxiom.Add(oVar); - } else { - // generate f(G,o) - var args = new List { o }; - if (f is ConstantField) { - args = Concat(tyexprs, args); - } - oDotF = new Bpl.NAryExpr(c.tok, new Bpl.FunctionCall(GetReadonlyField(f)), args); - bvsTypeAxiom.Add(oVar); - bvsAllocationAxiom.Add(hVar); bvsAllocationAxiom.Add(oVar); - } - - // antecedent: some subset of: $IsHeap(h) && o != null && $Is(o, TClassA(G)) && indexBounds - Bpl.Expr ante = Bpl.Expr.True; - if (is_array || f.IsMutable) { - ante = BplAnd(ante, isGoodHeap); - // Note: for the allocation axiom, isGoodHeap is added back in for !f.IsMutable below - } - if (!(f is ConstantField)) { - Bpl.Expr is_o = BplAnd( - ReceiverNotNull(o), - c is TraitDecl ? MkIs(o, o_ty) : DType(o, o_ty)); // $Is(o, ..) or dtype(o) == o_ty - ante = BplAnd(ante, is_o); - } - ante = BplAnd(ante, indexBounds); - - // trigger - var t_es = new List(); - t_es.Add(oDotF); - if (tyvars.Count > 0 && (is_array || !(f is ConstantField))) { - t_es.Add(o_ty); - } - var tr = new Bpl.Trigger(c.tok, true, t_es); - - // Now for the conclusion of the axioms - Bpl.Expr is_hf, isalloc_hf = null; - if (is_array) { - is_hf = MkIs(oDotF, tyexprs[0], true); - if (CommonHeapUse || NonGhostsUseHeap) { - isalloc_hf = MkIsAlloc(oDotF, tyexprs[0], h, true); - } - } else { - is_hf = MkIs(oDotF, f.Type); // $Is(h[o, f], ..) - if (CommonHeapUse || (NonGhostsUseHeap && !f.IsGhost)) { - isalloc_hf = MkIsAlloc(oDotF, f.Type, h); // $IsAlloc(h[o, f], ..) - } - } - - Bpl.Expr ax = BplForall(bvsTypeAxiom, tr, BplImp(ante, is_hf)); - sink.AddTopLevelDeclaration(new Bpl.Axiom(c.tok, BplImp(heightAntecedent, ax), string.Format("{0}.{1}: Type axiom", c, f))); - - if (isalloc_hf != null) { - if (!is_array && !f.IsMutable) { - // isGoodHeap wasn't added above, so add it now - ante = BplAnd(isGoodHeap, ante); - } - ante = BplAnd(ante, isalloc_o); - - // compute a different trigger - t_es = new List(); - t_es.Add(oDotF); - if (!is_array && !f.IsMutable) { - // since "h" is not part of oDotF, we add a separate term that mentions "h" - t_es.Add(isalloc_o); - } - if (!(f is ConstantField) && tyvars.Count > 0) { - t_es.Add(o_ty); - } - tr = new Bpl.Trigger(c.tok, true, t_es); - - ax = BplForall(bvsAllocationAxiom, tr, BplImp(ante, isalloc_hf)); - sink.AddTopLevelDeclaration(new Bpl.Axiom(c.tok, BplImp(heightAntecedent, ax), string.Format("{0}.{1}: Allocation axiom", c, f))); - } - } - } - Bpl.Expr InSeqRange(IToken tok, Bpl.Expr index, Type indexType, Bpl.Expr seq, bool isSequence, Bpl.Expr lowerBound, bool includeUpperBound) { Contract.Requires(tok != null); Contract.Requires(index != null); @@ -4320,222 +3127,6 @@ Bpl.Expr SaveInTemp(Bpl.Expr expr, bool otherExprsCanAffectPreviouslyKnownExpres } } - void AddMethodImpl(Method m, Bpl.Procedure proc, bool wellformednessProc) { - Contract.Requires(m != null); - Contract.Requires(proc != null); - Contract.Requires(sink != null && predef != null); - Contract.Requires(wellformednessProc || m.Body != null); - Contract.Requires(currentModule == null && codeContext == null && _tmpIEs.Count == 0 && isAllocContext == null); - Contract.Ensures(currentModule == null && codeContext == null && _tmpIEs.Count == 0 && isAllocContext == null); - - currentModule = m.EnclosingClass.EnclosingModuleDefinition; - codeContext = m; - isAllocContext = new IsAllocContext(m.IsGhost); - - List inParams = Bpl.Formal.StripWhereClauses(proc.InParams); - List outParams = Bpl.Formal.StripWhereClauses(proc.OutParams); - - BoogieStmtListBuilder builder = new BoogieStmtListBuilder(this); - builder.Add(new CommentCmd("AddMethodImpl: " + m + ", " + proc)); - var etran = new ExpressionTranslator(this, predef, m.tok); - InitializeFuelConstant(m.tok, builder, etran); - var localVariables = new List(); - GenerateImplPrelude(m, wellformednessProc, inParams, outParams, builder, localVariables); - - if (UseOptimizationInZ3) { - // We ask Z3 to minimize all parameters of type 'nat'. - foreach (var f in m.Ins) { - var udt = f.Type.NormalizeExpandKeepConstraints() as UserDefinedType; - if (udt != null && udt.Name == "nat") { - builder.Add(optimizeExpr(true, new IdentifierExpr(f.tok, f), f.Tok, etran)); - } - } - } - - Bpl.StmtList stmts; - if (!wellformednessProc) { - var inductionVars = ApplyInduction(m.Ins, m.Attributes); - if (inductionVars.Count != 0) { - // Let the parameters be this,x,y of the method M and suppose ApplyInduction returns y. - // Also, let Pre be the precondition and VF be the decreases clause. - // Then, insert into the method body what amounts to: - // assume case-analysis-on-parameter[[ y' ]]; - // forall (y' | Pre(this, x, y') && VF(this, x, y') << VF(this, x, y)) { - // this.M(x, y'); - // } - // Generate bound variables for the forall statement, and a substitution for the Pre and VF - - // assume case-analysis-on-parameter[[ y' ]]; - foreach (var inFormal in m.Ins) { - var dt = inFormal.Type.AsDatatype; - if (dt != null) { - var funcID = new Bpl.FunctionCall(new Bpl.IdentifierExpr(inFormal.tok, "$IsA#" + dt.FullSanitizedName, Bpl.Type.Bool)); - var f = new Bpl.IdentifierExpr(inFormal.tok, inFormal.AssignUniqueName(m.IdGenerator), TrType(inFormal.Type)); - builder.Add(TrAssumeCmd(inFormal.tok, new Bpl.NAryExpr(inFormal.tok, funcID, new List { f }))); - } - } - - var parBoundVars = new List(); - var parBounds = new List(); - var substMap = new Dictionary(); - Expression receiverSubst = null; - foreach (var iv in inductionVars) { - BoundVar bv; - if (iv == null) { - // this corresponds to "this" - Contract.Assert(!m.IsStatic); // if "m" is static, "this" should never have gone into the _induction attribute - Contract.Assert(receiverSubst == null); // we expect at most one - var receiverType = Resolver.GetThisType(m.tok, (TopLevelDeclWithMembers)m.EnclosingClass); - bv = new BoundVar(m.tok, CurrentIdGenerator.FreshId("$ih#this"), receiverType); // use this temporary variable counter, but for a Dafny name (the idea being that the number and the initial "_" in the name might avoid name conflicts) - var ie = new IdentifierExpr(m.tok, bv.Name); - ie.Var = bv; // resolve here - ie.Type = bv.Type; // resolve here - receiverSubst = ie; - } else { - IdentifierExpr ie; - CloneVariableAsBoundVar(iv.tok, iv, "$ih#" + iv.Name, out bv, out ie); - substMap.Add(iv, ie); - } - parBoundVars.Add(bv); - parBounds.Add(new ComprehensionExpr.SpecialAllocIndependenceAllocatedBoundedPool()); // record that we don't want alloc antecedents for these variables - } - - // Generate a CallStmt for the recursive call - Expression recursiveCallReceiver; - List recursiveCallArgs; - RecursiveCallParameters(m.tok, m, m.TypeArgs, m.Ins, substMap, out recursiveCallReceiver, out recursiveCallArgs); - var methodSel = new MemberSelectExpr(m.tok, recursiveCallReceiver, m.Name); - methodSel.Member = m; // resolve here - methodSel.TypeApplication_AtEnclosingClass = m.EnclosingClass.TypeArgs.ConvertAll(tp => (Type)new UserDefinedType(tp.tok, tp)); - methodSel.TypeApplication_JustMember = m.TypeArgs.ConvertAll(tp => (Type)new UserDefinedType(tp.tok, tp)); - methodSel.Type = new InferredTypeProxy(); - var recursiveCall = new CallStmt(m.tok, m.tok, new List(), methodSel, recursiveCallArgs); - recursiveCall.IsGhost = m.IsGhost; // resolve here - - Expression parRange = new LiteralExpr(m.tok, true); - parRange.Type = Type.Bool; // resolve here - foreach (var pre in m.Req) { - parRange = Expression.CreateAnd(parRange, Substitute(pre.E, receiverSubst, substMap)); - } - // construct an expression (generator) for: VF' << VF - ExpressionConverter decrCheck = delegate (Dictionary decrSubstMap, ExpressionTranslator exprTran) { - var decrToks = new List(); - var decrTypes = new List(); - var decrCallee = new List(); - var decrCaller = new List(); - foreach (var ee in m.Decreases.Expressions) { - decrToks.Add(ee.tok); - decrTypes.Add(ee.Type.NormalizeExpand()); - decrCaller.Add(exprTran.TrExpr(ee)); - Expression es = Substitute(ee, receiverSubst, substMap); - es = Substitute(es, null, decrSubstMap); - decrCallee.Add(exprTran.TrExpr(es)); - } - return DecreasesCheck(decrToks, decrTypes, decrTypes, decrCallee, decrCaller, null, null, false, true); - }; - -#if VERIFY_CORRECTNESS_OF_TRANSLATION_FORALL_STATEMENT_RANGE - var definedness = new BoogieStmtListBuilder(this); - var exporter = new BoogieStmtListBuilder(this); - TrForallStmtCall(m.tok, parBoundVars, parRange, decrCheck, null, recursiveCall, definedness, exporter, localVariables, etran); - // All done, so put the two pieces together - builder.Add(new Bpl.IfCmd(m.tok, null, definedness.Collect(m.tok), null, exporter.Collect(m.tok))); -#else - TrForallStmtCall(m.tok, parBoundVars, parBounds, parRange, decrCheck, null, recursiveCall, null, builder, localVariables, etran); -#endif - } - // translate the body of the method - Contract.Assert(m.Body != null); // follows from method precondition and the if guard - - // $_reverifyPost := false; - builder.Add(Bpl.Cmd.SimpleAssign(m.tok, new Bpl.IdentifierExpr(m.tok, "$_reverifyPost", Bpl.Type.Bool), Bpl.Expr.False)); - // register output parameters with definite-assignment trackers - Contract.Assert(definiteAssignmentTrackers.Count == 0); - m.Outs.Iter(p => AddExistingDefiniteAssignmentTracker(p, m.IsGhost)); - // translate the body - TrStmt(m.Body, builder, localVariables, etran); - m.Outs.Iter(p => CheckDefiniteAssignmentReturn(m.BodyEndTok, p, builder)); - stmts = builder.Collect(m.Body.Tok); - // tear down definite-assignment trackers - m.Outs.Iter(RemoveDefiniteAssignmentTracker); - Contract.Assert(definiteAssignmentTrackers.Count == 0); - } else { - // check well-formedness of any default-value expressions (before assuming preconditions) - foreach (var formal in m.Ins.Where(formal => formal.DefaultValue != null)) { - var e = formal.DefaultValue; - CheckWellformed(e, new WFOptions(null, false, false, true), localVariables, builder, etran); - builder.Add(new Bpl.AssumeCmd(e.tok, CanCallAssumption(e, etran))); - CheckSubrange(e.tok, etran.TrExpr(e), e.Type, formal.Type, builder); - - if (formal.IsOld) { - Bpl.Expr wh = GetWhereClause(e.tok, etran.TrExpr(e), e.Type, etran.Old, ISALLOC, true); - if (wh != null) { - builder.Add(Assert(e.tok, wh, "default value must be allocated in the two-state lemma's previous state")); - } - } - } - // check well-formedness of the preconditions, and then assume each one of them - foreach (AttributedExpression p in m.Req) { - CheckWellformedAndAssume(p.E, new WFOptions(), localVariables, builder, etran); - } - // check well-formedness of the modifies clauses - CheckFrameWellFormed(new WFOptions(), m.Mod.Expressions, localVariables, builder, etran); - // check well-formedness of the decreases clauses - foreach (Expression p in m.Decreases.Expressions) { - CheckWellformed(p, new WFOptions(), localVariables, builder, etran); - } - - if (!(m is TwoStateLemma)) { - // play havoc with the heap according to the modifies clause - builder.Add(new Bpl.HavocCmd(m.tok, new List { (Bpl.IdentifierExpr/*TODO: this cast is rather dubious*/)etran.HeapExpr })); - // assume the usual two-state boilerplate information - foreach (BoilerplateTriple tri in GetTwoStateBoilerplate(m.tok, m.Mod.Expressions, m.IsGhost, m.AllowsAllocation, etran.Old, etran, etran.Old)) { - if (tri.IsFree) { - builder.Add(TrAssumeCmd(m.tok, tri.Expr)); - } - } - } - - // also play havoc with the out parameters - if (outParams.Count != 0) { // don't create an empty havoc statement - List outH = new List(); - foreach (Bpl.Variable b in outParams) { - Contract.Assert(b != null); - outH.Add(new Bpl.IdentifierExpr(b.tok, b)); - } - builder.Add(new Bpl.HavocCmd(m.tok, outH)); - } - // mark the end of the modifles/out-parameter havocking with a CaptureState; make its location be the first ensures clause, if any (and just - // omit the CaptureState if there's no ensures clause) - if (m.Ens.Count != 0) { - builder.AddCaptureState(m.Ens[0].E.tok, false, "post-state"); - } - - // check wellformedness of postconditions - foreach (AttributedExpression p in m.Ens) { - CheckWellformedAndAssume(p.E, new WFOptions(), localVariables, builder, etran); - } - - stmts = builder.Collect(m.tok); - } - - if (EmitImplementation(m.Attributes)) { - // emit impl only when there are proof obligations. - QKeyValue kv = etran.TrAttributes(m.Attributes, null); - Bpl.Implementation impl = new Bpl.Implementation(m.tok, proc.Name, - new List(), inParams, outParams, - localVariables, stmts, kv); - sink.AddTopLevelDeclaration(impl); - - if (InsertChecksums) { - InsertChecksum(m, impl); - } - } - - isAllocContext = null; - Reset(); - } - #region Definite-assignment tracking bool NeedsDefiniteAssignmentTracker(bool isGhost, Type type) { @@ -4752,187 +3343,6 @@ internal static AssumeCmd optimizeExpr(bool minimize, Expression expr, IToken to return assumeCmd; } - private void AddFunctionOverrideCheckImpl(Function f) { - Contract.Requires(f != null); - Contract.Requires(f.EnclosingClass is TopLevelDeclWithMembers); - Contract.Requires(sink != null && predef != null); - Contract.Requires(f.OverriddenFunction != null); - Contract.Requires(f.Formals.Count == f.OverriddenFunction.Formals.Count); - Contract.Requires(currentModule == null && codeContext == null && _tmpIEs.Count == 0 && isAllocContext != null); - Contract.Ensures(currentModule == null && codeContext == null && _tmpIEs.Count == 0 && isAllocContext != null); - - #region first procedure, no impl yet - //Function nf = new Function(f.tok, "OverrideCheck_" + f.Name, f.IsStatic, f.IsGhost, f.TypeArgs, f.OpenParen, f.Formals, f.ResultType, f.Req, f.Reads, f.Ens, f.Decreases, f.Body, f.Attributes, f.SignatureEllipsis); - //AddFunction(f); - currentModule = f.EnclosingClass.EnclosingModuleDefinition; - codeContext = f; - - Bpl.Expr prevHeap = null; - Bpl.Expr currHeap = null; - var ordinaryEtran = new ExpressionTranslator(this, predef, f.tok); - ExpressionTranslator etran; - var inParams_Heap = new List(); - if (f is TwoStateFunction) { - var prevHeapVar = new Bpl.Formal(f.tok, new Bpl.TypedIdent(f.tok, "previous$Heap", predef.HeapType), true); - inParams_Heap.Add(prevHeapVar); - prevHeap = new Bpl.IdentifierExpr(f.tok, prevHeapVar); - if (f.ReadsHeap) { - var currHeapVar = new Bpl.Formal(f.tok, new Bpl.TypedIdent(f.tok, "current$Heap", predef.HeapType), true); - inParams_Heap.Add(currHeapVar); - currHeap = new Bpl.IdentifierExpr(f.tok, currHeapVar); - } - etran = new ExpressionTranslator(this, predef, currHeap, prevHeap); - } else { - etran = ordinaryEtran; - } - - // parameters of the procedure - var typeInParams = MkTyParamFormals(GetTypeParams(f)); - var inParams = new List(); - var outParams = new List(); - if (!f.IsStatic) { - var th = new Bpl.IdentifierExpr(f.tok, "this", TrReceiverType(f)); - Bpl.Expr wh = Bpl.Expr.And( - ReceiverNotNull(th), - etran.GoodRef(f.tok, th, Resolver.GetReceiverType(f.tok, f))); - Bpl.Formal thVar = new Bpl.Formal(f.tok, new Bpl.TypedIdent(f.tok, "this", TrReceiverType(f), wh), true); - inParams.Add(thVar); - } - foreach (Formal p in f.Formals) { - Bpl.Type varType = TrType(p.Type); - Bpl.Expr wh = GetWhereClause(p.tok, new Bpl.IdentifierExpr(p.tok, p.AssignUniqueName(f.IdGenerator), varType), p.Type, etran, NOALLOC); - inParams.Add(new Bpl.Formal(p.tok, new Bpl.TypedIdent(p.tok, p.AssignUniqueName(f.IdGenerator), varType, wh), true)); - } - - Formal pOut = null; - if (f.Result != null || f.OverriddenFunction.Result != null) { - if (f.Result != null) { - pOut = f.Result; - Contract.Assert(!pOut.IsOld); - } else { - var pp = f.OverriddenFunction.Result; - Contract.Assert(!pp.IsOld); - pOut = new Formal(pp.tok, pp.Name, f.ResultType, false, pp.IsGhost, null); - } - var varType = TrType(pOut.Type); - var wh = GetWhereClause(pOut.tok, new Bpl.IdentifierExpr(pOut.tok, pOut.AssignUniqueName(f.IdGenerator), varType), pOut.Type, etran, NOALLOC); - outParams.Add(new Bpl.Formal(pOut.tok, new Bpl.TypedIdent(pOut.tok, pOut.AssignUniqueName(f.IdGenerator), varType, wh), true)); - } - // the procedure itself - var req = new List(); - // free requires mh == ModuleContextHeight && fh == FunctionContextHeight; - req.Add(Requires(f.tok, true, etran.HeightContext(f.OverriddenFunction), null, null)); - if (f is TwoStateFunction) { - // free requires prevHeap == Heap && HeapSucc(prevHeap, currHeap) && IsHeap(currHeap) - var a0 = Bpl.Expr.Eq(prevHeap, ordinaryEtran.HeapExpr); - var a1 = HeapSucc(prevHeap, currHeap); - var a2 = FunctionCall(f.tok, BuiltinFunction.IsGoodHeap, null, currHeap); - req.Add(Requires(f.tok, true, BplAnd(a0, BplAnd(a1, a2)), null, null)); - } - // modifies $Heap, $Tick - var mod = new List { - (Bpl.IdentifierExpr/*TODO: this cast is rather dubious*/)ordinaryEtran.HeapExpr, - etran.Tick() - }; - var ens = new List(); - - var proc = new Bpl.Procedure(f.tok, "OverrideCheck$$" + f.FullSanitizedName, new List(), - Concat(Concat(typeInParams, inParams_Heap), inParams), outParams, - req, mod, ens, etran.TrAttributes(f.Attributes, null)); - sink.AddTopLevelDeclaration(proc); - var implInParams = Bpl.Formal.StripWhereClauses(inParams); - var implOutParams = Bpl.Formal.StripWhereClauses(outParams); - - #endregion - - //List outParams = Bpl.Formal.StripWhereClauses(proc.OutParams); - - BoogieStmtListBuilder builder = new BoogieStmtListBuilder(this); - List localVariables = new List(); - - // assume traitTypeParameter == G(overrideTypeParameters); - AddOverrideCheckTypeArgumentInstantiations(f, builder, localVariables); - - if (f is TwoStateFunction) { - // $Heap := current$Heap; - var heap = (Bpl.IdentifierExpr /*TODO: this cast is somewhat dubious*/)ordinaryEtran.HeapExpr; - builder.Add(Bpl.Cmd.SimpleAssign(f.tok, heap, etran.HeapExpr)); - etran = ordinaryEtran; // we no longer need the special heap names - } - - var substMap = new Dictionary(); - for (int i = 0; i < f.Formals.Count; i++) { - //get corresponsing formal in the class - var ie = new IdentifierExpr(f.Formals[i].tok, f.Formals[i].AssignUniqueName(f.IdGenerator)); - ie.Var = f.Formals[i]; ie.Type = ie.Var.Type; - substMap.Add(f.OverriddenFunction.Formals[i], ie); - } - - if (f.OverriddenFunction.Result != null) { - Contract.Assert(pOut != null); - //get corresponsing formal in the class - var ie = new IdentifierExpr(pOut.tok, pOut.AssignUniqueName(f.IdGenerator)); - ie.Var = pOut; ie.Type = ie.Var.Type; - substMap.Add(f.OverriddenFunction.Result, ie); - } - - //adding assume Pre’; assert P; // this checks that Pre’ implies P - AddFunctionOverrideReqsChk(f, builder, etran, substMap); - - //adding assert R <= Rank’; - AddOverrideTerminationChk(f, f.OverriddenFunction, builder, etran, substMap); - - //adding assert W <= Frame’ - AddFunctionOverrideSubsetChk(f, builder, etran, localVariables, substMap); - - //adding assume Q; assert Post’; - //adding assume J.F(ins) == C.F(ins); - AddFunctionOverrideEnsChk(f, builder, etran, substMap, implInParams, implOutParams.Count == 0 ? null : implOutParams[0]); - - var stmts = builder.Collect(f.tok); - - if (EmitImplementation(f.Attributes)) { - // emit the impl only when there are proof obligations. - QKeyValue kv = etran.TrAttributes(f.Attributes, null); - - var impl = new Bpl.Implementation(f.tok, proc.Name, new List(), - Concat(Concat(typeInParams, inParams_Heap), implInParams), implOutParams, localVariables, stmts, kv); - sink.AddTopLevelDeclaration(impl); - } - - if (InsertChecksums) { - InsertChecksum(f, proc, true); - } - - Reset(); - } - - private void AddOverrideCheckTypeArgumentInstantiations(MemberDecl member, BoogieStmtListBuilder builder, List localVariables) { - Contract.Requires(member is Function || member is Method); - Contract.Requires(member.EnclosingClass is TopLevelDeclWithMembers); - Contract.Requires(builder != null); - Contract.Requires(localVariables != null); - - MemberDecl overriddenMember; - List overriddenTypeParameters; - if (member is Function) { - var o = ((Function)member).OverriddenFunction; - overriddenMember = o; - overriddenTypeParameters = o.TypeArgs; - } else { - var o = ((Method)member).OverriddenMethod; - overriddenMember = o; - overriddenTypeParameters = o.TypeArgs; - } - var typeMap = GetTypeArgumentSubstitutionMap(overriddenMember, member); - foreach (var tp in Concat(overriddenMember.EnclosingClass.TypeArgs, overriddenTypeParameters)) { - var local = BplLocalVar(nameTypeParam(tp), predef.Ty, out var lhs); - localVariables.Add(local); - var rhs = TypeToTy(typeMap[tp]); - builder.Add(new Bpl.AssumeCmd(tp.tok, Bpl.Expr.Eq(lhs, rhs))); - } - } - private void AddFunctionOverrideEnsChk(Function f, BoogieStmtListBuilder builder, ExpressionTranslator etran, Dictionary substMap, List implInParams, Bpl.Variable/*?*/ resultVariable) { Contract.Requires(f.Formals.Count <= implInParams.Count); @@ -4995,373 +3405,66 @@ private void AddFunctionOverrideEnsChk(Function f, BoogieStmtListBuilder builder //generating trait post-conditions with class variables foreach (var en in f.OverriddenFunction.Ens) { Expression postcond = Substitute(en.E, null, substMap); - bool splitHappened; // we don't actually care - foreach (var s in TrSplitExpr(postcond, etran, false, out splitHappened)) { - if (s.IsChecked) { - builder.Add(Assert(f.tok, s.E, "the function must provide an equal or more detailed postcondition than in its parent trait")); - } - } - } - } - - /// - /// Return type arguments for function "f", where any type parameters are in terms of - /// the context of "overridingFunction ?? f". - /// - /// In more symbols, suppose "f" is declared as follows: - /// class/trait Tr[A,B] { - /// function f[C,D](...): ... - /// } - /// When "overridingFunction" is null, return: - /// [A, B, C, D] - /// When "overridingFunction" is non-null and stands for: - /// class/trait Cl[G] extends Tr[X(G),Y(G)] { - /// function f[R,S](...): ... - /// } - /// return: - /// [X(G), Y(G), R, S] - /// - /// See also GetTypeArgumentSubstitutionMap. - /// - private static List GetTypeArguments(Function f, Function/*?*/ overridingFunction) { - Contract.Requires(f != null); - Contract.Requires(overridingFunction == null || overridingFunction.EnclosingClass is TopLevelDeclWithMembers); - Contract.Requires(overridingFunction == null || f.TypeArgs.Count == overridingFunction.TypeArgs.Count); - - List tyargs; - if (overridingFunction == null) { - tyargs = f.EnclosingClass.TypeArgs.ConvertAll(tp => (Type)new UserDefinedType(tp.tok, tp)); - } else { - var cl = (TopLevelDeclWithMembers)overridingFunction.EnclosingClass; - var typeMap = cl.ParentFormalTypeParametersToActuals; - tyargs = f.EnclosingClass.TypeArgs.ConvertAll(tp => typeMap[tp]); - } - tyargs.AddRange((overridingFunction ?? f).TypeArgs.ConvertAll(tp => new UserDefinedType(tp.tok, tp))); - return tyargs; - } - - /// - /// Return a type-parameter substitution map for function "f", as instantiated by the context of "overridingFunction". - /// - /// In more symbols, suppose "f" is declared as follows: - /// class/trait Tr[A,B] { - /// function f[C,D](...): ... - /// } - /// and "overridingFunction" is declared as follows: - /// class/trait Cl[G] extends Tr[X(G),Y(G)] { - /// function f[R,S](...): ... - /// } - /// Then, return the following map: - /// A -> X(G) - /// B -> Y(G) - /// C -> R - /// D -> S - /// - /// See also GetTypeArguments. - /// - private static Dictionary GetTypeArgumentSubstitutionMap(MemberDecl member, MemberDecl overridingMember) { - Contract.Requires(member is Function || member is Method); - Contract.Requires(overridingMember is Function || overridingMember is Method); - Contract.Requires(overridingMember.EnclosingClass is TopLevelDeclWithMembers); - Contract.Requires(((ICallable)member).TypeArgs.Count == ((ICallable)overridingMember).TypeArgs.Count); - - var typeMap = new Dictionary(); - - var cl = (TopLevelDeclWithMembers)overridingMember.EnclosingClass; - var classTypeMap = cl.ParentFormalTypeParametersToActuals; - member.EnclosingClass.TypeArgs.ForEach(tp => typeMap.Add(tp, classTypeMap[tp])); - - var origTypeArgs = ((ICallable)member).TypeArgs; - var overridingTypeArgs = ((ICallable)overridingMember).TypeArgs; - for (var i = 0; i < origTypeArgs.Count; i++) { - var otp = overridingTypeArgs[i]; - typeMap.Add(origTypeArgs[i], new UserDefinedType(otp.tok, otp)); - } - - return typeMap; - } - - private void AddFunctionOverrideSubsetChk(Function func, BoogieStmtListBuilder builder, ExpressionTranslator etran, List localVariables, Dictionary substMap) { - //getting framePrime - List traitFrameExps = new List(); - foreach (var e in func.OverriddenFunction.Reads) { - var newE = Substitute(e.E, null, substMap); - FrameExpression fe = new FrameExpression(e.tok, newE, e.FieldName); - traitFrameExps.Add(fe); - } - - QKeyValue kv = etran.TrAttributes(func.Attributes, null); - - IToken tok = func.tok; - // Declare a local variable $_Frame: [ref, Field alpha]bool - Bpl.IdentifierExpr traitFrame = etran.TheFrame(func.OverriddenFunction.tok); // this is a throw-away expression, used only to extract the type and name of the $_Frame variable - traitFrame.Name = func.EnclosingClass.Name + "_" + traitFrame.Name; - Contract.Assert(traitFrame.Type != null); // follows from the postcondition of TheFrame - Bpl.LocalVariable frame = new Bpl.LocalVariable(tok, new Bpl.TypedIdent(tok, null ?? traitFrame.Name, traitFrame.Type)); - localVariables.Add(frame); - // $_Frame := (lambda $o: ref, $f: Field alpha :: $o != null && $Heap[$o,alloc] ==> ($o,$f) in Modifies/Reads-Clause); - Bpl.TypeVariable alpha = new Bpl.TypeVariable(tok, "alpha"); - Bpl.BoundVariable oVar = new Bpl.BoundVariable(tok, new Bpl.TypedIdent(tok, "$o", predef.RefType)); - Bpl.IdentifierExpr o = new Bpl.IdentifierExpr(tok, oVar); - Bpl.BoundVariable fVar = new Bpl.BoundVariable(tok, new Bpl.TypedIdent(tok, "$f", predef.FieldName(tok, alpha))); - Bpl.IdentifierExpr f = new Bpl.IdentifierExpr(tok, fVar); - Bpl.Expr ante = Bpl.Expr.And(Bpl.Expr.Neq(o, predef.Null), etran.IsAlloced(tok, o)); - Bpl.Expr consequent = InRWClause(tok, o, f, traitFrameExps, etran, null, null); - Bpl.Expr lambda = new Bpl.LambdaExpr(tok, new List { alpha }, new List { oVar, fVar }, null, - Bpl.Expr.Imp(ante, consequent)); - - //to initialize $_Frame variable to Frame' - builder.Add(Bpl.Cmd.SimpleAssign(tok, new Bpl.IdentifierExpr(tok, frame), lambda)); - - // emit: assert (forall o: ref, f: Field alpha :: o != null && $Heap[o,alloc] && (o,f) in subFrame ==> $_Frame[o,f]); - Bpl.Expr oInCallee = InRWClause(tok, o, f, func.Reads, etran, null, null); - Bpl.Expr consequent2 = InRWClause(tok, o, f, traitFrameExps, etran, null, null); - Bpl.Expr q = new Bpl.ForallExpr(tok, new List { alpha }, new List { oVar, fVar }, - Bpl.Expr.Imp(Bpl.Expr.And(ante, oInCallee), consequent2)); - builder.Add(Assert(tok, q, "expression may read an object not in the parent trait context's reads clause", kv)); - } - - private void AddFunctionOverrideReqsChk(Function f, BoogieStmtListBuilder builder, ExpressionTranslator etran, Dictionary substMap) { - Contract.Requires(f != null); - Contract.Requires(builder != null); - Contract.Requires(etran != null); - Contract.Requires(substMap != null); - //generating trait pre-conditions with class variables - foreach (var req in f.OverriddenFunction.Req) { - Expression precond = Substitute(req.E, null, substMap); - builder.Add(TrAssumeCmd(f.tok, etran.TrExpr(precond))); - } - //generating class pre-conditions - foreach (var req in f.Req) { - bool splitHappened; // we actually don't care - foreach (var s in TrSplitExpr(req.E, etran, false, out splitHappened)) { - if (s.IsChecked) { - builder.Add(Assert(f.tok, s.E, "the function must provide an equal or more permissive precondition than in its parent trait")); - } - } - } - } - - private void AddMethodOverrideCheckImpl(Method m, Bpl.Procedure proc) { - Contract.Requires(m != null); - Contract.Requires(proc != null); - Contract.Requires(sink != null && predef != null); - Contract.Requires(m.OverriddenMethod != null); - Contract.Requires(m.Ins.Count == m.OverriddenMethod.Ins.Count); - Contract.Requires(m.Outs.Count == m.OverriddenMethod.Outs.Count); - //Contract.Requires(wellformednessProc || m.Body != null); - Contract.Requires(currentModule == null && codeContext == null && _tmpIEs.Count == 0 && isAllocContext == null); - Contract.Ensures(currentModule == null && codeContext == null && _tmpIEs.Count == 0 && isAllocContext == null); - - currentModule = m.EnclosingClass.EnclosingModuleDefinition; - codeContext = m; - isAllocContext = new IsAllocContext(m.IsGhost); - - List inParams = Bpl.Formal.StripWhereClauses(proc.InParams); - List outParams = Bpl.Formal.StripWhereClauses(proc.OutParams); - - var builder = new BoogieStmtListBuilder(this); - var etran = new ExpressionTranslator(this, predef, m.tok); - var localVariables = new List(); - - // assume traitTypeParameter == G(overrideTypeParameters); - AddOverrideCheckTypeArgumentInstantiations(m, builder, localVariables); - - if (m is TwoStateLemma) { - // $Heap := current$Heap; - var heap = (Bpl.IdentifierExpr /*TODO: this cast is somewhat dubious*/)new ExpressionTranslator(this, predef, m.tok).HeapExpr; - builder.Add(Bpl.Cmd.SimpleAssign(m.tok, heap, new Bpl.IdentifierExpr(m.tok, "current$Heap", predef.HeapType))); - } - - - var substMap = new Dictionary(); - for (int i = 0; i < m.Ins.Count; i++) { - //get corresponsing formal in the class - var ie = new IdentifierExpr(m.Ins[i].tok, m.Ins[i].AssignUniqueName(m.IdGenerator)); - ie.Var = m.Ins[i]; ie.Type = ie.Var.Type; - substMap.Add(m.OverriddenMethod.Ins[i], ie); - } - for (int i = 0; i < m.Outs.Count; i++) { - //get corresponsing formal in the class - var ie = new IdentifierExpr(m.Outs[i].tok, m.Outs[i].AssignUniqueName(m.IdGenerator)); - ie.Var = m.Outs[i]; ie.Type = ie.Var.Type; - substMap.Add(m.OverriddenMethod.Outs[i], ie); - } - - Bpl.StmtList stmts; - //adding assume Pre’; assert P; // this checks that Pre’ implies P - AddMethodOverrideReqsChk(m, builder, etran, substMap); - - //adding assert R <= Rank’; - AddOverrideTerminationChk(m, m.OverriddenMethod, builder, etran, substMap); - - //adding assert W <= Frame’ - AddMethodOverrideSubsetChk(m, builder, etran, localVariables, substMap); - - if (!(m is TwoStateLemma)) { - //change the heap at locations W - HavocMethodFrameLocations(m, builder, etran, localVariables); - } - - //adding assume Q; assert Post’; - AddMethodOverrideEnsChk(m, builder, etran, substMap); - - stmts = builder.Collect(m.tok); - - if (EmitImplementation(m.Attributes)) { - // emit the impl only when there are proof obligations. - QKeyValue kv = etran.TrAttributes(m.Attributes, null); - Bpl.Implementation impl = new Bpl.Implementation(m.tok, proc.Name, new List(), inParams, outParams, localVariables, stmts, kv); - sink.AddTopLevelDeclaration(impl); - - if (InsertChecksums) { - InsertChecksum(m, impl); - } - } - - isAllocContext = null; - Reset(); - } - - private void HavocMethodFrameLocations(Method m, BoogieStmtListBuilder builder, ExpressionTranslator etran, List localVariables) { - Contract.Requires(m != null); - Contract.Requires(m.EnclosingClass != null && m.EnclosingClass is ClassDecl); - - // play havoc with the heap according to the modifies clause - builder.Add(new Bpl.HavocCmd(m.tok, new List { (Bpl.IdentifierExpr/*TODO: this cast is rather dubious*/)etran.HeapExpr })); - // assume the usual two-state boilerplate information - foreach (BoilerplateTriple tri in GetTwoStateBoilerplate(m.tok, m.Mod.Expressions, m.IsGhost, m.AllowsAllocation, etran.Old, etran, etran.Old)) { - if (tri.IsFree) { - builder.Add(TrAssumeCmd(m.tok, tri.Expr)); - } - } - } - - private void AddMethodOverrideEnsChk(Method m, BoogieStmtListBuilder builder, ExpressionTranslator etran, Dictionary substMap) { - Contract.Requires(m != null); - Contract.Requires(builder != null); - Contract.Requires(etran != null); - Contract.Requires(substMap != null); - //generating class post-conditions - foreach (var en in m.Ens) { - builder.Add(TrAssumeCmd(m.tok, etran.TrExpr(en.E))); - } - //generating trait post-conditions with class variables - foreach (var en in m.OverriddenMethod.Ens) { - Expression postcond = Substitute(en.E, null, substMap); - bool splitHappened; // we actually don't care - foreach (var s in TrSplitExpr(postcond, etran, false, out splitHappened)) { - if (s.IsChecked) { - builder.Add(Assert(m.tok, s.E, "the method must provide an equal or more detailed postcondition than in its parent trait")); - } - } - } - } - - private void AddMethodOverrideReqsChk(Method m, BoogieStmtListBuilder builder, ExpressionTranslator etran, Dictionary substMap) { - Contract.Requires(m != null); - Contract.Requires(builder != null); - Contract.Requires(etran != null); - Contract.Requires(substMap != null); - //generating trait pre-conditions with class variables - foreach (var req in m.OverriddenMethod.Req) { - Expression precond = Substitute(req.E, null, substMap); - builder.Add(TrAssumeCmd(m.tok, etran.TrExpr(precond))); - } - //generating class pre-conditions - foreach (var req in m.Req) { - bool splitHappened; // we actually don't care - foreach (var s in TrSplitExpr(req.E, etran, false, out splitHappened)) { + bool splitHappened; // we don't actually care + foreach (var s in TrSplitExpr(postcond, etran, false, out splitHappened)) { if (s.IsChecked) { - builder.Add(Assert(m.tok, s.E, "the method must provide an equal or more permissive precondition than in its parent trait")); + builder.Add(Assert(f.tok, s.E, "the function must provide an equal or more detailed postcondition than in its parent trait")); } } } } - private void AddOverrideTerminationChk(ICallable original, ICallable overryd, BoogieStmtListBuilder builder, ExpressionTranslator etran, Dictionary substMap) { - Contract.Requires(original != null); - Contract.Requires(overryd != null); - Contract.Requires(builder != null); - Contract.Requires(etran != null); - Contract.Requires(substMap != null); - // Note, it is as if the trait's method is calling the class's method. - var contextDecreases = overryd.Decreases.Expressions; - var calleeDecreases = original.Decreases.Expressions; - // We want to check: calleeDecreases <= contextDecreases (note, we can allow equality, since there is a bounded, namely 1, number of dynamic dispatches) - if (Contract.Exists(contextDecreases, e => e is WildcardExpr)) { - // no check needed - return; - } + /// + /// Return type arguments for function "f", where any type parameters are in terms of + /// the context of "overridingFunction ?? f". + /// + /// In more symbols, suppose "f" is declared as follows: + /// class/trait Tr[A,B] { + /// function f[C,D](...): ... + /// } + /// When "overridingFunction" is null, return: + /// [A, B, C, D] + /// When "overridingFunction" is non-null and stands for: + /// class/trait Cl[G] extends Tr[X(G),Y(G)] { + /// function f[R,S](...): ... + /// } + /// return: + /// [X(G), Y(G), R, S] + /// + /// See also GetTypeArgumentSubstitutionMap. + /// + private static List GetTypeArguments(Function f, Function/*?*/ overridingFunction) { + Contract.Requires(f != null); + Contract.Requires(overridingFunction == null || overridingFunction.EnclosingClass is TopLevelDeclWithMembers); + Contract.Requires(overridingFunction == null || f.TypeArgs.Count == overridingFunction.TypeArgs.Count); - int N = Math.Min(contextDecreases.Count, calleeDecreases.Count); - var toks = new List(); - var types0 = new List(); - var types1 = new List(); - var callee = new List(); - var caller = new List(); + List tyargs; + if (overridingFunction == null) { + tyargs = f.EnclosingClass.TypeArgs.ConvertAll(tp => (Type)new UserDefinedType(tp.tok, tp)); + } else { + var cl = (TopLevelDeclWithMembers)overridingFunction.EnclosingClass; + var typeMap = cl.ParentFormalTypeParametersToActuals; + tyargs = f.EnclosingClass.TypeArgs.ConvertAll(tp => typeMap[tp]); + } + tyargs.AddRange((overridingFunction ?? f).TypeArgs.ConvertAll(tp => new UserDefinedType(tp.tok, tp))); + return tyargs; + } - for (int i = 0; i < N; i++) { - Expression e0 = calleeDecreases[i]; - Expression e1 = Substitute(contextDecreases[i], null, substMap); - if (!CompatibleDecreasesTypes(e0.Type, e1.Type)) { - N = i; - break; - } - toks.Add(new NestedToken(original.Tok, e1.tok)); - types0.Add(e0.Type.NormalizeExpand()); - types1.Add(e1.Type.NormalizeExpand()); - callee.Add(etran.TrExpr(e0)); - caller.Add(etran.TrExpr(e1)); - } - - var decrCountT = contextDecreases.Count; - var decrCountC = calleeDecreases.Count; - // Generally, we want to produce a check "decrClass <= decrTrait", allowing (the common case where) they are equal. - // * If N < decrCountC && N < decrCountT, then "decrClass <= decrTrait" if the comparison ever gets beyond the - // parts that survived truncation. Thus, we compare with "allowNoChange" set to "false". - // Otherwise: - // * If decrCountC == decrCountT, then the truncation we did above had no effect and we pass in "allowNoChange" as "true". - // * If decrCountC > decrCountT, then we will have truncated decrClass above. Let x,y and x' denote decrClass and - // decrTrait, respectively, where x and x' have the same length. Considering how Dafny in effect pads the end of - // decreases tuples with a \top, we were supposed to evaluate (x,(y,\top)) <= (x',\top), which by lexicographic pairs - // we can expand to: - // x <= x' && (x == x' ==> (y,\top) <= \top) - // which is equivalent to just x <= x'. Thus, we called DecreasesCheck to compare x and x' and we pass in "allowNoChange" - // as "true". - // * If decrCountC < decrCountT, then we will have truncated decrTrait above. Let x and x',y' denote decrClass and - // decrTrait, respectively, where x and x' have the same length. We then want to check (x,\top) <= (x',(y',\top)), which - // expands to: - // x <= x' && (x == x' ==> \top <= (y',\top)) - // = { \top is strictly larger than a pair } - // x <= x' && (x == x' ==> false) - // = - // x < x' - // So we perform our desired check by calling DecreasesCheck to strictly compare x and x', so we pass in "allowNoChange" - // as "false". - bool allowNoChange = N == decrCountT && decrCountT <= decrCountC; - var decrChk = DecreasesCheck(toks, types0, types1, callee, caller, null, null, allowNoChange, false); - builder.Add(Assert(original.Tok, decrChk, string.Format("{0}'s decreases clause must be below or equal to that in the trait", original.WhatKind))); - } - - private void AddMethodOverrideSubsetChk(Method m, BoogieStmtListBuilder builder, ExpressionTranslator etran, List localVariables, Dictionary substMap) { + private void AddFunctionOverrideSubsetChk(Function func, BoogieStmtListBuilder builder, ExpressionTranslator etran, List localVariables, Dictionary substMap) { //getting framePrime List traitFrameExps = new List(); - List classFrameExps = m.Mod != null ? m.Mod.Expressions : new List(); - if (m.OverriddenMethod.Mod != null) { - foreach (var e in m.OverriddenMethod.Mod.Expressions) { - var newE = Substitute(e.E, null, substMap); - FrameExpression fe = new FrameExpression(e.tok, newE, e.FieldName); - traitFrameExps.Add(fe); - } + foreach (var e in func.OverriddenFunction.Reads) { + var newE = Substitute(e.E, null, substMap); + FrameExpression fe = new FrameExpression(e.tok, newE, e.FieldName); + traitFrameExps.Add(fe); } - QKeyValue kv = etran.TrAttributes(m.Attributes, null); + QKeyValue kv = etran.TrAttributes(func.Attributes, null); - IToken tok = m.tok; + IToken tok = func.tok; // Declare a local variable $_Frame: [ref, Field alpha]bool - Bpl.IdentifierExpr traitFrame = etran.TheFrame(m.OverriddenMethod.tok); // this is a throw-away expression, used only to extract the type and name of the $_Frame variable - traitFrame.Name = m.EnclosingClass.Name + "_" + traitFrame.Name; + Bpl.IdentifierExpr traitFrame = etran.TheFrame(func.OverriddenFunction.tok); // this is a throw-away expression, used only to extract the type and name of the $_Frame variable + traitFrame.Name = func.EnclosingClass.Name + "_" + traitFrame.Name; Contract.Assert(traitFrame.Type != null); // follows from the postcondition of TheFrame Bpl.LocalVariable frame = new Bpl.LocalVariable(tok, new Bpl.TypedIdent(tok, null ?? traitFrame.Name, traitFrame.Type)); localVariables.Add(frame); @@ -5380,36 +3483,46 @@ private void AddMethodOverrideSubsetChk(Method m, BoogieStmtListBuilder builder, builder.Add(Bpl.Cmd.SimpleAssign(tok, new Bpl.IdentifierExpr(tok, frame), lambda)); // emit: assert (forall o: ref, f: Field alpha :: o != null && $Heap[o,alloc] && (o,f) in subFrame ==> $_Frame[o,f]); - Bpl.Expr oInCallee = InRWClause(tok, o, f, classFrameExps, etran, null, null); + Bpl.Expr oInCallee = InRWClause(tok, o, f, func.Reads, etran, null, null); Bpl.Expr consequent2 = InRWClause(tok, o, f, traitFrameExps, etran, null, null); Bpl.Expr q = new Bpl.ForallExpr(tok, new List { alpha }, new List { oVar, fVar }, Bpl.Expr.Imp(Bpl.Expr.And(ante, oInCallee), consequent2)); - builder.Add(Assert(tok, q, "expression may modify an object not in the parent trait context's modifies clause", kv)); + builder.Add(Assert(tok, q, "expression may read an object not in the parent trait context's reads clause", kv)); } - private void InsertChecksum(Method m, Bpl.Declaration decl, bool specificationOnly = false) { - Contract.Requires(VisibleInScope(m)); - byte[] data; - using (var writer = new System.IO.StringWriter()) { - var printer = new Printer(writer); - printer.PrintAttributes(m.Attributes); - printer.PrintFormals(m.Ins, m); - if (m.Outs.Any()) { - writer.Write("returns "); - printer.PrintFormals(m.Outs, m); - } - printer.PrintSpec("", m.Req, 0); - printer.PrintFrameSpecLine("", m.Mod.Expressions, 0, null); - printer.PrintSpec("", m.Ens, 0); - printer.PrintDecreasesSpec(m.Decreases, 0); - writer.WriteLine(); - if (!specificationOnly && m.Body != null && RevealedInScope(m)) { - printer.PrintStatement(m.Body, 0); + private void AddFunctionOverrideReqsChk(Function f, BoogieStmtListBuilder builder, ExpressionTranslator etran, Dictionary substMap) { + Contract.Requires(f != null); + Contract.Requires(builder != null); + Contract.Requires(etran != null); + Contract.Requires(substMap != null); + //generating trait pre-conditions with class variables + foreach (var req in f.OverriddenFunction.Req) { + Expression precond = Substitute(req.E, null, substMap); + builder.Add(TrAssumeCmd(f.tok, etran.TrExpr(precond))); + } + //generating class pre-conditions + foreach (var req in f.Req) { + bool splitHappened; // we actually don't care + foreach (var s in TrSplitExpr(req.E, etran, false, out splitHappened)) { + if (s.IsChecked) { + builder.Add(Assert(f.tok, s.E, "the function must provide an equal or more permissive precondition than in its parent trait")); + } } - data = Encoding.UTF8.GetBytes(writer.ToString()); } + } - InsertChecksum(decl, data); + private void HavocMethodFrameLocations(Method m, BoogieStmtListBuilder builder, ExpressionTranslator etran, List localVariables) { + Contract.Requires(m != null); + Contract.Requires(m.EnclosingClass != null && m.EnclosingClass is ClassDecl); + + // play havoc with the heap according to the modifies clause + builder.Add(new Bpl.HavocCmd(m.tok, new List { (Bpl.IdentifierExpr/*TODO: this cast is rather dubious*/)etran.HeapExpr })); + // assume the usual two-state boilerplate information + foreach (BoilerplateTriple tri in GetTwoStateBoilerplate(m.tok, m.Mod.Expressions, m.IsGhost, m.AllowsAllocation, etran.Old, etran, etran.Old)) { + if (tri.IsFree) { + builder.Add(TrAssumeCmd(m.tok, tri.Expr)); + } + } } private void InsertChecksum(DatatypeDecl d, Bpl.Declaration decl) { @@ -8654,12 +6767,13 @@ public string FunctionHandle(Function f) { // = [Box] F(Ty1, .., TyN, Layer, Heap, self, [Unbox] arg1, .., [Unbox] argN) var fhandle = FunctionCall(f.tok, name, predef.HandleType, SnocSelf(SnocPrevH(args))); - var lhs = FunctionCall(f.tok, Apply(arity), TrType(f.ResultType), Concat(tyargs, Cons(h, Cons(fhandle, lhs_args)))); + var lhs = FunctionCall(f.tok, Apply(arity), TrType(f.ResultType), + Concat(tyargs, Cons(h, Cons(fhandle, lhs_args)))); var args_h = AlwaysUseHeap || f.ReadsHeap ? Snoc(SnocPrevH(args), h) : args; var rhs = FunctionCall(f.tok, f.FullSanitizedName, TrType(f.ResultType), Concat(SnocSelf(args_h), rhs_args)); var rhs_boxed = BoxIfUnboxed(rhs, f.ResultType); - sink.AddTopLevelDeclaration(new Axiom(f.tok, + AddIncludeDepAxiom(new Axiom(f.tok, BplForall(Concat(vars, bvars), BplTrigger(lhs), Bpl.Expr.Eq(lhs, rhs_boxed)))); } @@ -8678,7 +6792,7 @@ public string FunctionHandle(Function f) { rhs = FunctionCall(f.tok, RequiresName(f), Bpl.Type.Bool, Concat(SnocSelf(args_h), rhs_args)); } - sink.AddTopLevelDeclaration(new Axiom(f.tok, + AddIncludeDepAxiom(new Axiom(f.tok, BplForall(Concat(vars, bvars), BplTrigger(lhs), Bpl.Expr.Eq(lhs, rhs)))); } @@ -8711,7 +6825,7 @@ public string FunctionHandle(Function f) { var rhs_unboxed = UnboxIfBoxed(rhs, f.ResultType); var tr = BplTriggerHeap(this, f.tok, lhs, AlwaysUseHeap || f.ReadsHeap ? null : h); - sink.AddTopLevelDeclaration(new Axiom(f.tok, + AddIncludeDepAxiom(new Axiom(f.tok, BplForall(Concat(vars, func_vars), tr, Bpl.Expr.Eq(lhs, rhs_unboxed)))); } } @@ -8866,7 +6980,7 @@ private void AddArrowTypeAxioms(ArrowTypeDecl ad) { if (selectorVar == "r") { op = (u, v) => Bpl.Expr.Imp(v, u); } - sink.AddTopLevelDeclaration(new Axiom(tok, + AddIncludeDepAxiom(new Axiom(tok, BplForall(bvars, BplTrigger(lhs), op(lhs, rhs)))); }; SelectorSemantics(Apply(arity), predef.BoxType, "h", apply_ty, Requires(arity), requires_ty); @@ -9226,78 +7340,29 @@ private string AddTyAxioms(TopLevelDecl td) { Contract.Requires(td != null); IToken tok = td.tok; - var ty_repr = TrType(UserDefinedType.FromTopLevelDecl(td.tok, td)); - var arity = td.TypeArgs.Count; - var inner_name = GetClass(td).TypedIdent.Name; - string name = "T" + inner_name; - // Create the type constructor - if (td is ClassDecl cl && cl.IsObjectTrait) { - // the type constructor for "object" is in DafnyPrelude.bpl - } else if (td is TupleTypeDecl ttd && ttd.Dims == 2 && ttd.NonGhostDims == 2) { - // the type constructor for "Tuple2" is in DafnyPrelude.bpl - } else { - Bpl.Variable tyVarOut = BplFormalVar(null, predef.Ty, false); - List args = new List( - Enumerable.Range(0, arity).Select(i => - (Bpl.Variable)BplFormalVar(null, predef.Ty, true))); - var func = new Bpl.Function(tok, name, args, tyVarOut); - sink.AddTopLevelDeclaration(func); - } - - // Helper action to create variables and the function call. - Action, List, Bpl.Expr>> Helper = K => { - List argExprs; - var args = MkTyParamBinders(td.TypeArgs, out argExprs); - var inner = FunctionCall(tok, name, predef.Ty, argExprs); - K(argExprs, args, inner); - }; - - /* Create the Tag and calling Tag on this type constructor - * - * The common case: - * const unique TagList: TyTag; - * const unique tytagFamily$List: TyTagFamily; // defined once for each type named "List" - * axiom (forall t0: Ty :: { List(t0) } Tag(List(t0)) == TagList && TagFamily(List(t0)) == tytagFamily$List); - * For types obtained via an abstract import, just do: - * const unique tytagFamily$List: TyTagFamily; // defined once for each type named "List" - * axiom (forall t0: Ty :: { List(t0) } TagFamily(List(t0)) == tytagFamily$List); - */ - Helper((argExprs, args, inner) => { - Bpl.Expr body = Bpl.Expr.True; - - if (!td.EnclosingModuleDefinition.IsFacade) { - var tagName = "Tag" + inner_name; - var tag = new Bpl.Constant(tok, new Bpl.TypedIdent(tok, tagName, predef.TyTag), true); - sink.AddTopLevelDeclaration(tag); - body = Bpl.Expr.Eq(FunctionCall(tok, "Tag", predef.TyTag, inner), new Bpl.IdentifierExpr(tok, tag)); - } - - if (!tytagConstants.TryGetValue(td.Name, out var tagFamily)) { - tagFamily = new Bpl.Constant(Token.NoToken, new Bpl.TypedIdent(Token.NoToken, "tytagFamily$" + td.Name, predef.TyTagFamily), true); - tytagConstants.Add(td.Name, tagFamily); - } - body = BplAnd(body, Bpl.Expr.Eq(FunctionCall(tok, "TagFamily", predef.TyTagFamily, inner), new Bpl.IdentifierExpr(tok, tagFamily))); + var func = GetOrCreateTypeConstructor(td); + var name = func.Name; - var qq = BplForall(args, BplTrigger(inner), body); - sink.AddTopLevelDeclaration(new Axiom(tok, qq, name + " Tag")); - }); + var tagAxiom = CreateTagAndCallingForTypeConstructor(td); + AddOtherDefinition(func, tagAxiom); // Create the injectivity axiom and its function /* function List_0(Ty) : Ty; axiom (forall t0: Ty :: { List(t0) } List_0(List(t0)) == t0); */ - for (int i = 0; i < arity; i++) { - Helper((argExprs, args, inner) => { - Bpl.Variable tyVarIn = BplFormalVar(null, predef.Ty, true); - Bpl.Variable tyVarOut = BplFormalVar(null, predef.Ty, false); - var injname = name + "_" + i; - var injfunc = new Bpl.Function(tok, injname, Singleton(tyVarIn), tyVarOut); - var outer = FunctionCall(tok, injname, args[i].TypedIdent.Type, inner); - Bpl.Expr qq = BplForall(args, BplTrigger(inner), Bpl.Expr.Eq(outer, argExprs[i])); - sink.AddTopLevelDeclaration(new Axiom(tok, qq, name + " injectivity " + i)); - sink.AddTopLevelDeclaration(injfunc); - }); + for (int i = 0; i < func.InParams.Count; i++) { + var args = MkTyParamBinders(td.TypeArgs, out var argExprs); + var inner = FunctionCall(tok, name, predef.Ty, argExprs); + Bpl.Variable tyVarIn = BplFormalVar(null, predef.Ty, true); + Bpl.Variable tyVarOut = BplFormalVar(null, predef.Ty, false); + var injname = name + "_" + i; + var injfunc = new Bpl.Function(tok, injname, Singleton(tyVarIn), tyVarOut); + sink.AddTopLevelDeclaration(injfunc); + var outer = FunctionCall(tok, injname, args[i].TypedIdent.Type, inner); + Bpl.Expr qq = BplForall(args, BplTrigger(inner), Bpl.Expr.Eq(outer, argExprs[i])); + var injectivityAxiom = new Axiom(tok, qq, name + " injectivity " + i); + AddOtherDefinition(injfunc, injectivityAxiom); } // Boxing axiom (important for the properties of unbox) @@ -9309,15 +7374,76 @@ private string AddTyAxioms(TopLevelDecl td) { && $Is($Unbox(bx): DatatypeType, List(T))); */ if (!ModeledAsBoxType(UserDefinedType.FromTopLevelDecl(td.tok, td))) { - Helper((argExprs, args, _inner) => { - var typeTerm = FunctionCall(tok, name, predef.Ty, argExprs); - AddBoxUnboxAxiom(tok, name, typeTerm, ty_repr, args); - }); + var args = MkTyParamBinders(td.TypeArgs, out var argExprs); + var ty_repr = TrType(UserDefinedType.FromTopLevelDecl(td.tok, td)); + var typeTerm = FunctionCall(tok, name, predef.Ty, argExprs); + AddBoxUnboxAxiom(tok, name, typeTerm, ty_repr, args); } return name; } + private Bpl.Function GetOrCreateTypeConstructor(TopLevelDecl td) { + Bpl.Function func; + if (td is ClassDecl cl && cl.IsObjectTrait) { + // the type constructor for "object" is in DafnyPrelude.bpl + func = predef.ObjectTypeConstructor; + } else if (td is TupleTypeDecl ttd && ttd.Dims == 2 && ttd.NonGhostDims == 2) { + // the type constructor for "Tuple2" is in DafnyPrelude.bpl + func = this.predef.Tuple2TypeConstructor; + } else { + var inner_name = GetClass(td).TypedIdent.Name; + string name = "T" + inner_name; + + Bpl.Variable tyVarOut = BplFormalVar(null, predef.Ty, false); + var args = Enumerable.Range(0, td.TypeArgs.Count).Select(i => (Bpl.Variable)BplFormalVar(null, predef.Ty, true)).ToList(); + func = new Bpl.Function(td.tok, name, args, tyVarOut); + sink.AddTopLevelDeclaration(func); + } + + return func; + } + + /* Create the Tag and calling Tag on this type constructor + * + * The common case: + * const unique TagList: TyTag; + * const unique tytagFamily$List: TyTagFamily; // defined once for each type named "List" + * axiom (forall t0: Ty :: { List(t0) } Tag(List(t0)) == TagList && TagFamily(List(t0)) == tytagFamily$List); + * For types obtained via an abstract import, just do: + * const unique tytagFamily$List: TyTagFamily; // defined once for each type named "List" + * axiom (forall t0: Ty :: { List(t0) } TagFamily(List(t0)) == tytagFamily$List); + */ + private Axiom CreateTagAndCallingForTypeConstructor(TopLevelDecl td) { + IToken tok = td.tok; + var inner_name = GetClass(td).TypedIdent.Name; + string name = "T" + inner_name; + + var args = MkTyParamBinders(td.TypeArgs, out var argExprs); + var inner = FunctionCall(tok, name, predef.Ty, argExprs); + Bpl.Expr body = Bpl.Expr.True; + + if (!td.EnclosingModuleDefinition.IsFacade) { + var tagName = "Tag" + inner_name; + var tag = new Bpl.Constant(tok, new Bpl.TypedIdent(tok, tagName, predef.TyTag), true); + sink.AddTopLevelDeclaration(tag); + body = Bpl.Expr.Eq(FunctionCall(tok, "Tag", predef.TyTag, inner), new Bpl.IdentifierExpr(tok, tag)); + } + + if (!tytagConstants.TryGetValue(td.Name, out var tagFamily)) { + tagFamily = new Bpl.Constant(Token.NoToken, + new Bpl.TypedIdent(Token.NoToken, "tytagFamily$" + td.Name, predef.TyTagFamily), true); + tytagConstants.Add(td.Name, tagFamily); + } + + body = BplAnd(body, + Bpl.Expr.Eq(FunctionCall(tok, "TagFamily", predef.TyTagFamily, inner), new Bpl.IdentifierExpr(tok, tagFamily))); + + var qq = BplForall(args, BplTrigger(inner), body); + var tagAxiom = new Axiom(tok, qq, name + " Tag"); + return tagAxiom; + } + /// /// Generate: /// axiom (forall args: Ty, bx: Box :: @@ -9402,7 +7528,7 @@ Bpl.Constant GetField(Field f) { var ig = FunctionCall(f.tok, BuiltinFunction.IsGhostField, ty, Bpl.Expr.Ident(fc)); cond = Bpl.Expr.And(cond, f.IsGhost ? ig : Bpl.Expr.Not(ig)); Bpl.Axiom ax = new Bpl.Axiom(f.tok, cond); - sink.AddTopLevelDeclaration(ax); + AddIncludeDepAxiom(ax); } return fc; } @@ -9510,12 +7636,13 @@ Bpl.Expr GetField(MemberSelectExpr fse) { /// /// This method is expected to be called just once for each function in the program. /// - void AddFunction(Function f) { + Bpl.Function AddFunction(Function f) { Contract.Requires(f != null); Contract.Requires(predef != null && sink != null); // declare the function - if (!f.IsBuiltin) { + Bpl.Function func; + { var formals = new List(); formals.AddRange(MkTyParamFormals(GetTypeParams(f))); if (f.IsFuelAware()) { @@ -9534,7 +7661,7 @@ void AddFunction(Function f) { formals.Add(new Bpl.Formal(p.tok, new Bpl.TypedIdent(p.tok, p.AssignUniqueName(f.IdGenerator), TrType(p.Type)), true)); } var res = new Bpl.Formal(f.tok, new Bpl.TypedIdent(f.tok, Bpl.TypedIdent.NoName, TrType(f.ResultType)), false); - var func = new Bpl.Function(f.tok, f.FullSanitizedName, new List(), formals, res, "function declaration for " + f.FullName); + func = new Bpl.Function(f.tok, f.FullSanitizedName, new List(), formals, res, "function declaration for " + f.FullName); if (InsertChecksums) { InsertChecksum(f, func); } @@ -9561,6 +7688,8 @@ void AddFunction(Function f) { var canCallF = new Bpl.Function(f.tok, f.FullSanitizedName + "#canCall", new List(), formals, res); sink.AddTopLevelDeclaration(canCallF); } + + return func; } /// @@ -9593,165 +7722,6 @@ void AddFunction(Function f) { /// enum MethodTranslationKind { SpecWellformedness, Call, CoCall, Implementation, OverrideCheck } - /// - /// This method is expected to be called at most once for each parameter combination, and in particular - /// at most once for each value of "kind". - /// - Bpl.Procedure AddMethod(Method m, MethodTranslationKind kind) { - Contract.Requires(m != null); - Contract.Requires(m.EnclosingClass != null); - Contract.Requires(predef != null); - Contract.Requires(currentModule == null && codeContext == null && isAllocContext == null); - Contract.Ensures(currentModule == null && codeContext == null && isAllocContext == null); - Contract.Ensures(Contract.Result() != null); - Contract.Assert(VisibleInScope(m)); - - currentModule = m.EnclosingClass.EnclosingModuleDefinition; - codeContext = m; - isAllocContext = new IsAllocContext(m.IsGhost); - - Bpl.Expr prevHeap = null; - Bpl.Expr currHeap = null; - var ordinaryEtran = new ExpressionTranslator(this, predef, m.tok); - ExpressionTranslator etran; - var inParams = new List(); - if (m is TwoStateLemma) { - var prevHeapVar = new Bpl.Formal(m.tok, new Bpl.TypedIdent(m.tok, "previous$Heap", predef.HeapType), true); - var currHeapVar = new Bpl.Formal(m.tok, new Bpl.TypedIdent(m.tok, "current$Heap", predef.HeapType), true); - inParams.Add(prevHeapVar); - inParams.Add(currHeapVar); - prevHeap = new Bpl.IdentifierExpr(m.tok, prevHeapVar); - currHeap = new Bpl.IdentifierExpr(m.tok, currHeapVar); - etran = new ExpressionTranslator(this, predef, currHeap, prevHeap); - } else { - etran = ordinaryEtran; - } - - List outParams; - GenerateMethodParameters(m.tok, m, kind, etran, inParams, out outParams); - - var req = new List(); - var mod = new List(); - var ens = new List(); - // FREE PRECONDITIONS - if (kind == MethodTranslationKind.SpecWellformedness || kind == MethodTranslationKind.Implementation || kind == MethodTranslationKind.OverrideCheck) { // the other cases have no need for a free precondition - // free requires mh == ModuleContextHeight && fh == FunctionContextHeight; - req.Add(Requires(m.tok, true, etran.HeightContext(kind == MethodTranslationKind.OverrideCheck ? m.OverriddenMethod : m), null, null)); - if (m is TwoStateLemma) { - // free requires prevHeap == Heap && HeapSucc(prevHeap, currHeap) && IsHeap(currHeap) - var a0 = Bpl.Expr.Eq(prevHeap, ordinaryEtran.HeapExpr); - var a1 = HeapSucc(prevHeap, currHeap); - var a2 = FunctionCall(m.tok, BuiltinFunction.IsGoodHeap, null, currHeap); - req.Add(Requires(m.tok, true, BplAnd(a0, BplAnd(a1, a2)), null, null)); - } - } - if (m is TwoStateLemma) { - // Checked preconditions that old parameters really existed in previous state - var index = 0; - foreach (var formal in m.Ins) { - if (formal.IsOld) { - var dafnyFormalIdExpr = new IdentifierExpr(formal.tok, formal); - req.Add(Requires(formal.tok, false, MkIsAlloc(etran.TrExpr(dafnyFormalIdExpr), formal.Type, prevHeap), - string.Format("parameter{0} ('{1}') must be allocated in the two-state lemma's previous state", - m.Ins.Count == 1 ? "" : " " + index, formal.Name), null)); - } - index++; - } - } - mod.Add((Bpl.IdentifierExpr/*TODO: this cast is somewhat dubious*/)ordinaryEtran.HeapExpr); - mod.Add(etran.Tick()); - - var bodyKind = kind == MethodTranslationKind.SpecWellformedness || kind == MethodTranslationKind.Implementation; - - if (kind != MethodTranslationKind.SpecWellformedness && kind != MethodTranslationKind.OverrideCheck) { - // USER-DEFINED SPECIFICATIONS - var comment = "user-defined preconditions"; - foreach (var p in m.Req) { - string errorMessage = CustomErrorMessage(p.Attributes); - if (p.Label != null && kind == MethodTranslationKind.Implementation) { - // don't include this precondition here, but record it for later use - p.Label.E = (m is TwoStateLemma ? ordinaryEtran : etran.Old).TrExpr(p.E); - } else { - foreach (var s in TrSplitExprForMethodSpec(p.E, etran, kind)) { - if (s.IsOnlyChecked && bodyKind) { - // don't include in split - } else if (s.IsOnlyFree && !bodyKind) { - // don't include in split -- it would be ignored, anyhow - } else { - req.Add(Requires(s.E.tok, s.IsOnlyFree, s.E, errorMessage, comment)); - comment = null; - // the free here is not linked to the free on the original expression (this is free things generated in the splitting.) - } - } - } - } - comment = "user-defined postconditions"; - foreach (var p in m.Ens) { - string errorMessage = CustomErrorMessage(p.Attributes); - AddEnsures(ens, Ensures(p.E.tok, true, CanCallAssumption(p.E, etran), errorMessage, comment)); - comment = null; - foreach (var s in TrSplitExprForMethodSpec(p.E, etran, kind)) { - var post = s.E; - if (kind == MethodTranslationKind.Implementation && RefinementToken.IsInherited(s.E.tok, currentModule)) { - // this postcondition was inherited into this module, so make it into the form "$_reverifyPost ==> s.E" - post = Bpl.Expr.Imp(new Bpl.IdentifierExpr(s.E.tok, "$_reverifyPost", Bpl.Type.Bool), post); - } - if (s.IsOnlyFree && bodyKind) { - // don't include in split -- it would be ignored, anyhow - } else if (s.IsOnlyChecked && !bodyKind) { - // don't include in split - } else { - AddEnsures(ens, Ensures(s.E.tok, s.IsOnlyFree, post, errorMessage, null)); - } - } - } - if (m is Constructor && kind == MethodTranslationKind.Call) { - var fresh = Bpl.Expr.Not(etran.Old.IsAlloced(m.tok, new Bpl.IdentifierExpr(m.tok, "this", TrReceiverType(m)))); - AddEnsures(ens, Ensures(m.tok, false, fresh, null, "constructor allocates the object")); - } - foreach (BoilerplateTriple tri in GetTwoStateBoilerplate(m.tok, m.Mod.Expressions, m.IsGhost, m.AllowsAllocation, ordinaryEtran.Old, ordinaryEtran, ordinaryEtran.Old)) { - AddEnsures(ens, Ensures(tri.tok, tri.IsFree, tri.Expr, tri.ErrorMessage, tri.Comment)); - } - - // add the fuel assumption for the reveal method of a opaque method - if (IsOpaqueRevealLemma(m)) { - List args = Attributes.FindExpressions(m.Attributes, "fuel"); - if (args != null) { - MemberSelectExpr selectExpr = args[0].Resolved as MemberSelectExpr; - if (selectExpr != null) { - Function f = selectExpr.Member as Function; - FuelConstant fuelConstant = this.functionFuel.Find(x => x.f == f); - if (fuelConstant != null) { - Bpl.Expr startFuel = fuelConstant.startFuel; - Bpl.Expr startFuelAssert = fuelConstant.startFuelAssert; - Bpl.Expr moreFuel_expr = fuelConstant.MoreFuel(sink, predef, f.IdGenerator); - Bpl.Expr layer = etran.layerInterCluster.LayerN(1, moreFuel_expr); - Bpl.Expr layerAssert = etran.layerInterCluster.LayerN(2, moreFuel_expr); - - AddEnsures(ens, Ensures(m.tok, true, Bpl.Expr.Eq(startFuel, layer), null, null)); - AddEnsures(ens, Ensures(m.tok, true, Bpl.Expr.Eq(startFuelAssert, layerAssert), null, null)); - - AddEnsures(ens, Ensures(m.tok, true, Bpl.Expr.Eq(FunctionCall(f.tok, BuiltinFunction.AsFuelBottom, null, moreFuel_expr), moreFuel_expr), null, "Shortcut to LZ")); - } - } - } - } - } - - var name = MethodName(m, kind); - var proc = new Bpl.Procedure(m.tok, name, new List(), inParams, outParams, req, mod, ens, etran.TrAttributes(m.Attributes, null)); - - if (InsertChecksums) { - InsertChecksum(m, proc, true); - } - - currentModule = null; - codeContext = null; - isAllocContext = null; - - return proc; - } - static string MethodName(ICodeContext m, MethodTranslationKind kind) { Contract.Requires(m != null); switch (kind) { @@ -12221,7 +10191,7 @@ Expression LetDesugaring(LetExpr e) { var p = Substitute(e.RHSs[0], receiverReplacement, substMap); Bpl.Expr ax = Bpl.Expr.Imp(canCall, BplAnd(antecedent, etranCC.TrExpr(p))); ax = BplForall(gg, tr, ax); - sink.AddTopLevelDeclaration(new Bpl.Axiom(e.tok, ax)); + AddIncludeDepAxiom(new Bpl.Axiom(e.tok, ax)); } // now that we've declared the functions and axioms, let's prepare the let-such-that desugaring diff --git a/Source/DafnyDriver/DafnyDriver.cs b/Source/DafnyDriver/DafnyDriver.cs index 8afbb72d8aa..966239d2358 100644 --- a/Source/DafnyDriver/DafnyDriver.cs +++ b/Source/DafnyDriver/DafnyDriver.cs @@ -347,7 +347,7 @@ private static string BoogieProgramSuffix(string printFile, string suffix) { if (CommandLineOptions.Clo.PrintFile != null) { - var nm = nmodules > 1 ? BoogieProgramSuffix(CommandLineOptions.Clo.PrintFile, prog.Item1) : CommandLineOptions.Clo.PrintFile; + var nm = nmodules > 1 ? Dafny.Main.BoogieProgramSuffix(CommandLineOptions.Clo.PrintFile, prog.Item1) : CommandLineOptions.Clo.PrintFile; ExecutionEngine.PrintBplFile(nm, prog.Item2, false, false, CommandLineOptions.Clo.PrettyPrint); } @@ -357,41 +357,10 @@ private static string BoogieProgramSuffix(string printFile, string suffix) { } } - public static bool BoogieOnce(string baseFile, string moduleName, Bpl.Program boogieProgram, string programId, - out PipelineStatistics stats, out PipelineOutcome oc) { - if (programId == null) { - programId = "main_program_id"; - } - programId += "_" + moduleName; - - string bplFilename; - if (CommandLineOptions.Clo.PrintFile != null) { - bplFilename = CommandLineOptions.Clo.PrintFile; - } else { - string baseName = cce.NonNull(Path.GetFileName(baseFile)); - baseName = cce.NonNull(Path.ChangeExtension(baseName, "bpl")); - bplFilename = Path.Combine(Path.GetTempPath(), baseName); - } - - bplFilename = BoogieProgramSuffix(bplFilename, moduleName); - stats = null; - oc = BoogiePipelineWithRerun(boogieProgram, bplFilename, out stats, 1 < Dafny.DafnyOptions.Clo.VerifySnapshots ? programId : null); - return IsBoogieVerified(oc, stats); - } - - public static bool IsBoogieVerified(PipelineOutcome outcome, PipelineStatistics statistics) { - return (outcome == PipelineOutcome.Done || outcome == PipelineOutcome.VerificationCompleted) - && statistics.ErrorCount == 0 - && statistics.InconclusiveCount == 0 - && statistics.TimeoutCount == 0 - && statistics.OutOfResourceCount == 0 - && statistics.OutOfMemoryCount == 0; - } - - public static bool Boogie(string baseName, IEnumerable> boogiePrograms, string programId, out Dictionary statss, out PipelineOutcome oc) { + public static bool Boogie(string baseName, IEnumerable> boogiePrograms, string programId, out Dictionary statss, out PipelineOutcome outcome) { bool isVerified = true; - oc = PipelineOutcome.VerificationCompleted; + outcome = PipelineOutcome.VerificationCompleted; statss = new Dictionary(); Stopwatch watch = new Stopwatch(); @@ -402,12 +371,12 @@ public static bool Boogie(string baseName, IEnumerable otherFile return compiled; } - /// - /// Resolve, type check, infer invariants for, and verify the given Boogie program. - /// The intention is that this Boogie program has been produced by translation from something - /// else. Hence, any resolution errors and type checking errors are due to errors in - /// the translation. - /// The method prints errors for resolution and type checking errors, but still returns - /// their error code. - /// - static PipelineOutcome BoogiePipelineWithRerun(Bpl.Program/*!*/ program, string/*!*/ bplFileName, - out PipelineStatistics stats, string programId) { - Contract.Requires(program != null); - Contract.Requires(bplFileName != null); - Contract.Ensures(0 <= Contract.ValueAtReturn(out stats).InconclusiveCount && 0 <= Contract.ValueAtReturn(out stats).TimeoutCount); - - stats = new PipelineStatistics(); - CivlTypeChecker ctc; - PipelineOutcome oc = ExecutionEngine.ResolveAndTypecheck(program, bplFileName, out ctc); - switch (oc) { - case PipelineOutcome.Done: - return oc; - - case PipelineOutcome.ResolutionError: - case PipelineOutcome.TypeCheckingError: { - ExecutionEngine.PrintBplFile(bplFileName, program, false, false, CommandLineOptions.Clo.PrettyPrint); - Console.WriteLine(); - Console.WriteLine("*** Encountered internal translation error - re-running Boogie to get better debug information"); - Console.WriteLine(); - - List/*!*/ fileNames = new List(); - fileNames.Add(bplFileName); - Bpl.Program reparsedProgram = ExecutionEngine.ParseBoogieProgram(fileNames, true); - if (reparsedProgram != null) { - ExecutionEngine.ResolveAndTypecheck(reparsedProgram, bplFileName, out ctc); - } - } - return oc; - - case PipelineOutcome.ResolvedAndTypeChecked: - ExecutionEngine.EliminateDeadVariables(program); - ExecutionEngine.CollectModSets(program); - ExecutionEngine.CoalesceBlocks(program); - ExecutionEngine.Inline(program); - return ExecutionEngine.InferAndVerify(program, stats, programId); - - default: - Contract.Assert(false); throw new cce.UnreachableException(); // unexpected outcome - } - } - #region Output diff --git a/Source/DafnyLanguageServer.Test/Various/CounterExampleTest.cs b/Source/DafnyLanguageServer.Test/Various/CounterExampleTest.cs index bccdbd3acd1..fefc5513d87 100644 --- a/Source/DafnyLanguageServer.Test/Various/CounterExampleTest.cs +++ b/Source/DafnyLanguageServer.Test/Various/CounterExampleTest.cs @@ -763,7 +763,6 @@ method a(multiplier:int) { await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); var counterExamples = (await RequestCounterExamples(documentItem.Uri)).ToArray(); Assert.AreEqual(2, counterExamples.Length); - Assert.AreEqual(4, counterExamples[1].Variables.Count); Assert.IsTrue(counterExamples[1].Variables.ContainsKey("multiplier:int")); Assert.IsTrue(counterExamples[1].Variables.ContainsKey("s:seq")); StringAssert.Matches(counterExamples[1].Variables["s:seq"], new Regex("\\(Length := 3, .*\\[2\\] := 6.*\\)")); diff --git a/Source/DafnyLanguageServer/Language/DafnyProgramVerifier.cs b/Source/DafnyLanguageServer/Language/DafnyProgramVerifier.cs index f9e209f3b76..ad6004e1926 100644 --- a/Source/DafnyLanguageServer/Language/DafnyProgramVerifier.cs +++ b/Source/DafnyLanguageServer/Language/DafnyProgramVerifier.cs @@ -99,7 +99,7 @@ private bool VerifyWithBoogie(Boogie.Program program, CancellationToken cancella try { var statistics = new PipelineStatistics(); var outcome = ExecutionEngine.InferAndVerify(program, statistics, uniqueId, error => { }, uniqueId); - return DafnyDriver.IsBoogieVerified(outcome, statistics); + return Main.IsBoogieVerified(outcome, statistics); } catch (Exception e) when (e is not OperationCanceledException) { if (!cancellationToken.IsCancellationRequested) { throw; diff --git a/Source/DafnyPipeline.Test/IntraMethodVerificationStability.cs b/Source/DafnyPipeline.Test/IntraMethodVerificationStability.cs index 93ce4fed596..723b531b7d2 100644 --- a/Source/DafnyPipeline.Test/IntraMethodVerificationStability.cs +++ b/Source/DafnyPipeline.Test/IntraMethodVerificationStability.cs @@ -1,57 +1,155 @@ -using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Boogie; using Microsoft.Dafny; using Xunit; +using Xunit.Abstractions; using BoogieProgram = Microsoft.Boogie.Program; using Parser = Microsoft.Dafny.Parser; -using Type = System.Type; namespace DafnyPipeline.Test { // Main.Resolve has static shared state (TypeConstraint.ErrorsToBeReported for example) // so we can't execute tests that use it in parallel. [Collection("Singleton Test Collection - Resolution")] public class IntraMethodVerificationStability { - [Fact] - public void NoUniqueLinesWhenConcatenatingUnrelatedPrograms() { - var program = $@" + private readonly ITestOutputHelper testOutputHelper; + + // All types of top level declarations. + readonly string originalProgram = @" +module SomeModule { + + module NestedModule { + class C { + var f: int + constructor () + } + } + + method m() { + var x: NestedModule.C; + x := new NestedModule.C(); + x.f := 4; + } +} + +import opened SomeModule + +type FooSynonym = FooClass + +class FooClass { + var f: int + constructor () +} + datatype Friends = Agnes | Agatha | Jermaine -function SomeFunc(funcFormal: int): nat {{ 3 }} +function method SomeFunc(funcFormal: int): nat { 3 } method SomeMethod(methodFormal: int) returns (result: bool) + requires methodFormal == 2 ensures result == true -{{ - result := methodFormal == 3; -}} + // ensures forall x :: x == methodFormal +{ + m(); + var lambdaExpr := x => x + 1; + result := methodFormal == SomeFunc(42); +} "; - var renamedProgram = $@" + readonly string renamedProgram = @" + +module SomeModule2 { + + module NestedModule { + class C { + var f: int + constructor () + } + } + + method m() { + var x: NestedModule.C; + x := new NestedModule.C(); + x.f := 4; + } +} + +type FooSynonym2 = FooClass2 + +class FooClass2 { + var f: int + constructor () +} + datatype Friends2 = Agnes2 | Agatha2 | Jermaine2 -function SomeFunc2(funcFormal: int): nat {{ 3 }} +function method SomeFunc2(funcFormal: int): nat { 3 } method SomeMethod2(methodFormal: int) returns (result: bool) + requires methodFormal == 2 ensures result == true -{{ - result := methodFormal == 3; -}} + // ensures forall x :: x == methodFormal +{ + var lambdaExpr := x => x + 1; + result := methodFormal == SomeFunc2(42); +} "; + public IntraMethodVerificationStability(ITestOutputHelper testOutputHelper) { + this.testOutputHelper = testOutputHelper; + } + + [Fact] + public void NoUniqueLinesWhenConcatenatingUnrelatedPrograms() { DafnyOptions.Install(new DafnyOptions()); - var regularBoogie = GetBoogieText(program); - var renamedBoogie = GetBoogieText(renamedProgram); - var separate = UniqueNonCommentLines(regularBoogie + renamedBoogie); - var combinedBoogie = GetBoogieText(program + renamedProgram); + var regularBoogie = GetBoogie(originalProgram).ToList(); + var renamedBoogie = GetBoogie(renamedProgram).ToList(); + var regularBoogieText = GetBoogieText(regularBoogie); + var renamedBoogieText = GetBoogieText(renamedBoogie); + var separate = UniqueNonCommentLines(regularBoogieText + renamedBoogieText); + var combinedBoogie = GetBoogieText(GetBoogie(originalProgram + renamedProgram)); var together = UniqueNonCommentLines(combinedBoogie); var uniqueLines = separate.Union(together).Except(separate.Intersect(together)).ToList(); Assert.Equal(Enumerable.Empty(), uniqueLines); } + [Fact] + public void EqualProverLogWhenAddingUnrelatedProgram() { + + DafnyOptions.Install(new DafnyOptions()); + CommandLineOptions.Clo.Parse(new[] { "" }); + CommandLineOptions.Clo.ProcsToCheck.Add("*SomeMethod"); + ExecutionEngine.printer = new ConsolePrinter(); // For boogie errors + + var renamedProverLog = GetProverLogForProgram(GetBoogie(renamedProgram + originalProgram)); + var regularProverLog = GetProverLogForProgram(GetBoogie(originalProgram)); + Assert.Equal(regularProverLog, renamedProverLog); + } + + private string GetProverLogForProgram(IEnumerable boogiePrograms) { + var logs = GetProverLogsForProgram(boogiePrograms).ToList(); + Assert.Single(logs); + return logs[0]; + } + + private IEnumerable GetProverLogsForProgram(IEnumerable boogiePrograms) { + string directory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(directory); + var temp1 = directory + "/proverLog"; + testOutputHelper.WriteLine("proverLog: " + temp1); + CommandLineOptions.Clo.ProverLogFilePath = temp1; + foreach (var boogieProgram in boogiePrograms) { + Main.BoogieOnce("", "", boogieProgram, "programId", out _, out var outcome); + testOutputHelper.WriteLine("outcome: " + outcome); + foreach (var proverFile in Directory.GetFiles(directory)) { + yield return File.ReadAllText(proverFile); + } + } + } + ISet UniqueNonCommentLines(string input) { return input.Split('\n').Where(line => !line.TrimStart().StartsWith("//")).ToHashSet(); } @@ -63,8 +161,8 @@ string PrintBoogie(BoogieProgram program) { return result.ToString(); } - string GetBoogieText(string dafnyProgramText) { - return string.Join('\n', GetBoogie(dafnyProgramText).Select(PrintBoogie)); + string GetBoogieText(IEnumerable boogieProgram) { + return string.Join('\n', boogieProgram.Select(PrintBoogie)); } IEnumerable GetBoogie(string dafnyProgramText) { @@ -77,8 +175,8 @@ IEnumerable GetBoogie(string dafnyProgramText) { Assert.Equal(0, parseResult); var dafnyProgram = new Microsoft.Dafny.Program(fullFilePath, module, builtIns, errorReporter); Main.Resolve(dafnyProgram, errorReporter); - var boogiePrograms = Translator.Translate(dafnyProgram, errorReporter).Select(t => t.Item2); - return boogiePrograms; + Assert.Equal(0, errorReporter.ErrorCount); + return Translator.Translate(dafnyProgram, errorReporter).Select(t => t.Item2).ToList(); } } diff --git a/Source/DafnyServer/CounterExampleGeneration/DafnyModel.cs b/Source/DafnyServer/CounterExampleGeneration/DafnyModel.cs index 8052198301a..9b7a464826b 100644 --- a/Source/DafnyServer/CounterExampleGeneration/DafnyModel.cs +++ b/Source/DafnyServer/CounterExampleGeneration/DafnyModel.cs @@ -217,13 +217,13 @@ private string GetBoogieType(Model.Element element) { return null; } var name = GetTrueName(typeElement); - if (Model.GetFunc("SeqTypeInv0").OptEval(typeElement) != null) { + if (Model.TryGetFunc("SeqTypeInv0")?.OptEval(typeElement) != null) { return "SeqType"; } - if (Model.GetFunc("MapType0TypeInv0").OptEval(typeElement) != null) { + if (Model.TryGetFunc("MapType0TypeInv0")?.OptEval(typeElement) != null) { return "SetType"; } - if (Model.GetFunc("MapTypeInv0").OptEval(typeElement) != null) { + if (Model.TryGetFunc("MapTypeInv0")?.OptEval(typeElement) != null) { return "MapType"; } return name; diff --git a/Source/DafnyTestGeneration/Main.cs b/Source/DafnyTestGeneration/Main.cs index f330e1e866c..b79f82d16e1 100644 --- a/Source/DafnyTestGeneration/Main.cs +++ b/Source/DafnyTestGeneration/Main.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; diff --git a/Source/DafnyTestGeneration/ProgramModification.cs b/Source/DafnyTestGeneration/ProgramModification.cs index 32b6390546b..e4a516bf925 100644 --- a/Source/DafnyTestGeneration/ProgramModification.cs +++ b/Source/DafnyTestGeneration/ProgramModification.cs @@ -48,6 +48,8 @@ private static Program DeepCloneProgram(Program program) { private static DafnyOptions SetupOptions(string procedure) { var options = new DafnyOptions(); options.Parse(new[] { "/proc:" + procedure }); + options.NormalizeNames = false; + options.EmitDebugInformation = true; options.EnhancedErrorMessages = 1; options.ModelViewFile = "-"; options.ProverOptions = new List() { diff --git a/Source/Directory.Build.props b/Source/Directory.Build.props index 3c415d2631d..0cb3b183a02 100644 --- a/Source/Directory.Build.props +++ b/Source/Directory.Build.props @@ -7,7 +7,7 @@ - + diff --git a/Test/allocated1/dafny1/SchorrWaite.dfy b/Test/allocated1/dafny1/SchorrWaite.dfy index 42746f117f8..254f9e352a8 100644 --- a/Test/allocated1/dafny1/SchorrWaite.dfy +++ b/Test/allocated1/dafny1/SchorrWaite.dfy @@ -233,6 +233,7 @@ class Main { decreases unmarkedNodes, stackNodes, |t.children| - t.childrenVisited { if t.childrenVisited == |t.children| { + assert {:focus} true; // pop t.childrenVisited := 0; if p == null { @@ -251,6 +252,7 @@ class Main { t.childrenVisited := t.childrenVisited + 1; } else { + assert {:focus} true; // push var newT := t.children[t.childrenVisited]; diff --git a/Test/dafny1/SchorrWaite.dfy b/Test/dafny1/SchorrWaite.dfy index b1bdfb63a8f..27a98b8e5bf 100644 --- a/Test/dafny1/SchorrWaite.dfy +++ b/Test/dafny1/SchorrWaite.dfy @@ -1,10 +1,9 @@ -// RUN: %dafny /compile:0 /dprint:"%t.dprint" /autoTriggers:0 "%s" > "%t" +// RUN: %dafny /compile:0 /dprint:"%t.dprint" "%s" > "%t" // RUN: %diff "%s.expect" "%t" // Rustan Leino // 7 November 2008 // Schorr-Waite and other marking algorithms, written and verified in Dafny. -// Copyright (c) 2008, Microsoft. class Node { var children: seq @@ -178,7 +177,7 @@ class Main { ensures forall n :: n in S && n.marked ==> forall ch :: ch in n.children && ch != null ==> ch.marked // every marked node was reachable from 'root' in the pre-state: - ensures forall n {:autotriggers false} :: n in S && n.marked ==> old(Reachable(root, n, S)) + ensures forall n :: n in S && n.marked ==> old(Reachable(root, n, S)) // the structure of the graph has not changed: ensures forall n :: n in S ==> n.childrenVisited == old(n.childrenVisited) && @@ -212,8 +211,7 @@ class Main { invariant forall n :: n in S ==> n in stackNodes || n.children == old(n.children) invariant forall n :: n in stackNodes ==> |n.children| == old(|n.children|) && - forall j :: 0 <= j < |n.children| ==> - j == n.childrenVisited || n.children[j] == old(n.children[j]) + n.children == old(n.children)[n.childrenVisited := n.children[n.childrenVisited]] // every marked node is reachable: invariant old(allocated(path)) // needed to show 'path' worthy as argument to old(Reachable(...)) invariant old(ReachableVia(root, path, t, S)) @@ -222,10 +220,10 @@ class Main { invariant forall n :: n in S && n.marked ==> old(Reachable(root, n, S)) // the current values of m.children[m.childrenVisited] for m's on the stack: invariant 0 < |stackNodes| ==> stackNodes[0].children[stackNodes[0].childrenVisited] == null - invariant forall k :: 0 < k < |stackNodes| ==> + invariant forall k {:matchinglooprewrite false} :: 0 < k < |stackNodes| ==> stackNodes[k].children[stackNodes[k].childrenVisited] == stackNodes[k-1] // the original values of m.children[m.childrenVisited] for m's on the stack: - invariant forall k :: 0 <= k && k+1 < |stackNodes| ==> + invariant forall k {:matchinglooprewrite false} :: 0 <= k && k+1 < |stackNodes| ==> old(stackNodes[k].children)[stackNodes[k].childrenVisited] == stackNodes[k+1] invariant 0 < |stackNodes| ==> old(stackNodes[|stackNodes|-1].children)[stackNodes[|stackNodes|-1].childrenVisited] == t @@ -265,4 +263,4 @@ class Main { } } } -} +} \ No newline at end of file diff --git a/customBoogie.patch b/customBoogie.patch index bcc0bebd8d3..4987eca8f5f 100644 --- a/customBoogie.patch +++ b/customBoogie.patch @@ -138,7 +138,7 @@ index 30e67936..64a5a976 100644 -- +- +