//-----------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation. All Rights Reserved.
//
//-----------------------------------------------------------------------------
using System;
using System.IO;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Collections;
using System.Collections.Generic;
//using util;
using Microsoft.Boogie;
// Simplified interface to an external prover like Simplify or the z3 process, taken from Bird.
namespace Microsoft.Boogie.Simplify {
///
/// An interface to Simplify theorem prover.
///
public abstract class ProverProcess {
[ContractInvariantMethod]
void ObjectInvariant() {
Contract.Invariant(simplify != null);
Contract.Invariant(fromSimplify != null);
Contract.Invariant(toSimplify != null);
Contract.Invariant(fromStdError != null);
}
[Rep]
protected readonly Process simplify;
[Rep]
readonly TextReader fromSimplify;
[Rep]
readonly TextWriter toSimplify;
[Rep]
readonly TextReader fromStdError;
protected bool readTimedOut;
int nFormulasChecked = 0;
public int NumFormulasChecked {
get {
return nFormulasChecked;
}
}
// Note: In the Everett build (.NET framework < 2.0), Process.PeakVirtualMemorySize64
// is not defined, but rather a version that returns an 'int' rather than a 'long'.
public long PeakVirtualMemorySize {
[NoDefaultContract]
get
//modifies this.*;
{
Contract.Requires(cce.IsPeerConsistent(this));
cce.BeginExpose(this);
{
simplify.Refresh();
#if WHIDBEY
return simplify.PeakVirtualMemorySize64;
#else
return simplify.PeakPagedMemorySize64;
#endif
}
cce.EndExpose();
}
}
public bool HasExited {
get {
return simplify.HasExited;
}
}
public ProverProcess(ProcessStartInfo psi, string proverPath) { // throws ProverException
Contract.Requires(psi != null);
Contract.Requires(proverPath != null);
try {
Process simplify = Process.Start(psi);
this.simplify = simplify;
fromSimplify = simplify.StandardOutput;
toSimplify = simplify.StandardInput;
fromStdError = simplify.StandardError;
} catch (System.ComponentModel.Win32Exception e) {
throw new ProverException(string.Format("Unable to start the process {0}: {1}", proverPath, e.Message));
}
// base();
}
public abstract string OptionComments();
//[Pure(false)]
public virtual IEnumerable!*/> ParameterSettings {
get {
yield break;
}
}
public void Close()
//modifies this.*;
{
cce.BeginExpose(this);
{
toSimplify.Flush();
if (this.simplify != null) {
if (!simplify.HasExited) {
this.Kill();
}
simplify.Close();
}
}
cce.EndExpose();
}
[NoDefaultContract] // this method assumes nothing about the object, other than that it has been constructed (which means simplify!=null)
[Verify(false)] // The call simplify.Kill will require simplify.IsPeerConsistent, but since we don't know the state of "this" and "simplify", we cannot afford the run-time check that an assume statement here would impose
public void Kill() {
try {
if (CommandLineOptions.Clo.ProverShutdownLimit > 0) {
toSimplify.Close();
for (int i = 0; !simplify.HasExited && i <= CommandLineOptions.Clo.ProverShutdownLimit * 1000; i += 100) {
System.Threading.Thread.Sleep(100);
}
}
if (!simplify.HasExited) {
simplify.Kill();
}
} catch (InvalidOperationException) { /* already exited */
} catch (System.ComponentModel.Win32Exception) { /* already exiting */
}
}
public virtual void AddAxioms(string s)
//modifies this.*;
//modifies Console.Out.*, Console.Error.*;
{
Contract.Requires(s != null);
Contract.EnsuresOnThrow(true);
cce.BeginExpose(this);
toSimplify.Write("(BG_PUSH ");
toSimplify.Write(s);
toSimplify.WriteLine(")");
cce.EndExpose();
}
public virtual void Feed(string s, int statementCount)
//modifies this.*;
//modifies Console.Out.*, Console.Error.*;
{
Contract.Requires(s != null);
Contract.EnsuresOnThrow(true);
cce.BeginExpose(this);
{
toSimplify.Write(s);
}
cce.EndExpose();
}
public virtual void PopAxioms()
//modifies this.*;
//modifies Console.Out.*, Console.Error.*;
{
Contract.EnsuresOnThrow(true);
cce.BeginExpose(this);
{
toSimplify.WriteLine("(BG_POP)");
}
cce.EndExpose();
}
public void ToFlush()
//modifies this.*;
{
cce.BeginExpose(this);
{
toSimplify.Flush();
}
cce.EndExpose();
}
public enum ProverOutcome {
Valid,
NotValid,
TimeOut,
OutOfMemory,
Inconclusive
}
///
/// Passes the formula to Simplify.
///
public void BeginCheck(string descriptiveName, string formula)
//modifies this.*;
//modifies Console.Out.*, Console.Error.*;
{
Contract.Requires(descriptiveName != null);
Contract.Requires(formula != null);
DoBeginCheck(descriptiveName, formula);
nFormulasChecked++;
}
///
/// Reports the outcome of formula checking. If the outcome is Invalid,
/// then the "handler" is invoked with each counterexample.
///
public abstract ProverOutcome CheckOutcome(Microsoft.Boogie.ProverInterface.ErrorHandler handler);
//modifies this.**;
//modifies Console.Out.*, Console.Error.*;
//modifies handler.*;
//TODO: Contract.EnsuresOnThrow(true);
protected abstract void DoBeginCheck(string descriptiveName, string formula);
//modifies this.*;
//modifies Console.Out.*, Console.Error.*;
///
/// Returns an array of the labels in "labels", with "|" brackets (if any)
/// stripped off.
/// Assumes that every label begins with "|+" or "|@", or just "+" or "@",
/// and ends with "|" if it started with one, and that these "|" brackets are
/// the only "|"s in "labels".
///
protected static List!*/> ParseLabels(string labels) {
Contract.Requires(labels != null);
Contract.Ensures(Contract.Result>() != null);
List list = new List();
int j = 0;
while (true)
// invariant: j is the number of characters of "labels" consumed so far
// invariant: an even number of '|' characters remain in "labels"
{
cce.LoopInvariant(0 <= j && j <= labels.Length);
j = labels.IndexOfAny(new char[] { '|', '+', '@' }, j);
if (j < 0) {
// no more labels
return list;
}
char ch = labels[j];
if (ch == '|') {
j++; // skip the '|'
Contract.Assume(j < labels.Length); // there should now be a '+' or '@'
ch = labels[j];
}
Contract.Assume(ch == '+' || ch == '@');
j++; // skip the '+' or '@'
int k = labels.IndexOfAny(new char[] { '|', ' ', ')' }, j);
Contract.Assume(j + 2 <= k);
string s = labels.Substring(j, k - j);
list.Add(s);
j = k + 1;
}
}
[Rep]
char[] expectBuffer = null;
///
/// Expects s[0]==ch and the next s.Length-1 characters of the input to be s[1,..]
/// If not, more characters may be read from "fromSimplify" to provide additional context
/// for the UnexpectedProverOutputException exception that will be thrown.
///
protected void Expect(int ch, string s)
//modifies this.*;
{
Contract.Requires(s != null);
Contract.Requires(1 <= s.Length);
Contract.EnsuresOnThrow(true);
if (ch == -1) {
// a return of -1 from FromReadChar means that there is no StdOutput
// to treat this we can return the error message we get from Z3 on StdError and then
// declare this case to be inconclusive
string str = FromStdErrorAll();
if (str == "") {
throw new ProverDiedException();
} else {
throw new UnexpectedProverOutputException("Expected \"" + s + "\", found:\r\n<<>>\r\n" + str + "<<>>");
}
}
string badInputPrefix;
if (ch != s[0]) {
badInputPrefix = Char.ToString((char)ch);
} else {
int len = s.Length - 1;
if (expectBuffer == null || expectBuffer.Length < len) {
cce.BeginExpose(this);
{
expectBuffer = new char[len];
}
cce.EndExpose();
}
try {
string s0;
cce.BeginExpose(this);
{
fromSimplify.ReadBlock(expectBuffer, 0, len);
s0 = new string(expectBuffer, 0, len);
}
cce.EndExpose();
string s1 = s.Substring(1, len);
if (s0.CompareTo(s1) == 0) {
badInputPrefix = null; // no error
} else {
badInputPrefix = (char)ch + s0;
}
} catch (IOException) {
throw new UnexpectedProverOutputException("Expected \"" + s + "\", encountered IO exception.");
}
}
if (badInputPrefix != null) {
// Read the rest of the available input, without blocking!
// Despite the confusing documentation for the Read method, it seems
// that Read does block. It if didn't, I would have written:
// string remaining = "";
// char[] buf = new char[1024];
// while (true) {
// int len = fromSimplify.Read(buf, 0, buf.Length);
// remaining += new String(buf, 0, len);
// if (len != buf.Length) {
// break;
// }
// }
// But instead, I'll just hope that one line of input is available and read
// it.
string remaining = fromSimplify.ReadLine() + "\r\n";
throw new UnexpectedProverOutputException("Expected \"" + s + "\", found:\r\n<<>>\r\n" + badInputPrefix + remaining + "<<>>");
}
}
protected int FromReadChar()
//modifies this.*;
{
cce.BeginExpose(this);
{
return fromSimplify.Read();
}
cce.EndExpose();
}
private void KillProver(object state) {
cce.BeginExpose(this);
{
this.readTimedOut = true;
simplify.Kill();
}
cce.EndExpose();
}
protected int FromReadChar(int timeout)
//modifies this.*;
{
Contract.Requires(-1 <= timeout);
cce.BeginExpose(this);
{
this.readTimedOut = false;
System.Threading.Timer t = new System.Threading.Timer(this.KillProver, null, timeout, System.Threading.Timeout.Infinite);
int ch = fromSimplify.Read();
t.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
t.Dispose();
return ch;
}
cce.EndExpose();
}
protected string FromReadLine()
//modifies this.*;
{
Contract.Ensures(Contract.Result() != null);
cce.BeginExpose(this);
{
string s = fromSimplify.ReadLine();
if (s == null) {
// this is what ReadLine returns if all characters have been read
s = "";
}
return s;
}
cce.EndExpose();
}
protected string FromStdErrorAll()
//modifies this.*;
{
Contract.Ensures(Contract.Result() != null);
cce.BeginExpose(this);
{
if (fromStdError != null) {
string s = fromStdError.ReadToEnd();
if (s == null) {
// this is what ReadLine returns if all characters have been read
s = "";
}
return s;
}
// there is no StdErrorReader available
else {
return "";
}
}
cce.EndExpose();
}
protected void ToWriteLine(string s)
//modifies this.*;
{
Contract.Requires(s != null);
cce.BeginExpose(this);
{
toSimplify.WriteLine(s);
}
cce.EndExpose();
}
}
// derived by Z3ProverProcess
public class SimplifyProverProcess : ProverProcess {
public SimplifyProverProcess(string proverPath, bool dummy) :base(getPSI(proverPath),proverPath) {
Contract.Requires(proverPath != null);
// throws ProverException
}
private static ProcessStartInfo getPSI(string proverPath){ProcessStartInfo psi = new ProcessStartInfo(proverPath, "-labelsonly");
psi.CreateNoWindow = true;
psi.UseShellExecute = false;
psi.RedirectStandardInput = true;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
Contract.Assume(psi.EnvironmentVariables != null); // by inspecting the code through Reflector; the documentation says this property is "null by default", whatever that means --KRML
if (0 <= CommandLineOptions.Clo.ProverKillTime) {
psi.EnvironmentVariables["PROVER_KILL_TIME"] = CommandLineOptions.Clo.ProverKillTime.ToString();
}
if (0 <= CommandLineOptions.Clo.SimplifyProverMatchDepth) {
psi.EnvironmentVariables["PROVER_MATCH_DEPTH"] = CommandLineOptions.Clo.SimplifyProverMatchDepth.ToString();
}
if (0 <= CommandLineOptions.Clo.ProverCCLimit) {
psi.EnvironmentVariables["PROVER_CC_LIMIT"] = CommandLineOptions.Clo.ProverCCLimit.ToString();
}
return psi;
}
public override string OptionComments() {
Contract.Ensures(Contract.Result() != null);
// would we want the timeout stuff here?
return "";
}
[NotDelayed]
// TODO it complains about things not beeing peer consistent upon call to EatPrompt()
// not sure what is it about... --micmo
[Verify(false)]
public SimplifyProverProcess(string proverPath):base(getPSI(proverPath),proverPath)
//modifies Console.Out.*, Console.Error.*;
{
Contract.Requires(proverPath != null);
Contract.EnsuresOnThrow(true);
EatPrompt();
}
private void EatPrompt()
//modifies this.*;
//modifies Console.Out.*, Console.Error.*;
{
Contract.EnsuresOnThrow(true);
// skips text matching the regular expression: (white space)* ">\t"
ToFlush();
int ch = 0;
do {
ch = FromReadChar();
} while (Char.IsWhiteSpace((char)ch));
while (ch == 'W') {
ch = ConsumeWarnings(ch, null);
}
Expect(ch, ">\t");
}
public override void AddAxioms(string s) {
Contract.Requires(s != null);
Contract.EnsuresOnThrow(true);
//ToWriteLine("(PROMPT_OFF)");
base.AddAxioms(s);
//ToWriteLine("(PROMPT_ON)");
EatPrompt();
}
public override void Feed(string s, int statementCount) {
Contract.Requires(s != null);
Contract.EnsuresOnThrow(true);
//ToWriteLine("(PROMPT_OFF)");
base.Feed(s, statementCount);
//ToWriteLine("(PROMPT_ON)");
for (int i = 0; i < statementCount; i++) {
EatPrompt();
}
}
public override void PopAxioms() {
Contract.EnsuresOnThrow(true);
base.PopAxioms();
EatPrompt();
}
protected override void DoBeginCheck(string descriptiveName, string formula) {
Contract.Requires(descriptiveName != null);
Contract.Requires(formula != null);
//simplify.Refresh();
//this.Comment("@@@@ Virtual Memory: " + simplify.PeakVirtualMemorySize64);
//this.Comment("@@@@ Working Set: " + simplify.PeakWorkingSet64);
//this.Comment("@@@@ Paged Memory: " + simplify.PeakPagedMemorySize64);
ToWriteLine(formula);
ToFlush();
}
public override ProverOutcome CheckOutcome(Microsoft.Boogie.ProverInterface.ErrorHandler handler) {
Contract.Requires(handler != null);
Contract.EnsuresOnThrow(true);
ProverOutcome outcome;
if (this.simplify == null) {
return ProverOutcome.Inconclusive;
}
int ch = FromReadChar();
while (ch == 'W') {
ch = ConsumeWarnings(ch, handler);
}
if (ch == 'E') {
Expect(ch, "Exceeded PROVER_KILL_TIME -- discontinuing search for counterexamples.");
FromReadLine();
ch = FromReadChar();
if (ch == '\n') {
ch = FromReadChar();
}
Expect(ch, " labels:");
FromReadLine();
ch = FromReadChar();
ch = FromReadChar();
ch = FromReadChar();
FromReadLine();
ch = FromReadChar();
ch = FromReadChar();
ch = FromReadChar();
return ProverOutcome.TimeOut;
}
if ('0' <= ch && ch <= '9') {
// Valid!
do {
ch = FromReadChar();
} while ('0' <= ch && ch <= '9');
Expect(ch, ": Valid.");
outcome = ProverOutcome.Valid;
ToWriteLine(String.Format("; FORMULA {0} IS VALID!", NumFormulasChecked + 1 /*Simplify starts at 1*/));
} else {
// now we expect one or more counterexample contexts, each proving a list of labels
do {
List labels = ReadLabels(ch);
handler.OnModel(labels, null);
ch = FromReadChar();
} while (ch == 'C');
// now we expect ": Invalid" where is some number
while ('0' <= ch && ch <= '9') {
ch = FromReadChar();
}
Expect(ch, ": Invalid.");
outcome = ProverOutcome.NotValid;
ToWriteLine(String.Format("; FORMULA {0} IS INVALID", NumFormulasChecked + 1 /*Simplify starts at 1*/));
}
EatPrompt();
return outcome;
}
List!*/> ReadLabels(int ch)
//modifies this.*;
{
Contract.Ensures(Contract.Result>() != null);
Contract.EnsuresOnThrow(true);
Expect(ch, "Counterexample:\n"); // FIX! ? Is there a problem with \r\n here?
ch = FromReadChar();
List theLabels;
if (ch == ' ') {
// there are labels
Expect(ch, " labels: ");
string labels = FromReadLine(); // reads "(A B C ...)\n"
theLabels = ParseLabels(labels);
ch = FromReadChar();
} else {
theLabels = new List();
}
Expect(ch, "\n"); // empty line
return theLabels;
}
int ConsumeWarnings(int ch, Microsoft.Boogie.ProverInterface.ErrorHandler handler)
//modifies this.*;
//modifies Console.Out.*, Console.Error.*;
{
Contract.Requires(ch == 'W');
Contract.EnsuresOnThrow(true);
Expect(ch, "Warning: ");
string w = FromReadLine();
if (w.StartsWith("triggerless quantifier body")) {
FromReadLine(); // blank line
w = "triggerless quantifier body: " + FromReadLine(); // expression (body)
FromReadLine(); // blank line
FromReadLine(); // "with X pattern variable(s)...
FromReadLine(); // blank line
FromReadLine(); // expression (entire quantifier)
}
if (handler != null)
handler.OnProverWarning(w);
ch = FromReadChar();
if (ch == '\n') {
// make up for a poorly designed ReadLine routine (only the first
// character of the DOS end-of-line sequence "\r\n" is read)
ch = FromReadChar();
}
return ch;
}
}
}