//----------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All Rights Reserved. // //----------------------------------------------------------------------------- using System; using System.Linq; using System.Collections; using System.Collections.Generic; using System.Threading; using System.IO; using System.Text.RegularExpressions; using System.Diagnostics; using System.Diagnostics.Contracts; using Microsoft.Boogie.AbstractInterpretation; using Microsoft.Boogie.VCExprAST; using Microsoft.Basetypes; using System.Threading.Tasks; namespace Microsoft.Boogie { enum CheckerStatus { Idle, Ready, Busy, Closed } /// /// Interface to the theorem prover specialized to Boogie. /// /// This class creates the appropriate background axioms. There /// should be one instance per BoogiePL program. /// public sealed class Checker { [ContractInvariantMethod] void ObjectInvariant() { Contract.Invariant(gen != null); Contract.Invariant(thmProver != null); } private readonly VCExpressionGenerator gen; private ProverInterface thmProver; private int timeout; // state for the async interface private volatile ProverInterface.Outcome outcome; private volatile bool hasOutput; private volatile UnexpectedProverOutputException outputExn; private DateTime proverStart; private TimeSpan proverRunTime; private volatile ProverInterface.ErrorHandler handler; private volatile CheckerStatus status; public volatile Program Program; public void GetReady() { Contract.Requires(IsIdle); status = CheckerStatus.Ready; } public void GoBackToIdle() { Contract.Requires(IsBusy); status = CheckerStatus.Idle; } public Task ProverTask { get; set; } public bool WillingToHandle(int timeout, Program prog) { return status == CheckerStatus.Idle && timeout == this.timeout && (prog == null || Program == prog); } public VCExpressionGenerator VCExprGen { get { Contract.Ensures(Contract.Result() != null); return this.gen; } } public ProverInterface TheoremProver { get { Contract.Ensures(Contract.Result() != null); return this.thmProver; } } ///////////////////////////////////////////////////////////////////////////////// // We share context information for the same program between different Checkers private struct ContextCacheKey { [ContractInvariantMethod] void ObjectInvariant() { Contract.Invariant(program != null); } public readonly Program program; public ContextCacheKey(Program prog) { Contract.Requires(prog != null); this.program = prog; } [Pure] [Reads(ReadsAttribute.Reads.Nothing)] public override bool Equals(object that) { if (that is ContextCacheKey) { ContextCacheKey thatKey = (ContextCacheKey)that; return this.program.Equals(thatKey.program); } return false; } [Pure] public override int GetHashCode() { return this.program.GetHashCode(); } } ///////////////////////////////////////////////////////////////////////////////// /// /// Constructor. Initialize a checker with the program and log file. /// Optionally, use prover context provided by parameter "ctx". /// public Checker(VC.ConditionGeneration vcgen, Program prog, string/*?*/ logFilePath, bool appendLogFile, int timeout, ProverContext ctx = null) { Contract.Requires(vcgen != null); Contract.Requires(prog != null); this.timeout = timeout; this.Program = prog; ProverOptions options = cce.NonNull(CommandLineOptions.Clo.TheProverFactory).BlankProverOptions(); if (logFilePath != null) { options.LogFilename = logFilePath; if (appendLogFile) options.AppendLogFile = appendLogFile; } if (timeout > 0) { options.TimeLimit = timeout * 1000; } options.Parse(CommandLineOptions.Clo.ProverOptions); ContextCacheKey key = new ContextCacheKey(prog); ProverInterface prover; if (vcgen.CheckerCommonState == null) { vcgen.CheckerCommonState = new Dictionary(); } IDictionary/*!>!*/ cachedContexts = (IDictionary)vcgen.CheckerCommonState; if (ctx == null && cachedContexts.TryGetValue(key, out ctx)) { ctx = (ProverContext)cce.NonNull(ctx).Clone(); prover = (ProverInterface) CommandLineOptions.Clo.TheProverFactory.SpawnProver(options, ctx); } else { if (ctx == null) ctx = (ProverContext)CommandLineOptions.Clo.TheProverFactory.NewProverContext(options); Setup(prog, ctx); // we first generate the prover and then store a clone of the // context in the cache, so that the prover can setup stuff in // the context to be cached prover = (ProverInterface) CommandLineOptions.Clo.TheProverFactory.SpawnProver(options, ctx); cachedContexts.Add(key, cce.NonNull((ProverContext)ctx.Clone())); } this.thmProver = prover; this.gen = prover.VCExprGen; } public void Retarget(Program prog, ProverContext ctx, int timeout = 0) { lock (this) { hasOutput = default(bool); outcome = default(ProverInterface.Outcome); outputExn = default(UnexpectedProverOutputException); handler = default(ProverInterface.ErrorHandler); TheoremProver.FullReset(gen); ctx.Reset(); Setup(prog, ctx); this.timeout = timeout; SetTimeout(); } } public void RetargetWithoutReset(Program prog, ProverContext ctx) { ctx.Clear(); Setup(prog, ctx); } public void SetTimeout() { if (0 < timeout) { TheoremProver.SetTimeOut(timeout * 1000); } else { TheoremProver.SetTimeOut(0); } } /// /// Set up the context. /// private void Setup(Program prog, ProverContext ctx) { Program = prog; // TODO(wuestholz): Is this lock necessary? lock (Program.TopLevelDeclarations) { foreach (Declaration decl in Program.TopLevelDeclarations) { Contract.Assert(decl != null); var typeDecl = decl as TypeCtorDecl; var constDecl = decl as Constant; var funDecl = decl as Function; var axiomDecl = decl as Axiom; var glVarDecl = decl as GlobalVariable; if (typeDecl != null) { ctx.DeclareType(typeDecl, null); } else if (constDecl != null) { ctx.DeclareConstant(constDecl, constDecl.Unique, null); } else if (funDecl != null) { ctx.DeclareFunction(funDecl, null); } else if (axiomDecl != null) { ctx.AddAxiom(axiomDecl, null); } else if (glVarDecl != null) { ctx.DeclareGlobalVariable(glVarDecl, null); } } } } /// /// Clean-up. /// public void Close() { thmProver.Close(); status = CheckerStatus.Closed; } /// /// Push a Verification Condition as an Axiom /// (Required for Doomed Program Point detection) /// public void PushVCExpr(VCExpr vc) { Contract.Requires(vc != null); //thmProver.Context.AddAxiom(vc); thmProver.PushVCExpression(vc); } public bool IsBusy { get { return status == CheckerStatus.Busy; } } public bool IsReady { get { return status == CheckerStatus.Ready; } } public bool IsClosed { get { return status == CheckerStatus.Closed; } } public bool IsIdle { get { return status == CheckerStatus.Idle; } } public bool HasOutput { get { return hasOutput; } } public TimeSpan ProverRunTime { get { return proverRunTime; } } private void WaitForOutput(object dummy) { lock (this) { try { outcome = thmProver.CheckOutcome(cce.NonNull(handler)); } catch (UnexpectedProverOutputException e) { outputExn = e; } switch (outcome) { case ProverInterface.Outcome.Valid: thmProver.LogComment("Valid"); break; case ProverInterface.Outcome.Invalid: thmProver.LogComment("Invalid"); break; case ProverInterface.Outcome.TimeOut: thmProver.LogComment("Timed out"); break; case ProverInterface.Outcome.OutOfMemory: thmProver.LogComment("Out of memory"); break; case ProverInterface.Outcome.Undetermined: thmProver.LogComment("Undetermined"); break; } hasOutput = true; proverRunTime = DateTime.UtcNow - proverStart; } } public void BeginCheck(string descriptiveName, VCExpr vc, ProverInterface.ErrorHandler handler) { Contract.Requires(descriptiveName != null); Contract.Requires(vc != null); Contract.Requires(handler != null); Contract.Requires(IsReady); status = CheckerStatus.Busy; hasOutput = false; outputExn = null; this.handler = handler; thmProver.Reset(gen); SetTimeout(); proverStart = DateTime.UtcNow; thmProver.BeginCheck(descriptiveName, vc, handler); // gen.ClearSharedFormulas(); PR: don't know yet what to do with this guy ProverTask = Task.Factory.StartNew(() => { WaitForOutput(null); }, TaskCreationOptions.LongRunning); } public ProverInterface.Outcome ReadOutcome() { Contract.Requires(IsBusy); Contract.Requires(HasOutput); Contract.EnsuresOnThrow(true); hasOutput = false; if (outputExn != null) { throw outputExn; } return outcome; } } // ----------------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------------- public abstract class ProverInterface { public static ProverInterface CreateProver(Program prog, string/*?*/ logFilePath, bool appendLogFile, int timeout, int taskID = -1) { Contract.Requires(prog != null); ProverOptions options = cce.NonNull(CommandLineOptions.Clo.TheProverFactory).BlankProverOptions(); if (logFilePath != null) { options.LogFilename = logFilePath; if (appendLogFile) options.AppendLogFile = appendLogFile; } if (timeout > 0) { options.TimeLimit = timeout * 1000; } if (taskID >= 0) { options.Parse(CommandLineOptions.Clo.Cho[taskID].ProverOptions); } else { options.Parse(CommandLineOptions.Clo.ProverOptions); } ProverContext ctx = (ProverContext)CommandLineOptions.Clo.TheProverFactory.NewProverContext(options); // set up the context foreach (Declaration decl in prog.TopLevelDeclarations) { Contract.Assert(decl != null); TypeCtorDecl t = decl as TypeCtorDecl; if (t != null) { ctx.DeclareType(t, null); } } foreach (Declaration decl in prog.TopLevelDeclarations) { Contract.Assert(decl != null); Constant c = decl as Constant; if (c != null) { ctx.DeclareConstant(c, c.Unique, null); } else { Function f = decl as Function; if (f != null) { ctx.DeclareFunction(f, null); } } } foreach (var ax in prog.Axioms) { ctx.AddAxiom(ax, null); } foreach (Declaration decl in prog.TopLevelDeclarations) { Contract.Assert(decl != null); GlobalVariable v = decl as GlobalVariable; if (v != null) { ctx.DeclareGlobalVariable(v, null); } } return (ProverInterface)CommandLineOptions.Clo.TheProverFactory.SpawnProver(options, ctx); } public enum Outcome { Valid, Invalid, TimeOut, OutOfMemory, Undetermined, Bounded } public class ErrorHandler { // Used in CheckOutcomeCore public virtual int StartingProcId() { return 0; } public virtual void OnModel(IList labels, Model model, Outcome proverOutcome) { Contract.Requires(cce.NonNullElements(labels)); } public virtual void OnResourceExceeded(string message, IEnumerable> assertCmds = null) { Contract.Requires(message != null); } public virtual void OnProverWarning(string message) { Contract.Requires(message != null); switch (CommandLineOptions.Clo.PrintProverWarnings) { case CommandLineOptions.ProverWarnings.None: break; case CommandLineOptions.ProverWarnings.Stdout: Console.WriteLine("Prover warning: " + message); break; case CommandLineOptions.ProverWarnings.Stderr: Console.Error.WriteLine("Prover warning: " + message); break; default: Contract.Assume(false); throw new cce.UnreachableException(); // unexpected case } } public virtual Absy Label2Absy(string label) { Contract.Requires(label != null); Contract.Ensures(Contract.Result() != null); throw new System.NotImplementedException(); } } public abstract void BeginCheck(string descriptiveName, VCExpr vc, ErrorHandler handler); public virtual Outcome CheckRPFP(string descriptiveName, RPFP vc, ErrorHandler handler, out RPFP.Node cex, Dictionary> varSubst, Dictionary extra_bound = null) { throw new System.NotImplementedException(); } [NoDefaultContract] public abstract Outcome CheckOutcome(ErrorHandler handler, int taskID = -1); public virtual string[] CalculatePath(int controlFlowConstant) { throw new System.NotImplementedException(); } public virtual void LogComment(string comment) { Contract.Requires(comment != null); } public virtual void Close() { } public abstract void Reset(VCExpressionGenerator gen); public abstract void FullReset(VCExpressionGenerator gen); /// /// MSchaef: Allows to Push a VCExpression as Axiom on the prover stack (beta) /// for now it is only implemented by ProcessTheoremProver and still requires some /// testing /// public virtual void PushVCExpression(VCExpr vc) { Contract.Requires(vc != null); throw new NotImplementedException(); } public virtual string VCExpressionToString(VCExpr vc) { Contract.Requires(vc != null); Contract.Ensures(Contract.Result() != null); throw new NotImplementedException(); } public virtual void Pop() { Contract.EnsuresOnThrow(true); throw new NotImplementedException(); } public virtual int NumAxiomsPushed() { throw new NotImplementedException(); } public virtual int FlushAxiomsToTheoremProver() { Contract.EnsuresOnThrow(true); throw new NotImplementedException(); } // (assert vc) public virtual void Assert(VCExpr vc, bool polarity) { throw new NotImplementedException(); } // (assert implicit-axioms) public virtual void AssertAxioms() { throw new NotImplementedException(); } // (check-sat) public virtual void Check() { throw new NotImplementedException(); } // (check-sat + get-unsat-core + checkOutcome) public virtual Outcome CheckAssumptions(List assumptions, out List unsatCore, ErrorHandler handler) { throw new NotImplementedException(); } public virtual Outcome CheckAssumptions(List hardAssumptions, List softAssumptions, out List unsatisfiedSoftAssumptions, ErrorHandler handler) { throw new NotImplementedException(); } public virtual Outcome CheckOutcomeCore(ErrorHandler handler, int taskID = -1) { throw new NotImplementedException(); } // (push 1) public virtual void Push() { throw new NotImplementedException(); } // Set theorem prover timeout for the next "check-sat" public virtual void SetTimeOut(int ms) { } public abstract ProverContext Context { get; } public abstract VCExpressionGenerator VCExprGen { get; } public virtual void DefineMacro(Macro fun, VCExpr vc) { throw new NotImplementedException(); } public class VCExprEvaluationException : Exception { } public virtual object Evaluate(VCExpr expr) { throw new NotImplementedException(); } ////////////////////// // For interpolation queries ////////////////////// // Assert vc tagged with a name public virtual void AssertNamed(VCExpr vc, bool polarity, string name) { throw new NotImplementedException(); } // Returns Interpolant(A,B) public virtual VCExpr ComputeInterpolant(VCExpr A, VCExpr B) { throw new NotImplementedException(); } // Returns for each l, Interpolant(root + (leaves - l), l) // Preconditions: // leaves cannot have subformulas with same variable names // Both root and leaves should have been previously named via AssertNamed public virtual List GetTreeInterpolant(List root, List leaves) { throw new NotImplementedException(); } } public class ProverInterfaceContracts : ProverInterface { public override ProverContext Context { get { Contract.Ensures(Contract.Result() != null); throw new NotImplementedException(); } } public override VCExpressionGenerator VCExprGen { get { Contract.Ensures(Contract.Result() != null); throw new NotImplementedException(); } } public override void BeginCheck(string descriptiveName, VCExpr vc, ErrorHandler handler) {/*Contract.Requires(descriptiveName != null);*/ //Contract.Requires(vc != null); //Contract.Requires(handler != null); throw new NotImplementedException(); } [NoDefaultContract] public override Outcome CheckOutcome(ErrorHandler handler, int taskID = -1) { //Contract.Requires(handler != null); Contract.EnsuresOnThrow(true); throw new NotImplementedException(); } public override void Reset(VCExpressionGenerator gen) { throw new NotImplementedException(); } public override void FullReset(VCExpressionGenerator gen) { throw new NotImplementedException(); } } public class ProverException : Exception { public ProverException(string s) : base(s) { } } public class UnexpectedProverOutputException : ProverException { public UnexpectedProverOutputException(string s) : base(s) { } } public class ProverDiedException : UnexpectedProverOutputException { public ProverDiedException() : base("Prover died with no further output, perhaps it ran out of memory or was killed.") { } } }