using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using Bpl = Microsoft.Boogie; using IToken = Microsoft.Boogie.IToken; namespace Microsoft.Dafny { public abstract class IRewriter { protected readonly ErrorReporter reporter; public IRewriter(ErrorReporter reporter) { Contract.Requires(reporter != null); this.reporter = reporter; } internal virtual void PreResolve(ModuleDefinition m) { Contract.Requires(m != null); } internal virtual void PostResolve(ModuleDefinition m) { Contract.Requires(m != null); } // After SCC/Cyclicity/Recursivity analysis: internal virtual void PostCyclicityResolve(ModuleDefinition m) { Contract.Requires(m != null); } } public class AutoGeneratedToken : TokenWrapper { public AutoGeneratedToken(Boogie.IToken wrappedToken) : base(wrappedToken) { Contract.Requires(wrappedToken != null); } } public class TriggerGeneratingRewriter : IRewriter { internal TriggerGeneratingRewriter(ErrorReporter reporter) : base(reporter) { Contract.Requires(reporter != null); } internal override void PostCyclicityResolve(ModuleDefinition m) { var finder = new Triggers.QuantifierCollector(reporter); foreach (var decl in ModuleDefinition.AllCallables(m.TopLevelDecls)) { finder.Visit(decl, false); } var triggersCollector = new Triggers.TriggersCollector(finder.exprsInOldContext); foreach (var quantifierCollection in finder.quantifierCollections) { quantifierCollection.ComputeTriggers(triggersCollector); quantifierCollection.CommitTriggers(); } } } internal class QuantifierSplittingRewriter : IRewriter { internal QuantifierSplittingRewriter(ErrorReporter reporter) : base(reporter) { Contract.Requires(reporter != null); } internal override void PostResolve(ModuleDefinition m) { var splitter = new Triggers.QuantifierSplitter(); foreach (var decl in ModuleDefinition.AllCallables(m.TopLevelDecls)) { splitter.Visit(decl); } splitter.Commit(); } } // write out the quantifier for ForallStmt public class ForallStmtRewriter : IRewriter { public ForallStmtRewriter(ErrorReporter reporter) : base(reporter) { Contract.Requires(reporter != null); } internal override void PostResolve(ModuleDefinition m) { var forallvisiter = new ForAllStmtVisitor(); foreach (var decl in ModuleDefinition.AllCallables(m.TopLevelDecls)) { forallvisiter.Visit(decl, true); } } internal class ForAllStmtVisitor : TopDownVisitor { protected override bool VisitOneStmt(Statement stmt, ref bool st) { if (stmt is ForallStmt) { ForallStmt s = (ForallStmt)stmt; if (s.Kind == ForallStmt.ParBodyKind.Proof) { Expression term = s.Ens.Count != 0 ? s.Ens[0].E : new LiteralExpr(s.Tok, true); for (int i = 1; i < s.Ens.Count; i++) { term = new BinaryExpr(s.Tok, BinaryExpr.ResolvedOpcode.And, term, s.Ens[i].E); } List exprList = new List(); ForallExpr expr = new ForallExpr(s.Tok, s.BoundVars, s.Range, term, s.Attributes); expr.Type = Type.Bool; // resolve here exprList.Add(expr); s.ForallExpressions = exprList; } else if (s.Kind == ForallStmt.ParBodyKind.Assign) { if (s.BoundVars.Count != 0) { var s0 = (AssignStmt)s.S0; if (s0.Rhs is ExprRhs) { List exprList = new List(); Expression Fi = null; Func lhsBuilder = null; var lhs = s0.Lhs.Resolved; var i = s.BoundVars[0]; if (s.BoundVars.Count == 1) { //var lhsContext = null; // Detect the following cases: // 0: forall i | R(i) { F(i).f := E(i); } // 1: forall i | R(i) { A[F(i)] := E(i); } // 2: forall i | R(i) { F(i)[N] := E(i); } if (lhs is MemberSelectExpr) { var ll = (MemberSelectExpr)lhs; Fi = ll.Obj; lhsBuilder = e => { var l = new MemberSelectExpr(ll.tok, e, ll.MemberName); l.Member = ll.Member; l.Type = ll.Type; return l; }; } else if (lhs is SeqSelectExpr) { var ll = (SeqSelectExpr)lhs; Contract.Assert(ll.SelectOne); if (!Translator.ContainsFreeVariable(ll.Seq, false, i)) { Fi = ll.E0; lhsBuilder = e => { var l = new SeqSelectExpr(ll.tok, true, ll.Seq, e, null); l.Type = ll.Type; return l; }; } else if (!Translator.ContainsFreeVariable(ll.E0, false, i)) { Fi = ll.Seq; lhsBuilder = e => { var l = new SeqSelectExpr(ll.tok, true, e, ll.E0, null); l.Type = ll.Type; return l; }; } } } var rhs = ((ExprRhs)s0.Rhs).Expr; bool usedInversion = false; if (Fi != null) { var j = new BoundVar(i.tok, i.Name + "#inv", Fi.Type); var jj = Expression.CreateIdentExpr(j); var jList = new List() { j }; var vals = InvertExpression(i, j, s.Range, Fi); #if DEBUG_PRINT Console.WriteLine("DEBUG: Trying to invert:"); Console.WriteLine("DEBUG: " + Printer.ExprToString(s.Range) + " && " + j.Name + " == " + Printer.ExprToString(Fi)); if (vals == null) { Console.WriteLine("DEBUG: Can't"); } else { Console.WriteLine("DEBUG: The inverse is the disjunction of the following:"); foreach (var val in vals) { Console.WriteLine("DEBUG: " + Printer.ExprToString(val.Range) + " && " + Printer.ExprToString(val.FInverse) + " == " + i.Name); } } #endif if (vals != null) { foreach (var val in vals) { lhs = lhsBuilder(jj); Attributes attributes = new Attributes("trigger", new List() { lhs }, s.Attributes); var expr = new ForallExpr(s.Tok, jList, val.Range, new BinaryExpr(s.Tok, BinaryExpr.ResolvedOpcode.EqCommon, lhs, Substitute(rhs, i, val.FInverse)), attributes); expr.Type = Type.Bool; //resolve here exprList.Add(expr); } usedInversion = true; } } if (!usedInversion) { var expr = new ForallExpr(s.Tok, s.BoundVars, s.Range, new BinaryExpr(s.Tok, BinaryExpr.ResolvedOpcode.EqCommon, lhs, rhs), s.Attributes); expr.Type = Type.Bool; // resolve here exprList.Add(expr); } s.ForallExpressions = exprList; } } } else if (s.Kind == ForallStmt.ParBodyKind.Call) { var s0 = (CallStmt)s.S0; Expression term = s0.Method.Ens.Count != 0 ? s0.Method.Ens[0].E : new LiteralExpr(s.Tok, true); for (int i = 1; i < s0.Method.Ens.Count; i++) { term = new BinaryExpr(s.Tok, BinaryExpr.ResolvedOpcode.And, term, s0.Method.Ens[i].E); } List exprList = new List(); ForallExpr expr = new ForallExpr(s.Tok, s.BoundVars, s.Range, term, s.Attributes); expr.Type = Type.Bool; // resolve here exprList.Add(expr); } else { Contract.Assert(false); // unexpected kind } } return true; //visit the sub-parts with the same "st" } internal class ForallStmtTranslationValues { public readonly Expression Range; public readonly Expression FInverse; public ForallStmtTranslationValues(Expression range, Expression fInverse) { Contract.Requires(range != null); Contract.Requires(fInverse != null); Range = range; FInverse = fInverse; } public ForallStmtTranslationValues Subst(IVariable j, Expression e) { Contract.Requires(j != null); Contract.Requires(e != null); Dictionary typeMap = new Dictionary(); var substMap = new Dictionary(); substMap.Add(j, e); Translator.Substituter sub = new Translator.Substituter(null, substMap, typeMap, null); var v = new ForallStmtTranslationValues(sub.Substitute(Range), sub.Substitute(FInverse)); return v; } } /// /// Find piecewise inverse of F under R. More precisely, find lists of expressions P and F-1 /// such that /// R(i) && j == F(i) /// holds iff the disjunction of the following predicates holds: /// P_0(j) && F-1_0(j) == i /// ... /// P_{n-1}(j) && F-1_{n-1}(j) == i /// If no such disjunction is found, return null. /// If such a disjunction is found, return for each disjunct: /// * The predicate P_k(j), which is an expression that may have free occurrences of j (but no free occurrences of i) /// * The expression F-1_k(j), which also may have free occurrences of j but not of i /// private List InvertExpression(BoundVar i, BoundVar j, Expression R, Expression F) { Contract.Requires(i != null); Contract.Requires(j != null); Contract.Requires(R != null); Contract.Requires(F != null); var vals = new List(InvertExpressionIter(i, j, R, F)); if (vals.Count == 0) { return null; } else { return vals; } } /// /// See InvertExpression. /// private IEnumerable InvertExpressionIter(BoundVar i, BoundVar j, Expression R, Expression F) { Contract.Requires(i != null); Contract.Requires(j != null); Contract.Requires(R != null); Contract.Requires(F != null); F = F.Resolved; if (!Translator.ContainsFreeVariable(F, false, i)) { // We're looking at R(i) && j == K. // We cannot invert j == K, but if we're lucky, R(i) contains a conjunct i==G. Expression r = Expression.CreateBoolLiteral(R.tok, true); Expression G = null; foreach (var c in Expression.Conjuncts(R)) { if (G == null && c is BinaryExpr) { var bin = (BinaryExpr)c; if (BinaryExpr.IsEqualityOp(bin.ResolvedOp)) { var id = bin.E0.Resolved as IdentifierExpr; if (id != null && id.Var == i) { G = bin.E1; continue; } id = bin.E1.Resolved as IdentifierExpr; if (id != null && id.Var == i) { G = bin.E0; continue; } } } r = Expression.CreateAnd(r, c); } if (G != null) { var jIsK = Expression.CreateEq(Expression.CreateIdentExpr(j), F, j.Type); var rr = Substitute(r, i, G); yield return new ForallStmtTranslationValues(Expression.CreateAnd(rr, jIsK), G); } } else if (F is IdentifierExpr) { var e = (IdentifierExpr)F; if (e.Var == i) { // We're looking at R(i) && j == i, which is particularly easy to invert: R(j) && j == i var jj = Expression.CreateIdentExpr(j); yield return new ForallStmtTranslationValues(Substitute(R, i, jj), jj); } } else if (F is BinaryExpr) { var bin = (BinaryExpr)F; if (bin.ResolvedOp == BinaryExpr.ResolvedOpcode.Add && (bin.E0.Type.IsIntegerType || bin.E0.Type.IsRealType)) { if (!Translator.ContainsFreeVariable(bin.E1, false, i)) { // We're looking at: R(i) && j == f(i) + K. // By a recursive call, we'll ask to invert: R(i) && j' == f(i). // For each P_0(j') && f-1_0(j') == i we get back, we yield: // P_0(j - K) && f-1_0(j - K) == i var jMinusK = Expression.CreateSubtract(Expression.CreateIdentExpr(j), bin.E1); foreach (var val in InvertExpression(i, j, R, bin.E0)) { yield return val.Subst(j, jMinusK); } } else if (!Translator.ContainsFreeVariable(bin.E0, false, i)) { // We're looking at: R(i) && j == K + f(i) // Do as in previous case, but with operands reversed. var jMinusK = Expression.CreateSubtract(Expression.CreateIdentExpr(j), bin.E0); foreach (var val in InvertExpression(i, j, R, bin.E1)) { yield return val.Subst(j, jMinusK); } } } else if (bin.ResolvedOp == BinaryExpr.ResolvedOpcode.Sub && (bin.E0.Type.IsIntegerType || bin.E0.Type.IsRealType)) { if (!Translator.ContainsFreeVariable(bin.E1, false, i)) { // We're looking at: R(i) && j == f(i) - K // Recurse on f(i) and then replace j := j + K var jPlusK = Expression.CreateAdd(Expression.CreateIdentExpr(j), bin.E1); foreach (var val in InvertExpression(i, j, R, bin.E0)) { yield return val.Subst(j, jPlusK); } } else if (!Translator.ContainsFreeVariable(bin.E0, false, i)) { // We're looking at: R(i) && j == K - f(i) // Recurse on f(i) and then replace j := K - j var kMinusJ = Expression.CreateAdd(Expression.CreateIdentExpr(j), bin.E0); foreach (var val in InvertExpression(i, j, R, bin.E1)) { yield return val.Subst(j, kMinusJ); } } } } else if (F is ITEExpr) { var ife = (ITEExpr)F; // We're looking at R(i) && j == if A(i) then B(i) else C(i), which is equivalent to the disjunction of: // R(i) && A(i) && j == B(i) // R(i) && !A(i) && j == C(i) // We recurse on each one, yielding the results var r = Expression.CreateAnd(R, ife.Test); var valsThen = InvertExpression(i, j, r, ife.Thn); if (valsThen != null) { r = Expression.CreateAnd(R, Expression.CreateNot(ife.tok, ife.Test)); var valsElse = InvertExpression(i, j, r, ife.Els); if (valsElse != null) { foreach (var val in valsThen) { yield return val; } foreach (var val in valsElse) { yield return val; } } } } } Expression Substitute(Expression expr, IVariable v, Expression e) { Dictionary substMap = new Dictionary(); Dictionary typeMap = new Dictionary(); substMap.Add(v, e); Translator.Substituter sub = new Translator.Substituter(null, substMap, typeMap, null); return sub.Substitute(expr); } } } /// /// AutoContracts is an experimental feature that will fill much of the dynamic-frames boilerplate /// into a class. From the user's perspective, what needs to be done is simply: /// - mark the class with {:autocontracts} /// - declare a function (or predicate) called Valid() /// /// AutoContracts will then: /// /// Declare: /// ghost var Repr: set(object) /// /// For function/predicate Valid(), insert: /// reads this, Repr /// Into body of Valid(), insert (at the beginning of the body): /// this in Repr && null !in Repr /// and also insert, for every array-valued field A declared in the class: /// (A != null ==> A in Repr) && /// and for every field F of a class type T where T has a field called Repr, also insert: /// (F != null ==> F in Repr && F.Repr SUBSET Repr && this !in Repr) /// Except, if A or F is declared with {:autocontracts false}, then the implication will not /// be added. /// /// For every constructor, add: /// modifies this; /// ensures Valid() && fresh(Repr - {this}) /// At the end of the body of the constructor, add: /// Repr := {this}; /// if (A != null) { Repr := Repr + {A}; } /// if (F != null) { Repr := Repr + {F} + F.Repr; } /// /// For every non-static non-ghost method that is not a "simple query method", /// add: /// requires Valid() /// modifies Repr /// ensures Valid() && fresh(Repr - old(Repr)) /// At the end of the body of the method, add: /// if (A != null) { Repr := Repr + {A}; } /// if (F != null) { Repr := Repr + {F} + F.Repr; } /// For every non-static method that is either ghost or is a "simple query method", /// add: /// requires Valid() /// public class AutoContractsRewriter : IRewriter { public AutoContractsRewriter(ErrorReporter reporter) : base(reporter) { Contract.Requires(reporter != null); } internal override void PreResolve(ModuleDefinition m) { foreach (var d in m.TopLevelDecls) { bool sayYes = true; if (d is ClassDecl && Attributes.ContainsBool(d.Attributes, "autocontracts", ref sayYes) && sayYes) { ProcessClassPreResolve((ClassDecl)d); } } } void ProcessClassPreResolve(ClassDecl cl) { // Add: ghost var Repr: set; // ...unless a field with that name is already present if (!cl.Members.Exists(member => member is Field && member.Name == "Repr")) { Type ty = new SetType(true, new ObjectType()); cl.Members.Add(new Field(new AutoGeneratedToken(cl.tok), "Repr", true, ty, null)); } foreach (var member in cl.Members) { bool sayYes = true; if (Attributes.ContainsBool(member.Attributes, "autocontracts", ref sayYes) && !sayYes) { // the user has excluded this member continue; } if (member.RefinementBase != null) { // member is inherited from a module where it was already processed continue; } Boogie.IToken tok = new AutoGeneratedToken(member.tok); if (member is Function && member.Name == "Valid" && !member.IsStatic) { var valid = (Function)member; // reads this; valid.Reads.Add(new FrameExpression(tok, new ThisExpr(tok), null)); // reads Repr; valid.Reads.Add(new FrameExpression(tok, new MemberSelectExpr(tok, new ImplicitThisExpr(tok), "Repr"), null)); } else if (member is Constructor) { var ctor = (Constructor)member; // modifies this; ctor.Mod.Expressions.Add(new FrameExpression(tok, new ImplicitThisExpr(tok), null)); // ensures Valid(); ctor.Ens.Insert(0, new MaybeFreeExpression(new FunctionCallExpr(tok, "Valid", new ImplicitThisExpr(tok), tok, new List()))); // ensures fresh(Repr - {this}); var freshness = new UnaryOpExpr(tok, UnaryOpExpr.Opcode.Fresh, new BinaryExpr(tok, BinaryExpr.Opcode.Sub, new MemberSelectExpr(tok, new ImplicitThisExpr(tok), "Repr"), new SetDisplayExpr(tok, true, new List() { new ThisExpr(tok) }))); ctor.Ens.Insert(1, new MaybeFreeExpression(freshness)); } else if (member is Method && !member.IsStatic) { var m = (Method)member; // requires Valid(); m.Req.Insert(0, new MaybeFreeExpression(new FunctionCallExpr(tok, "Valid", new ImplicitThisExpr(tok), tok, new List()))); // If this is a mutating method, we should also add a modifies clause and a postcondition, but we don't do that if it's // a simple query method. However, we won't know if it's a simple query method until after resolution, so we'll add the // rest of the spec then. } } } internal override void PostResolve(ModuleDefinition m) { foreach (var d in m.TopLevelDecls) { bool sayYes = true; if (d is ClassDecl && Attributes.ContainsBool(d.Attributes, "autocontracts", ref sayYes) && sayYes) { ProcessClassPostResolve((ClassDecl)d); } } } void ProcessClassPostResolve(ClassDecl cl) { // Find all fields of a reference type, and make a note of whether or not the reference type has a Repr field. // Also, find the Repr field and the function Valid in class "cl" Field ReprField = null; Function Valid = null; var subobjects = new List>(); foreach (var member in cl.Members) { var field = member as Field; if (field != null) { bool sayYes = true; if (field.Name == "Repr") { ReprField = field; } else if (Attributes.ContainsBool(field.Attributes, "autocontracts", ref sayYes) && !sayYes) { // ignore this field } else if (field.Type is ObjectType) { subobjects.Add(new Tuple(field, null)); } else if (field.Type.IsRefType) { var rcl = (ClassDecl)((UserDefinedType)field.Type).ResolvedClass; Field rRepr = null; foreach (var memb in rcl.Members) { var f = memb as Field; if (f != null && f.Name == "Repr") { rRepr = f; break; } } subobjects.Add(new Tuple(field, rRepr)); } } else if (member is Function && member.Name == "Valid" && !member.IsStatic) { var fn = (Function)member; if (fn.Formals.Count == 0 && fn.ResultType.IsBoolType) { Valid = fn; } } } Contract.Assert(ReprField != null); // we expect there to be a "Repr" field, since we added one in PreResolve Boogie.IToken clTok = new AutoGeneratedToken(cl.tok); Type ty = UserDefinedType.FromTopLevelDecl(clTok, cl); var self = new ThisExpr(clTok); self.Type = ty; var implicitSelf = new ImplicitThisExpr(clTok); implicitSelf.Type = ty; var Repr = new MemberSelectExpr(clTok, implicitSelf, "Repr"); Repr.Member = ReprField; Repr.Type = ReprField.Type; var cNull = new LiteralExpr(clTok); cNull.Type = new ObjectType(); foreach (var member in cl.Members) { bool sayYes = true; if (Attributes.ContainsBool(member.Attributes, "autocontracts", ref sayYes) && !sayYes) { continue; } Boogie.IToken tok = new AutoGeneratedToken(member.tok); if (member is Function && member.Name == "Valid" && !member.IsStatic) { var valid = (Function)member; if (valid.IsGhost && valid.ResultType.IsBoolType) { Expression c; if (valid.RefinementBase == null) { var c0 = BinBoolExpr(tok, BinaryExpr.ResolvedOpcode.InSet, self, Repr); // this in Repr var c1 = BinBoolExpr(tok, BinaryExpr.ResolvedOpcode.NotInSet, cNull, Repr); // null !in Repr c = BinBoolExpr(tok, BinaryExpr.ResolvedOpcode.And, c0, c1); } else { c = new LiteralExpr(tok, true); c.Type = Type.Bool; } foreach (var ff in subobjects) { if (ff.Item1.RefinementBase != null) { // the field has been inherited from a refined module, so don't include it here continue; } var F = Resolver.NewMemberSelectExpr(tok, implicitSelf, ff.Item1, null); var c0 = BinBoolExpr(tok, BinaryExpr.ResolvedOpcode.NeqCommon, F, cNull); var c1 = BinBoolExpr(tok, BinaryExpr.ResolvedOpcode.InSet, F, Repr); if (ff.Item2 == null) { // F != null ==> F in Repr (so, nothing else to do) } else { // F != null ==> F in Repr && F.Repr <= Repr && this !in F.Repr var FRepr = new MemberSelectExpr(tok, F, ff.Item2.Name); FRepr.Member = ff.Item2; FRepr.Type = ff.Item2.Type; var c2 = BinBoolExpr(tok, BinaryExpr.ResolvedOpcode.Subset, FRepr, Repr); var c3 = BinBoolExpr(tok, BinaryExpr.ResolvedOpcode.NotInSet, self, FRepr); c1 = BinBoolExpr(tok, BinaryExpr.ResolvedOpcode.And, c1, BinBoolExpr(tok, BinaryExpr.ResolvedOpcode.And, c2, c3)); } c = BinBoolExpr(tok, BinaryExpr.ResolvedOpcode.And, c, BinBoolExpr(tok, BinaryExpr.ResolvedOpcode.Imp, c0, c1)); } if (valid.Body == null) { valid.Body = c; } else { valid.Body = BinBoolExpr(tok, BinaryExpr.ResolvedOpcode.And, c, valid.Body); } } } else if (member is Constructor) { var ctor = (Constructor)member; if (ctor.Body != null) { var bodyStatements = ((BlockStmt)ctor.Body).Body; // Repr := {this}; var e = new SetDisplayExpr(tok, true, new List() { self }); e.Type = new SetType(true, new ObjectType()); Statement s = new AssignStmt(tok, tok, Repr, new ExprRhs(e)); s.IsGhost = true; bodyStatements.Add(s); AddSubobjectReprs(tok, subobjects, bodyStatements, self, implicitSelf, cNull, Repr); } } else if (member is Method && !member.IsStatic && !member.IsGhost) { var m = (Method)member; if (Valid != null && !IsSimpleQueryMethod(m)) { if (member.RefinementBase == null) { // modifies Repr; m.Mod.Expressions.Add(new FrameExpression(Repr.tok, Repr, null)); // ensures Valid(); var valid = new FunctionCallExpr(tok, "Valid", implicitSelf, tok, new List()); valid.Function = Valid; valid.Type = Type.Bool; // Add the identity substitution to this call valid.TypeArgumentSubstitutions = new Dictionary(); foreach (var p in cl.TypeArgs) { valid.TypeArgumentSubstitutions.Add(p, new UserDefinedType(p)); } m.Ens.Insert(0, new MaybeFreeExpression(valid)); // ensures fresh(Repr - old(Repr)); var e0 = new OldExpr(tok, Repr); e0.Type = Repr.Type; var e1 = new BinaryExpr(tok, BinaryExpr.Opcode.Sub, Repr, e0); e1.ResolvedOp = BinaryExpr.ResolvedOpcode.SetDifference; e1.Type = Repr.Type; var freshness = new UnaryOpExpr(tok, UnaryOpExpr.Opcode.Fresh, e1); freshness.Type = Type.Bool; m.Ens.Insert(1, new MaybeFreeExpression(freshness)); } if (m.Body != null) { var bodyStatements = ((BlockStmt)m.Body).Body; AddSubobjectReprs(tok, subobjects, bodyStatements, self, implicitSelf, cNull, Repr); } } } } } void AddSubobjectReprs(Boogie.IToken tok, List> subobjects, List bodyStatements, Expression self, Expression implicitSelf, Expression cNull, Expression Repr) { // TODO: these assignments should be included on every return path foreach (var ff in subobjects) { var F = Resolver.NewMemberSelectExpr(tok, implicitSelf, ff.Item1, null); // create a resolved MemberSelectExpr Expression e = new SetDisplayExpr(tok, true, new List() { F }); e.Type = new SetType(true, new ObjectType()); // resolve here var rhs = new BinaryExpr(tok, BinaryExpr.Opcode.Add, Repr, e); rhs.ResolvedOp = BinaryExpr.ResolvedOpcode.Union; // resolve here rhs.Type = Repr.Type; // resolve here if (ff.Item2 == null) { // Repr := Repr + {F} (so, nothing else to do) } else { // Repr := Repr + {F} + F.Repr var FRepr = Resolver.NewMemberSelectExpr(tok, F, ff.Item2, null); // create resolved MemberSelectExpr rhs = new BinaryExpr(tok, BinaryExpr.Opcode.Add, rhs, FRepr); rhs.ResolvedOp = BinaryExpr.ResolvedOpcode.Union; // resolve here rhs.Type = Repr.Type; // resolve here } // Repr := Repr + ...; Statement s = new AssignStmt(tok, tok, Repr, new ExprRhs(rhs)); s.IsGhost = true; // wrap if statement around s e = BinBoolExpr(tok, BinaryExpr.ResolvedOpcode.NeqCommon, F, cNull); var thn = new BlockStmt(tok, tok, new List() { s }); thn.IsGhost = true; s = new IfStmt(tok, tok, false, e, thn, null); s.IsGhost = true; // finally, add s to the body bodyStatements.Add(s); } } bool IsSimpleQueryMethod(Method m) { // A simple query method has out parameters, its body has no effect other than to assign to them, // and the postcondition does not explicitly mention the pre-state. return m.Outs.Count != 0 && m.Body != null && LocalAssignsOnly(m.Body) && m.Ens.TrueForAll(mfe => !MentionsOldState(mfe.E)); } bool LocalAssignsOnly(Statement s) { Contract.Requires(s != null); if (s is AssignStmt) { var ss = (AssignStmt)s; return ss.Lhs.Resolved is IdentifierExpr; } else if (s is ConcreteUpdateStatement) { var ss = (ConcreteUpdateStatement)s; return ss.Lhss.TrueForAll(e => e.Resolved is IdentifierExpr); } else if (s is CallStmt) { return false; } else { foreach (var ss in s.SubStatements) { if (!LocalAssignsOnly(ss)) { return false; } } } return true; } /// /// Returns true iff 'expr' is a two-state expression, that is, if it mentions "old(...)" or "fresh(...)". /// static bool MentionsOldState(Expression expr) { Contract.Requires(expr != null); if (expr is OldExpr) { return true; } else if (expr is UnaryOpExpr && ((UnaryOpExpr)expr).Op == UnaryOpExpr.Opcode.Fresh) { return true; } foreach (var ee in expr.SubExpressions) { if (MentionsOldState(ee)) { return true; } } return false; } public static BinaryExpr BinBoolExpr(Boogie.IToken tok, BinaryExpr.ResolvedOpcode rop, Expression e0, Expression e1) { var p = new BinaryExpr(tok, BinaryExpr.ResolvedOp2SyntacticOp(rop), e0, e1); p.ResolvedOp = rop; // resolve here p.Type = Type.Bool; // resolve here return p; } } /// /// For any function foo() with the :opaque attribute, /// hide the body, so that it can only be seen within its /// recursive clique (if any), or if the programmer /// specifically asks to see it via the reveal_foo() lemma /// public class OpaqueFunctionRewriter : IRewriter { protected Dictionary fullVersion; // Given an opaque function, retrieve the full protected Dictionary original; // Given a full version of an opaque function, find the original opaque version protected Dictionary revealOriginal; // Map reveal_* lemmas back to their original functions public OpaqueFunctionRewriter(ErrorReporter reporter) : base(reporter) { Contract.Requires(reporter != null); fullVersion = new Dictionary(); original = new Dictionary(); revealOriginal = new Dictionary(); } internal override void PreResolve(ModuleDefinition m) { foreach (var d in m.TopLevelDecls) { if (d is ClassDecl) { DuplicateOpaqueClassFunctions((ClassDecl)d); } } } internal override void PostResolve(ModuleDefinition m) { // Fix up the ensures clause of the full version of the function, // since it may refer to the original opaque function foreach (var fn in ModuleDefinition.AllFunctions(m.TopLevelDecls)) { if (isFullVersion(fn)) { // Is this a function we created to supplement an opaque function? OpaqueFunctionVisitor visitor = new OpaqueFunctionVisitor(); var context = new OpaqueFunctionContext(original[fn], fn); foreach (Expression ens in fn.Ens) { visitor.Visit(ens, context); } } } foreach (var decl in ModuleDefinition.AllCallables(m.TopLevelDecls)) { if (decl is Lemma) { var lem = (Lemma)decl; if (revealOriginal.ContainsKey(lem)) { fixupRevealLemma(lem, revealOriginal[lem]); fixupTypeArguments(lem, revealOriginal[lem]); } } } } internal override void PostCyclicityResolve(ModuleDefinition m) { // Add layer quantifier if the function is recursive foreach (var decl in ModuleDefinition.AllCallables(m.TopLevelDecls)) { if (decl is Lemma) { var lem = (Lemma)decl; if (revealOriginal.ContainsKey(lem)) { needsLayerQuantifier(lem, revealOriginal[lem]); } } } } // Is f the full version of an opaque function? public bool isFullVersion(Function f) { return original.ContainsKey(f); } // In case we change how opacity is denoted public bool isOpaque(Function f) { return fullVersion.ContainsKey(f); } public Function OpaqueVersion(Function f) { Function ret; original.TryGetValue(f, out ret); return ret; } public Function FullVersion(Function f) { Function ret; fullVersion.TryGetValue(f, out ret); return ret; } // Trims the body from the original function and then adds an internal, // full version, along with a lemma connecting the two protected void DuplicateOpaqueClassFunctions(ClassDecl c) { List newDecls = new List(); foreach (MemberDecl member in c.Members) { if (member is Function) { var f = (Function)member; if (!Attributes.Contains(f.Attributes, "opaque")) { // Nothing to do } else if (f.IsProtected) { reporter.Error(MessageSource.Rewriter, f.tok, ":opaque is not allowed to be applied to protected functions (this will be allowed when the language introduces 'opaque'/'reveal' as keywords)"); } else if (!RefinementToken.IsInherited(f.tok, c.Module)) { // Create a copy, which will be the internal version with a full body // which will allow us to verify that the ensures are true var cloner = new Cloner(); var fWithBody = cloner.CloneFunction(f, "#" + f.Name + "_FULL"); newDecls.Add(fWithBody); fullVersion.Add(f, fWithBody); original.Add(fWithBody, f); var newToken = new Boogie.Token(f.tok.line, f.tok.col); newToken.filename = f.tok.filename; newToken._val = fWithBody.Name; newToken._kind = f.tok.kind; newToken._pos = f.tok.pos; fWithBody.tok = (f.tok is IncludeToken) ? new IncludeToken(newToken) : (Boogie.IToken)newToken; // Annotate the new function so we remember that we introduced it fWithBody.Attributes = new Attributes("opaque_full", new List(), fWithBody.Attributes); fWithBody.Attributes = new Attributes("auto_generated", new List(), fWithBody.Attributes); // Create a lemma to allow the user to selectively reveal the function's body // That is, given: // function {:opaque} foo(x:int, y:int) : int // requires 0 <= x < 5; // requires 0 <= y < 5; // ensures foo(x, y) < 10; // { x + y } // We produce: // lemma {:axiom} {:auto_generated} reveal_foo() // ensures forall x:int, y:int {:trigger foo(x,y)} :: 0 <= x < 5 && 0 <= y < 5 ==> foo(x,y) == foo_FULL(x,y); Expression reqExpr = new LiteralExpr(f.tok, true); foreach (Expression req in f.Req) { Expression newReq = cloner.CloneExpr(req); reqExpr = new BinaryExpr(f.tok, BinaryExpr.Opcode.And, reqExpr, newReq); } List typeVars = new List(); foreach (TypeParameter tp in f.TypeArgs) { typeVars.Add(cloner.CloneTypeParam(tp)); } var boundVars = f.Formals.ConvertAll(formal => new BoundVar(formal.tok, formal.Name, cloner.CloneType(formal.Type))); // Build the implication connecting the function's requires to the connection with the revealed-body version Func func_builder = func => new ApplySuffix(func.tok, new NameSegment(func.tok, func.Name, null), func.Formals.ConvertAll(x => (Expression)new IdentifierExpr(func.tok, x.Name))); var oldEqualsNew = new BinaryExpr(f.tok, BinaryExpr.Opcode.Eq, func_builder(f), func_builder(fWithBody)); var requiresImpliesOldEqualsNew = new BinaryExpr(f.tok, BinaryExpr.Opcode.Imp, reqExpr, oldEqualsNew); MaybeFreeExpression newEnsures; if (f.Formals.Count > 0) { // Build an explicit trigger for the forall, so Z3 doesn't get confused Expression trigger = func_builder(f); List args = new List(); args.Add(trigger); Attributes attrs = new Attributes("trigger", args, null); // Also specify that this is a type quantifier attrs = new Attributes("typeQuantifier", new List(), attrs); newEnsures = new MaybeFreeExpression(new ForallExpr(f.tok, typeVars, boundVars, null, requiresImpliesOldEqualsNew, attrs)); } else { // No need for a forall newEnsures = new MaybeFreeExpression(oldEqualsNew); } var newEnsuresList = new List(); newEnsuresList.Add(newEnsures); // Add an axiom attribute so that the compiler won't complain about the lemma's lack of a body Attributes lemma_attrs = new Attributes("axiom", new List(), null); lemma_attrs = new Attributes("auto_generated", new List(), lemma_attrs); var reveal = new Lemma(f.tok, "reveal_" + f.Name, f.HasStaticKeyword, new List(), new List(), new List(), new List(), new Specification(new List(), null), newEnsuresList, new Specification(new List(), null), null, lemma_attrs, null); newDecls.Add(reveal); revealOriginal[reveal] = f; // Update f's body to simply call the full version, so we preserve recursion checks, decreases clauses, etc. f.Body = func_builder(fWithBody); } } } c.Members.AddRange(newDecls); } protected class OpaqueFunctionContext { public Function original; // The original declaration of the opaque function public Function full; // The version we added that has a body public OpaqueFunctionContext(Function Orig, Function Full) { original = Orig; full = Full; } } class OpaqueFunctionVisitor : TopDownVisitor { protected override bool VisitOneExpr(Expression expr, ref OpaqueFunctionContext context) { if (expr is FunctionCallExpr) { var e = (FunctionCallExpr)expr; if (e.Function == context.original) { // Attempting to call the original opaque function // Redirect the call to the full version and its type-argument substitution map // First, do some sanity checks: Contract.Assert(e.TypeArgumentSubstitutions.Count == context.original.EnclosingClass.TypeArgs.Count + context.original.TypeArgs.Count); Contract.Assert(context.original.EnclosingClass == context.full.EnclosingClass); Contract.Assert(context.original.TypeArgs.Count == context.full.TypeArgs.Count); if (context.full.TypeArgs.Count != 0) { var newTypeArgsSubst = new Dictionary(); context.original.EnclosingClass.TypeArgs.ForEach(tp => newTypeArgsSubst.Add(tp, e.TypeArgumentSubstitutions[tp])); for (int i = 0; i < context.original.TypeArgs.Count; i++) { var tpOrig = context.original.TypeArgs[i]; var tpFull = context.full.TypeArgs[i]; newTypeArgsSubst.Add(tpFull, e.TypeArgumentSubstitutions[tpOrig]); } e.TypeArgumentSubstitutions = newTypeArgsSubst; } e.Function = context.full; } } return true; } } // If the function is recursive, make the reveal lemma quantifier a layerQuantifier protected void needsLayerQuantifier(Lemma lem, Function fn) { var origForall = lem.Ens[0].E as ForallExpr; if (origForall != null && fn.IsRecursive) { var newAttrib = new Attributes("layerQuantifier", new List(), origForall.Attributes); var newForall = new ForallExpr(origForall.tok, origForall.TypeArgs, origForall.BoundVars, origForall.Range, origForall.Term, newAttrib); newForall.Type = Type.Bool; lem.Ens[0] = new MaybeFreeExpression(newForall); } } // This is for the reveal_F function. The ensures clause looks like this: // // ensures forall x : T :: F(x) == F_FULL(x) // // But the type argument substitutions for F and F_FULL are way off, so // we use this function to make a substitution to the type variables the // forall quantifier binds. protected void fixupTypeArguments(Lemma lem, Function fn) { var origForall = lem.Ens[0].E as ForallExpr; if (origForall != null) { Contract.Assert(origForall.TypeArgs.Count == fn.TypeArgs.Count); fixupTypeArguments(lem.Ens[0].E, fn, origForall.TypeArgs); } } // Type argument substitution for the fn_FULL function using the orginal // fn function. protected void fixupTypeArguments(Expression expr, Function fn, List qparams) { FunctionCallExpr e; if (((e = expr as FunctionCallExpr) != null) && (e.Function == fullVersion[fn])) { var newTypeArgsSubst = new Dictionary(); fn.EnclosingClass.TypeArgs.ForEach(tp => newTypeArgsSubst.Add(tp, e.TypeArgumentSubstitutions[tp])); for (int i = 0; i < e.Function.TypeArgs.Count; i++) { newTypeArgsSubst.Add(e.Function.TypeArgs[i], new UserDefinedType(qparams[i])); } e.TypeArgumentSubstitutions = newTypeArgsSubst; } foreach (var ee in expr.SubExpressions) { fixupTypeArguments(ee, fn, qparams); } } protected void fixupRevealLemma(Lemma lem, Function fn) { if (fn.Req.Count == 0) { return; } // DR: Note: I don't know of any example that actually gets to these lines below: // Consolidate the requirements Expression reqs = Expression.CreateBoolLiteral(fn.tok, true); foreach (var expr in fn.Req) { reqs = Expression.CreateAnd(reqs, expr); } if (fn.Formals.Count == 0) { lem.Ens[0] = new MaybeFreeExpression(Expression.CreateImplies(reqs, lem.Ens[0].E)); return; } var origForall = (ForallExpr)lem.Ens[0].E; var origImpl = (BinaryExpr)origForall.Term; // Substitute the forall's variables for those of the fn var formals = fn.Formals.ConvertAll(x => (NonglobalVariable)x); var typeMap = Util.Dict(fn.TypeArgs, Util.Map(origForall.TypeArgs, x => new UserDefinedType(x))); reqs = Expression.VarSubstituter(formals, origForall.BoundVars, reqs, typeMap); var newImpl = Expression.CreateImplies(reqs, origImpl.E1); //var newForall = Expression.CreateQuantifier(origForall, true, newImpl); var newForall = new ForallExpr(origForall.tok, origForall.TypeArgs, origForall.BoundVars, origForall.Range, newImpl, origForall.Attributes); newForall.Type = Type.Bool; lem.Ens[0] = new MaybeFreeExpression(newForall); } } /// /// Automatically accumulate requires for function calls within a function body, /// if requested via {:autoreq} /// public class AutoReqFunctionRewriter : IRewriter { Function parentFunction; OpaqueFunctionRewriter opaqueInfo; bool containsMatch; // TODO: Track this per-requirement, rather than per-function public AutoReqFunctionRewriter(ErrorReporter reporter, OpaqueFunctionRewriter o) : base(reporter) { Contract.Requires(reporter != null); this.opaqueInfo = o; } internal override void PostResolve(ModuleDefinition m) { var components = m.CallGraph.TopologicallySortedComponents(); foreach (var scComponent in components) { // Visit the call graph bottom up, so anything we call already has its prequisites calculated if (scComponent is Function) { Function fn = (Function)scComponent; if (Attributes.ContainsBoolAtAnyLevel(fn, "autoReq")) { parentFunction = fn; // Remember where the recursion started containsMatch = false; // Assume no match statements are involved if (!opaqueInfo.isOpaque(fn)) { // It's a normal function List auto_reqs = new List(); // First handle all of the requirements' preconditions foreach (Expression req in fn.Req) { auto_reqs.AddRange(generateAutoReqs(req)); } fn.Req.InsertRange(0, auto_reqs); // Need to come before the actual requires addAutoReqToolTipInfoToFunction("pre", fn, auto_reqs); // Then the body itself, if any if (fn.Body != null) { auto_reqs = generateAutoReqs(fn.Body); fn.Req.AddRange(auto_reqs); addAutoReqToolTipInfoToFunction("post", fn, auto_reqs); } } else { // Opaque functions need special handling // The opaque function's requirements are the same as for the _FULL version, // so we don't need to generate them again. We do, however, need to swap // the function's variables for those of the FULL version List fnVars = new List(); foreach (var formal in fn.Formals) { var id = new IdentifierExpr(formal.tok, formal.Name); id.Var = formal; // resolve here id.Type = formal.Type; // resolve here fnVars.Add(id); } var new_reqs = new List(); foreach (var req in opaqueInfo.FullVersion(fn).Req) { new_reqs.Add(subVars(opaqueInfo.FullVersion(fn).Formals, fnVars, req, null)); } fn.Req.Clear(); fn.Req.AddRange(new_reqs); } } } else if (scComponent is Method) { Method method = (Method)scComponent; if (Attributes.ContainsBoolAtAnyLevel(method, "autoReq")) { parentFunction = null; containsMatch = false; // Assume no match statements are involved List auto_reqs = new List(); foreach (MaybeFreeExpression req in method.Req) { List local_auto_reqs = generateAutoReqs(req.E); foreach (Expression local_auto_req in local_auto_reqs) { auto_reqs.Add(new MaybeFreeExpression(local_auto_req, !req.IsFree)); } } method.Req.InsertRange(0, auto_reqs); // Need to come before the actual requires addAutoReqToolTipInfoToMethod("pre", method, auto_reqs); } } } } Expression subVars(List formals, List values, Expression e, Expression f_this) { Contract.Assert(formals != null); Contract.Assert(values != null); Contract.Assert(formals.Count == values.Count); Dictionary substMap = new Dictionary(); Dictionary typeMap = new Dictionary(); for (int i = 0; i < formals.Count; i++) { substMap.Add(formals[i], values[i]); } Translator.Substituter sub = new Translator.Substituter(f_this, substMap, typeMap, null); return sub.Substitute(e); } public void addAutoReqToolTipInfoToFunction(string label, Function f, List reqs) { string prefix = "auto requires " + label + " "; string tip = ""; string sep = ""; foreach (var req in reqs) { if (containsMatch) { // Pretty print the requirements tip += sep + prefix + Printer.ExtendedExprToString(req) + ";"; } else { tip += sep + prefix + Printer.ExprToString(req) + ";"; } sep = "\n"; } if (!tip.Equals("")) { reporter.Info(MessageSource.Rewriter, f.tok, tip); if (DafnyOptions.O.AutoReqPrintFile != null) { using (System.IO.TextWriter writer = new System.IO.StreamWriter(DafnyOptions.O.AutoReqPrintFile, true)) { writer.WriteLine(f.Name); writer.WriteLine("\t" + tip); } } } } public void addAutoReqToolTipInfoToMethod(string label, Method method, List reqs) { string tip = ""; foreach (var req in reqs) { string prefix = "auto "; if (req.IsFree) { prefix += "free "; } prefix += " requires " + label + " "; if (containsMatch) { // Pretty print the requirements tip += prefix + Printer.ExtendedExprToString(req.E) + ";\n"; } else { tip += prefix + Printer.ExprToString(req.E) + ";\n"; } } if (!tip.Equals("")) { reporter.Info(MessageSource.Rewriter, method.tok, tip); if (DafnyOptions.O.AutoReqPrintFile != null) { using (System.IO.TextWriter writer = new System.IO.StreamWriter(DafnyOptions.O.AutoReqPrintFile, true)) { writer.WriteLine(method.Name); writer.WriteLine("\t" + tip); } } } } // Stitch a list of expressions together with logical ands Expression andify(Bpl.IToken tok, List exprs) { Expression ret = Expression.CreateBoolLiteral(tok, true); foreach (var expr in exprs) { ret = Expression.CreateAnd(ret, expr); } return ret; } List gatherReqs(Function f, List args, Expression f_this) { List translated_f_reqs = new List(); if (f.Req.Count > 0) { Dictionary substMap = new Dictionary(); Dictionary typeMap = new Dictionary(); for (int i = 0; i < f.Formals.Count; i++) { substMap.Add(f.Formals[i], args[i]); } foreach (var req in f.Req) { Translator.Substituter sub = new Translator.Substituter(f_this, substMap, typeMap, null); translated_f_reqs.Add(sub.Substitute(req)); } } return translated_f_reqs; } List generateAutoReqs(Expression expr) { List reqs = new List(); if (expr is LiteralExpr) { } else if (expr is ThisExpr) { } else if (expr is IdentifierExpr) { } else if (expr is SetDisplayExpr) { SetDisplayExpr e = (SetDisplayExpr)expr; foreach (var elt in e.Elements) { reqs.AddRange(generateAutoReqs(elt)); } } else if (expr is MultiSetDisplayExpr) { MultiSetDisplayExpr e = (MultiSetDisplayExpr)expr; foreach (var elt in e.Elements) { reqs.AddRange(generateAutoReqs(elt)); } } else if (expr is SeqDisplayExpr) { SeqDisplayExpr e = (SeqDisplayExpr)expr; foreach (var elt in e.Elements) { reqs.AddRange(generateAutoReqs(elt)); } } else if (expr is MapDisplayExpr) { MapDisplayExpr e = (MapDisplayExpr)expr; foreach (ExpressionPair p in e.Elements) { reqs.AddRange(generateAutoReqs(p.A)); reqs.AddRange(generateAutoReqs(p.B)); } } else if (expr is MemberSelectExpr) { MemberSelectExpr e = (MemberSelectExpr)expr; Contract.Assert(e.Member != null && e.Member is Field); reqs.AddRange(generateAutoReqs(e.Obj)); } else if (expr is SeqSelectExpr) { SeqSelectExpr e = (SeqSelectExpr)expr; reqs.AddRange(generateAutoReqs(e.Seq)); if (e.E0 != null) { reqs.AddRange(generateAutoReqs(e.E0)); } if (e.E1 != null) { reqs.AddRange(generateAutoReqs(e.E1)); } } else if (expr is SeqUpdateExpr) { SeqUpdateExpr e = (SeqUpdateExpr)expr; reqs.AddRange(generateAutoReqs(e.Seq)); reqs.AddRange(generateAutoReqs(e.Index)); reqs.AddRange(generateAutoReqs(e.Value)); } else if (expr is DatatypeUpdateExpr) { foreach (var ee in expr.SubExpressions) { reqs.AddRange(generateAutoReqs(ee)); } } else if (expr is FunctionCallExpr) { FunctionCallExpr e = (FunctionCallExpr)expr; // All of the arguments need to be satisfied foreach (var arg in e.Args) { reqs.AddRange(generateAutoReqs(arg)); } if (parentFunction != null && ModuleDefinition.InSameSCC(e.Function, parentFunction)) { // We're making a call within the same SCC, so don't descend into this function } else { reqs.AddRange(gatherReqs(e.Function, e.Args, e.Receiver)); } } else if (expr is DatatypeValue) { DatatypeValue dtv = (DatatypeValue)expr; Contract.Assert(dtv.Ctor != null); // since dtv has been successfully resolved for (int i = 0; i < dtv.Arguments.Count; i++) { Expression arg = dtv.Arguments[i]; reqs.AddRange(generateAutoReqs(arg)); } } else if (expr is OldExpr) { } else if (expr is MatchExpr) { MatchExpr e = (MatchExpr)expr; containsMatch = true; reqs.AddRange(generateAutoReqs(e.Source)); List newMatches = new List(); foreach (MatchCaseExpr caseExpr in e.Cases) { //MatchCaseExpr c = new MatchCaseExpr(caseExpr.tok, caseExpr.Id, caseExpr.Arguments, andify(caseExpr.tok, generateAutoReqs(caseExpr.Body))); //c.Ctor = caseExpr.Ctor; // resolve here MatchCaseExpr c = Expression.CreateMatchCase(caseExpr, andify(caseExpr.tok, generateAutoReqs(caseExpr.Body))); newMatches.Add(c); } reqs.Add(Expression.CreateMatch(e.tok, e.Source, newMatches, e.Type)); } else if (expr is MultiSetFormingExpr) { MultiSetFormingExpr e = (MultiSetFormingExpr)expr; reqs.AddRange(generateAutoReqs(e.E)); } else if (expr is UnaryExpr) { UnaryExpr e = (UnaryExpr)expr; Expression arg = e.E; reqs.AddRange(generateAutoReqs(arg)); } else if (expr is BinaryExpr) { BinaryExpr e = (BinaryExpr)expr; switch (e.ResolvedOp) { case BinaryExpr.ResolvedOpcode.Imp: case BinaryExpr.ResolvedOpcode.And: reqs.AddRange(generateAutoReqs(e.E0)); foreach (var req in generateAutoReqs(e.E1)) { // We only care about this req if E0 is true, since And short-circuits reqs.Add(Expression.CreateImplies(e.E0, req)); } break; case BinaryExpr.ResolvedOpcode.Or: reqs.AddRange(generateAutoReqs(e.E0)); foreach (var req in generateAutoReqs(e.E1)) { // We only care about this req if E0 is false, since Or short-circuits reqs.Add(Expression.CreateImplies(Expression.CreateNot(e.E1.tok, e.E0), req)); } break; default: reqs.AddRange(generateAutoReqs(e.E0)); reqs.AddRange(generateAutoReqs(e.E1)); break; } } else if (expr is TernaryExpr) { var e = (TernaryExpr)expr; reqs.AddRange(generateAutoReqs(e.E0)); reqs.AddRange(generateAutoReqs(e.E1)); reqs.AddRange(generateAutoReqs(e.E2)); } else if (expr is LetExpr) { var e = (LetExpr)expr; if (e.Exact) { foreach (var rhs in e.RHSs) { reqs.AddRange(generateAutoReqs(rhs)); } var new_reqs = generateAutoReqs(e.Body); if (new_reqs.Count > 0) { reqs.Add(Expression.CreateLet(e.tok, e.LHSs, e.RHSs, andify(e.tok, new_reqs), e.Exact)); } } else { // TODO: Still need to figure out what the right choice is here: // Given: var x :| g(x); f(x, y) do we: // 1) Update the original statement to be: var x :| g(x) && WP(f(x,y)); f(x, y) // 2) Add forall x :: g(x) ==> WP(f(x, y)) to the function's requirements // 3) Current option -- do nothing. Up to the spec writer to fix } } else if (expr is NamedExpr) { reqs.AddRange(generateAutoReqs(((NamedExpr)expr).Body)); } else if (expr is QuantifierExpr) { QuantifierExpr e = (QuantifierExpr)expr; // See LetExpr for issues with the e.Range var auto_reqs = generateAutoReqs(e.Term); if (auto_reqs.Count > 0) { Expression allReqsSatisfied = andify(e.Term.tok, auto_reqs); Expression allReqsSatisfiedAndTerm = Expression.CreateAnd(allReqsSatisfied, e.Term); e.UpdateTerm(allReqsSatisfiedAndTerm); reporter.Info(MessageSource.Rewriter, e.tok, "autoreq added (" + Printer.ExtendedExprToString(allReqsSatisfied) + ") &&"); } } else if (expr is SetComprehension) { var e = (SetComprehension)expr; // Translate "set xs | R :: T" // See LetExpr for issues with the e.Range //reqs.AddRange(generateAutoReqs(e.Range)); var auto_reqs = generateAutoReqs(e.Term); if (auto_reqs.Count > 0) { reqs.Add(Expression.CreateQuantifier(new ForallExpr(e.tok, new List(), e.BoundVars, e.Range, andify(e.Term.tok, auto_reqs), e.Attributes), true)); } } else if (expr is MapComprehension) { var e = (MapComprehension)expr; // Translate "map x | R :: T" into // See LetExpr for issues with the e.Range //reqs.AddRange(generateAutoReqs(e.Range)); var auto_reqs = generateAutoReqs(e.Term); if (auto_reqs.Count > 0) { reqs.Add(Expression.CreateQuantifier(new ForallExpr(e.tok, new List(), e.BoundVars, e.Range, andify(e.Term.tok, auto_reqs), e.Attributes), true)); } } else if (expr is StmtExpr) { var e = (StmtExpr)expr; reqs.AddRange(generateAutoReqs(e.E)); } else if (expr is ITEExpr) { ITEExpr e = (ITEExpr)expr; reqs.AddRange(generateAutoReqs(e.Test)); reqs.Add(Expression.CreateITE(e.Test, andify(e.Thn.tok, generateAutoReqs(e.Thn)), andify(e.Els.tok, generateAutoReqs(e.Els)))); } else if (expr is ConcreteSyntaxExpression) { var e = (ConcreteSyntaxExpression)expr; reqs.AddRange(generateAutoReqs(e.ResolvedExpression)); } else { //Contract.Assert(false); throw new cce.UnreachableException(); // unexpected expression } return reqs; } } /// /// Replace all occurrences of attribute {:timeLimitMultiplier X} with {:timeLimit Y} /// where Y = X*default-time-limit or Y = X*command-line-time-limit /// public class TimeLimitRewriter : IRewriter { public TimeLimitRewriter(ErrorReporter reporter) : base(reporter) { Contract.Requires(reporter != null); } internal override void PreResolve(ModuleDefinition m) { foreach (var d in m.TopLevelDecls) { if (d is ClassDecl) { var c = (ClassDecl)d; foreach (MemberDecl member in c.Members) { if (member is Function || member is Method) { // Check for the timeLimitMultiplier attribute if (Attributes.Contains(member.Attributes, "timeLimitMultiplier")) { Attributes attrs = member.Attributes; foreach (var attr in attrs.AsEnumerable()) { if (attr.Name == "timeLimitMultiplier") { if (attr.Args.Count == 1 && attr.Args[0] is LiteralExpr) { var arg = attr.Args[0] as LiteralExpr; System.Numerics.BigInteger value = (System.Numerics.BigInteger)arg.Value; if (value.Sign > 0) { int current_limit = DafnyOptions.O.ProverKillTime > 0 ? DafnyOptions.O.ProverKillTime : 10; // Default to 10 seconds attr.Args[0] = new LiteralExpr(attr.Args[0].tok, value * current_limit); attr.Name = "timeLimit"; } } } } } } } } } } } class MatchCaseExprSubstituteCloner : Cloner { private List> patternSubst; private BoundVar oldvar; private BoundVar var; // the cloner is called after resolving the body of matchexpr, trying // to replace casepattern in the body that has been replaced by bv public MatchCaseExprSubstituteCloner(List> subst) { this.patternSubst = subst; this.oldvar = null; this.var = null; } public MatchCaseExprSubstituteCloner(BoundVar oldvar, BoundVar var) { this.patternSubst = null; this.oldvar = oldvar; this.var = var; } public override BoundVar CloneBoundVar(BoundVar bv) { // replace bv with this.var if bv == oldvar BoundVar bvNew; if (oldvar != null && bv.Name.Equals(oldvar.Name)) { bvNew = new BoundVar(new AutoGeneratedToken(bv.tok), var.Name, CloneType(bv.Type)); } else { bvNew = new BoundVar(Tok(bv.tok), bv.Name, CloneType(bv.Type)); } bvNew.IsGhost = bv.IsGhost; return bvNew; } public override NameSegment CloneNameSegment(Expression expr) { var e = (NameSegment)expr; if (oldvar != null && e.Name.Equals(oldvar.Name)) { return new NameSegment(new AutoGeneratedToken(e.tok), var.Name, e.OptTypeArguments == null ? null : e.OptTypeArguments.ConvertAll(CloneType)); } else { return new NameSegment(Tok(e.tok), e.Name, e.OptTypeArguments == null ? null : e.OptTypeArguments.ConvertAll(CloneType)); } } public override Expression CloneApplySuffix(ApplySuffix e) { // if the ApplySuffix matches the CasePattern, then replace it with the BoundVar. CasePattern cp = null; BoundVar bv = null; if (FindMatchingPattern(e, out cp, out bv)) { if (bv.tok is MatchCaseToken) { Contract.Assert(e.Args.Count == cp.Arguments.Count); for (int i = 0; i < e.Args.Count; i++) { ((MatchCaseToken)bv.tok).AddVar(e.Args[i].tok, cp.Arguments[i].Var, false); } } return new NameSegment(new AutoGeneratedToken(e.tok), bv.Name, null); } else { return new ApplySuffix(Tok(e.tok), CloneExpr(e.Lhs), e.Args.ConvertAll(CloneExpr)); } } private bool FindMatchingPattern(ApplySuffix e, out CasePattern pattern, out BoundVar bv) { pattern = null; bv = null; if (patternSubst == null) { return false; } Expression lhs = e.Lhs; if (!(lhs is NameSegment)) { return false; } string applyName = ((NameSegment)lhs).Name; foreach (Tuple pair in patternSubst) { CasePattern cp = pair.Item1; string ctorName = cp.Id; if (!(applyName.Equals(ctorName)) || (e.Args.Count != cp.Arguments.Count)) { continue; } bool found = true; for (int i = 0; i < e.Args.Count; i++) { var arg1 = e.Args[i]; var arg2 = cp.Arguments[i]; if (arg1.Resolved is IdentifierExpr) { var bv1 = ((IdentifierExpr)arg1.Resolved).Var; if (bv1 != arg2.Var) { found = false; } } else { found = false; } } if (found) { pattern = cp; bv = pair.Item2; return true; } } return false; } } // MatchCaseToken is used to record BoundVars that are consolidated due to rewrite of // nested match patterns. We want to record the original BoundVars that are consolidated // so that they will show up in the IDE correctly. public class MatchCaseToken : AutoGeneratedToken { public readonly List> varList; public MatchCaseToken(IToken tok) : base(tok) { varList = new List>(); } public void AddVar(IToken tok, BoundVar var, bool isDefinition) { varList.Add(new Tuple(tok, var, isDefinition)); } } // A cloner that replace the original token with AutoGeneratedToken. class AutoGeneratedTokenCloner : Cloner { public override IToken Tok(IToken tok) { return new AutoGeneratedToken(tok); } } // =========================================================================================== public class InductionRewriter : IRewriter { internal InductionRewriter(ErrorReporter reporter) : base(reporter) { Contract.Requires(reporter != null); } internal override void PostCyclicityResolve(ModuleDefinition m) { if (DafnyOptions.O.Induction == 0) { // Don't bother inferring :induction attributes. This will also have the effect of not warning about malformed :induction attributes } else { foreach (var decl in m.TopLevelDecls) { if (decl is ClassDecl) { var cl = (ClassDecl)decl; foreach (var member in cl.Members) { if (member is FixpointLemma) { var method = (FixpointLemma)member; ProcessMethodExpressions(method); ComputeLemmaInduction(method.PrefixLemma); ProcessMethodExpressions(method.PrefixLemma); } else if (member is Method) { var method = (Method)member; ComputeLemmaInduction(method); ProcessMethodExpressions(method); } else if (member is FixpointPredicate) { var function = (FixpointPredicate)member; ProcessFunctionExpressions(function); ProcessFunctionExpressions(function.PrefixPredicate); } else if (member is Function) { var function = (Function)member; ProcessFunctionExpressions(function); } } } else if (decl is NewtypeDecl) { var nt = (NewtypeDecl)decl; if (nt.Constraint != null) { var visitor = new Induction_Visitor(this); visitor.Visit(nt.Constraint); } } } } } void ProcessMethodExpressions(Method method) { Contract.Requires(method != null); var visitor = new Induction_Visitor(this); method.Req.ForEach(mfe => visitor.Visit(mfe.E)); method.Ens.ForEach(mfe => visitor.Visit(mfe.E)); if (method.Body != null) { visitor.Visit(method.Body); } } void ProcessFunctionExpressions(Function function) { Contract.Requires(function != null); var visitor = new Induction_Visitor(this); function.Req.ForEach(visitor.Visit); function.Ens.ForEach(visitor.Visit); if (function.Body != null) { visitor.Visit(function.Body); } } void ComputeLemmaInduction(Method method) { Contract.Requires(method != null); if (method.Body != null && method.IsGhost && method.Mod.Expressions.Count == 0 && method.Outs.Count == 0 && !(method is FixpointLemma)) { var specs = new List(); method.Req.ForEach(mfe => specs.Add(mfe.E)); method.Ens.ForEach(mfe => specs.Add(mfe.E)); ComputeInductionVariables(method.tok, method.Ins, specs, method, ref method.Attributes); } } void ComputeInductionVariables(IToken tok, List boundVars, List searchExprs, Method lemma, ref Attributes attributes) where VarType : class, IVariable { Contract.Requires(tok != null); Contract.Requires(boundVars != null); Contract.Requires(searchExprs != null); Contract.Requires(DafnyOptions.O.Induction != 0); var args = Attributes.FindExpressions(attributes, "induction"); // we only look at the first one we find, since it overrides any other ones if (args == null) { if (DafnyOptions.O.Induction < 2) { // No explicit induction variables and we're asked not to infer anything, so we're done return; } else if (DafnyOptions.O.Induction == 2 && lemma != null) { // We're asked to infer induction variables only for quantifiers, not for lemmas return; } // GO INFER below (only select boundVars) } else if (args.Count == 0) { // {:induction} is treated the same as {:induction true}, which says to automatically infer induction variables // GO INFER below (all boundVars) } else if (args.Count == 1 && args[0] is LiteralExpr && ((LiteralExpr)args[0]).Value is bool) { // {:induction false} or {:induction true} if (!(bool)((LiteralExpr)args[0]).Value) { // we're told not to infer anything return; } // GO INFER below (all boundVars) } else { // Here, we're expecting the arguments to {:induction args} to be a sublist of "boundVars". var goodArguments = new List(); var i = 0; foreach (var arg in args) { var ie = arg.Resolved as IdentifierExpr; if (ie != null) { var j = boundVars.FindIndex(i, v => v == ie.Var); if (i <= j) { goodArguments.Add(ie); i = j; continue; } if (0 <= boundVars.FindIndex(v => v == ie.Var)) { reporter.Warning(MessageSource.Rewriter, arg.tok, "{0}s given as :induction arguments must be given in the same order as in the {1}; ignoring attribute", lemma != null ? "lemma parameter" : "bound variable", lemma != null ? "lemma" : "quantifier"); return; } } reporter.Warning(MessageSource.Rewriter, arg.tok, "invalid :induction attribute argument; expected {0}{1}; ignoring attribute", i == 0 ? "'false' or 'true' or " : "", lemma != null ? "lemma parameter" : "bound variable"); return; } // The argument list was legal, so let's use it for the _induction attribute attributes = new Attributes("_induction", goodArguments, attributes); return; } // Okay, here we go, coming up with good induction setting for the given situation var inductionVariables = new List(); foreach (IVariable n in boundVars) { if (!n.Type.IsTypeParameter && (args != null || searchExprs.Exists(expr => VarOccursInArgumentToRecursiveFunction(expr, n)))) { inductionVariables.Add(new IdentifierExpr(n)); } } if (inductionVariables.Count != 0) { // We found something usable, so let's record that in an attribute attributes = new Attributes("_induction", inductionVariables, attributes); // And since we're inferring something, let's also report that in a hover text. var s = Printer.OneAttributeToString(attributes, "induction"); if (lemma is PrefixLemma) { s = lemma.Name + " " + s; } reporter.Info(MessageSource.Rewriter, tok, s); } } class Induction_Visitor : BottomUpVisitor { readonly InductionRewriter IndRewriter; public Induction_Visitor(InductionRewriter inductionRewriter) { Contract.Requires(inductionRewriter != null); IndRewriter = inductionRewriter; } protected override void VisitOneExpr(Expression expr) { var q = expr as QuantifierExpr; if (q != null && q.SplitQuantifier == null) { IndRewriter.ComputeInductionVariables(q.tok, q.BoundVars, new List() { q.LogicalBody() }, null, ref q.Attributes); } } void VisitInductionStmt(Statement stmt) { Contract.Requires(stmt != null); // visit a selection of subexpressions if (stmt is AssertStmt) { var s = (AssertStmt)stmt; Visit(s.Expr); } // recursively visit all substatements foreach (var s in stmt.SubStatements) { VisitInductionStmt(s); } } } /// /// Returns 'true' iff by looking at 'expr' the Induction Heuristic determines that induction should be applied to 'n'. /// More precisely: /// DafnyInductionHeuristic Return 'true' /// ----------------------- ------------- /// 0 always /// 1 if 'n' occurs as any subexpression (of 'expr') /// 2 if 'n' occurs as any subexpression of any index argument of an array/sequence select expression or any argument to a recursive function /// 3 if 'n' occurs as a prominent subexpression of any index argument of an array/sequence select expression or any argument to a recursive function /// 4 if 'n' occurs as any subexpression of any argument to a recursive function /// 5 if 'n' occurs as a prominent subexpression of any argument to a recursive function /// 6 if 'n' occurs as a prominent subexpression of any decreases-influencing argument to a recursive function /// Parameter 'n' is allowed to be a ThisSurrogate. /// public static bool VarOccursInArgumentToRecursiveFunction(Expression expr, IVariable n) { switch (DafnyOptions.O.InductionHeuristic) { case 0: return true; case 1: return Translator.ContainsFreeVariable(expr, false, n); default: return VarOccursInArgumentToRecursiveFunction(expr, n, false); } } /// /// Worker routine for VarOccursInArgumentToRecursiveFunction(expr,n), where the additional parameter 'exprIsProminent' says whether or /// not 'expr' has prominent status in its context. /// DafnyInductionHeuristic cases 0 and 1 are assumed to be handled elsewhere (i.e., a precondition of this method is DafnyInductionHeuristic is at least 2). /// Parameter 'n' is allowed to be a ThisSurrogate. /// static bool VarOccursInArgumentToRecursiveFunction(Expression expr, IVariable n, bool exprIsProminent) { Contract.Requires(expr != null); Contract.Requires(n != null); // The following variable is what gets passed down to recursive calls if the subexpression does not itself acquire prominent status. var subExprIsProminent = DafnyOptions.O.InductionHeuristic == 2 || DafnyOptions.O.InductionHeuristic == 4 ? /*once prominent, always prominent*/exprIsProminent : /*reset the prominent status*/false; if (expr is IdentifierExpr) { var e = (IdentifierExpr)expr; return exprIsProminent && e.Var == n; } else if (expr is SeqSelectExpr) { var e = (SeqSelectExpr)expr; var q = DafnyOptions.O.InductionHeuristic < 4 || subExprIsProminent; return VarOccursInArgumentToRecursiveFunction(e.Seq, n, subExprIsProminent) || // this subexpression does not acquire "prominent" status (e.E0 != null && VarOccursInArgumentToRecursiveFunction(e.E0, n, q)) || // this one does (unless arrays/sequences are excluded) (e.E1 != null && VarOccursInArgumentToRecursiveFunction(e.E1, n, q)); // ditto } else if (expr is MultiSelectExpr) { var e = (MultiSelectExpr)expr; var q = DafnyOptions.O.InductionHeuristic < 4 || subExprIsProminent; return VarOccursInArgumentToRecursiveFunction(e.Array, n, subExprIsProminent) || e.Indices.Exists(exp => VarOccursInArgumentToRecursiveFunction(exp, n, q)); } else if (expr is FunctionCallExpr) { var e = (FunctionCallExpr)expr; // For recursive functions: arguments are "prominent" // For non-recursive function: arguments are "prominent" if the call is var rec = e.Function.IsRecursive && e.CoCall != FunctionCallExpr.CoCallResolution.Yes; var decr = e.Function.Decreases.Expressions; bool variantArgument; if (DafnyOptions.O.InductionHeuristic < 6) { variantArgument = rec; } else { // The receiver is considered to be "variant" if the function is recursive and the receiver participates // in the effective decreases clause of the function. The receiver participates if it's a free variable // of a term in the explicit decreases clause. variantArgument = rec && decr.Exists(ee => Translator.ContainsFreeVariable(ee, true, null)); } if (VarOccursInArgumentToRecursiveFunction(e.Receiver, n, variantArgument || subExprIsProminent)) { return true; } Contract.Assert(e.Function.Formals.Count == e.Args.Count); for (int i = 0; i < e.Function.Formals.Count; i++) { var f = e.Function.Formals[i]; var exp = e.Args[i]; if (DafnyOptions.O.InductionHeuristic < 6) { variantArgument = rec; } else if (rec) { // The argument position is considered to be "variant" if the function is recursive and... // ... it has something to do with why the callee is well-founded, which happens when... if (f is ImplicitFormal) { // ... it is the argument is the implicit _k parameter, which is always first in the effective decreases clause of a prefix lemma, or variantArgument = true; } else if (decr.Exists(ee => Translator.ContainsFreeVariable(ee, false, f))) { // ... it participates in the effective decreases clause of the function, which happens when it is // a free variable of a term in the explicit decreases clause, or variantArgument = true; } else { // ... the callee is a prefix predicate. variantArgument = true; } } if (VarOccursInArgumentToRecursiveFunction(exp, n, variantArgument || subExprIsProminent)) { return true; } } return false; } else if (expr is TernaryExpr) { var e = (TernaryExpr)expr; switch (e.Op) { case TernaryExpr.Opcode.PrefixEqOp: case TernaryExpr.Opcode.PrefixNeqOp: return VarOccursInArgumentToRecursiveFunction(e.E0, n, true) || VarOccursInArgumentToRecursiveFunction(e.E1, n, subExprIsProminent) || VarOccursInArgumentToRecursiveFunction(e.E2, n, subExprIsProminent); default: Contract.Assert(false); throw new cce.UnreachableException(); // unexpected ternary expression } } else if (expr is DatatypeValue) { var e = (DatatypeValue)expr; var q = n.Type.IsDatatype ? exprIsProminent : subExprIsProminent; // prominent status continues, if we're looking for a variable whose type is a datatype return e.Arguments.Exists(exp => VarOccursInArgumentToRecursiveFunction(exp, n, q)); } else if (expr is UnaryExpr) { var e = (UnaryExpr)expr; // both Not and SeqLength preserve prominence return VarOccursInArgumentToRecursiveFunction(e.E, n, exprIsProminent); } else if (expr is BinaryExpr) { var e = (BinaryExpr)expr; bool q; switch (e.ResolvedOp) { case BinaryExpr.ResolvedOpcode.Add: case BinaryExpr.ResolvedOpcode.Sub: case BinaryExpr.ResolvedOpcode.Mul: case BinaryExpr.ResolvedOpcode.Div: case BinaryExpr.ResolvedOpcode.Mod: case BinaryExpr.ResolvedOpcode.Union: case BinaryExpr.ResolvedOpcode.Intersection: case BinaryExpr.ResolvedOpcode.SetDifference: case BinaryExpr.ResolvedOpcode.Concat: // these operators preserve prominence q = exprIsProminent; break; default: // whereas all other binary operators do not q = subExprIsProminent; break; } return VarOccursInArgumentToRecursiveFunction(e.E0, n, q) || VarOccursInArgumentToRecursiveFunction(e.E1, n, q); } else if (expr is StmtExpr) { var e = (StmtExpr)expr; // ignore the statement return VarOccursInArgumentToRecursiveFunction(e.E, n); } else if (expr is ITEExpr) { var e = (ITEExpr)expr; return VarOccursInArgumentToRecursiveFunction(e.Test, n, subExprIsProminent) || // test is not "prominent" VarOccursInArgumentToRecursiveFunction(e.Thn, n, exprIsProminent) || // but the two branches are VarOccursInArgumentToRecursiveFunction(e.Els, n, exprIsProminent); } else if (expr is OldExpr || expr is ConcreteSyntaxExpression || expr is BoxingCastExpr || expr is UnboxingCastExpr) { foreach (var exp in expr.SubExpressions) { if (VarOccursInArgumentToRecursiveFunction(exp, n, exprIsProminent)) { // maintain prominence return true; } } return false; } else { // in all other cases, reset the prominence status and recurse on the subexpressions foreach (var exp in expr.SubExpressions) { if (VarOccursInArgumentToRecursiveFunction(exp, n, subExprIsProminent)) { return true; } } return false; } } } }