Verifier.java
package dev.civl.mc.run.IF;
import static dev.civl.mc.config.IF.CIVLConstants.collectOutputO;
import static dev.civl.mc.config.IF.CIVLConstants.maxdepthO;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import dev.civl.mc.config.IF.CIVLConstants;
import dev.civl.mc.log.IF.CIVLExecutionException;
import dev.civl.mc.log.IF.CIVLLogEntry;
import dev.civl.mc.model.IF.CIVLException.Certainty;
import dev.civl.mc.model.IF.CIVLInternalException;
import dev.civl.mc.model.IF.CIVLProperty;
import dev.civl.mc.model.IF.CIVLSource;
import dev.civl.mc.model.IF.Model;
import dev.civl.mc.predicate.IF.Predicates;
import dev.civl.mc.run.common.VerificationStatus;
import dev.civl.mc.semantics.IF.Transition;
import dev.civl.mc.state.IF.CIVLStateException;
import dev.civl.mc.state.IF.State;
import dev.civl.mc.util.IF.Pair;
import dev.civl.mc.util.IF.Printable;
import dev.civl.gmc.CommandLineException;
import dev.civl.gmc.ExcessiveErrorException;
import dev.civl.gmc.GMCConfiguration;
import dev.civl.gmc.StateSpaceCycleException;
import dev.civl.gmc.seq.DfsSearcher;
import dev.civl.sarl.IF.expr.BooleanExpression;
import dev.civl.sarl.IF.expr.SymbolicExpression;
public class Verifier extends Player {
private String errorBoundExceeds;
VerificationStatus verificationStatus;
class SearchUpdater implements Printable {
@Override
public void print(PrintStream out) {
long time = (long) Math
.ceil((System.currentTimeMillis() - startTime) / 1000.0);
long megabytes = (long) (((double) Runtime.getRuntime()
.totalMemory()) / (double) 1048576.0);
out.print(time + "s: ");
out.print("mem=" + megabytes + "Mb");
out.print(" trans=" + executor.getNumSteps());
out.print(" traceSteps=" + searcher.numTransitions());
out.print(" explored=" + stateManager.numStatesExplored());
out.print(" saved=" + searcher.numOfSearchNodeSaved());
out.print(
" prove=" + modelFactory.universe().numProverValidCalls());
out.println();
}
}
/**
* Updater for the web app. Instead of printing to the given stream, it
* ignores that stream and instead creates a new file and prints the data to
* that file.
*
* @author siegel
*
*/
class WebUpdater implements Printable {
/**
* String used to form the name of the file that will be created. This
* string will have the time step appended to it to create a unique
* file.
*/
private String filenameRoot;
/**
* The directory in which the new file will be created.
*/
private File directory;
/**
* The number of times {@link #print(PrintStream)} has been called on
* this updater.
*/
private int timeStep = 0;
public WebUpdater(File directory, String filenameRoot) {
this.directory = directory;
this.filenameRoot = filenameRoot;
}
private void fail(File file, String msg) {
throw new CIVLInternalException(msg + " " + file.getAbsolutePath(),
(CIVLSource) null);
}
private void print(boolean isFinal) {
double time = Math.ceil(
(System.currentTimeMillis() - startTime) / 100.0) / 10.0;
long megabytes = (long) (((double) Runtime.getRuntime()
.totalMemory()) / (double) 1048576.0);
File file;
timeStep++;
file = new File(directory, filenameRoot + "_" + timeStep + ".txt");
try {
if (file.exists())
file.delete();
file.createNewFile();
FileOutputStream stream = new FileOutputStream(file);
FileChannel channel = stream.getChannel();
FileLock lock = channel.lock();
PrintStream out = new PrintStream(stream);
out.println("{");
out.println("\"time\" : " + time + " ,");
out.println("\"mem\" : " + megabytes + " ,");
out.println("\"steps\" : " + searcher.numTransitions() + " ,");
out.println("\"trans\" : " + executor.getNumSteps() + " ,");
out.println("\"seen\" : " + searcher.numStatesSeen() + " ,");
out.println("\"saved\" : " + searcher.numOfSearchNodeSaved()
+ " ,");
out.println("\"prove\" : "
+ modelFactory.universe().numProverValidCalls() + " ,");
out.println("\"counterexample\" : "
+ log.getMinimalCounterexampleSize() + ",");
out.println("\"isFinal\" : " + isFinal);
out.println("}");
out.flush();
lock.release();
out.close();
} catch (IOException e) {
fail(file, "Could not write to file");
}
}
@Override
public void print(PrintStream out) {
print(false);
}
public void printFinal() {
print(true);
}
}
/**
* Runnable to be used to create a thread that every so many seconds tells
* the state manager to print an update message.
*
* @author siegel
*
*/
class UpdaterRunnable implements Runnable {
/**
* Number of milliseconds between sending update message to state
* manager.
*/
private long millis;
/**
* Constructs new runnable with given number of milliseconds.
*
* @param millis
* number of milliseconds between update messages
*/
public UpdaterRunnable(long millis) {
this.millis = millis;
}
/**
* Runs this thread. The thread will loop forever until interrupted,
* then it will terminate.
*/
@Override
public void run() {
while (alive) {
try {
Thread.sleep(millis);
stateManager.printUpdate();
} catch (InterruptedException e) {
}
}
}
}
/**
* Should the update thread run?
*/
private volatile boolean alive = true;
/**
* Number of seconds between printing updates.
*/
private int updatePeriod;
/**
* The object used to print the update message.
*/
private Printable updater;
/**
* The update thread itself.
*/
private Thread updateThread = null;
/**
* The object used to perform the depth-first search of the state space.
*
*/
private DfsSearcher<State, Transition> searcher;
// private boolean shortFileNamesShown;
/**
* The time at which execution started, as a double.
*/
private double startTime;
public Verifier(GMCConfiguration config, Model model, PrintStream out,
PrintStream err, double startTime, boolean collectOutputs,
String[] outputNames,
Map<BooleanExpression, Set<Pair<State, SymbolicExpression[]>>> specOutputs)
throws CommandLineException {
super(config, model, out, err, collectOutputs);
if (random) {
throw new CommandLineException(
"\"-random\" mode is incompatible with civl verify command.");
}
this.startTime = startTime;
if (outputNames != null && specOutputs != null)
this.addPredicate(
Predicates.newFunctionalEquivalence(modelFactory.universe(),
symbolicAnalyzer, outputNames, specOutputs));
searcher = new DfsSearcher<State, Transition>(enabler, stateManager,
predicate, config);
if (civlConfig.debug())
searcher.setDebugOut(out);
searcher.setReportCycleAsViolation(
civlConfig.isPropertyToggled(CIVLProperty.TERMINATION));
searcher.setFairCycleCheck(civlConfig.isFair());
searcher.setName(sessionName);
log.setSearcher(searcher);
if (minimize)
log.setMinimize(true);
if (config.getAnonymousSection().getValue(maxdepthO) != null)
searcher.boundDepth(maxdepth);
if (civlConfig.preemptionBound() >= 0) {
searcher.setPreemptionBound(civlConfig.preemptionBound());
}
if (config.getAnonymousSection().isTrue(CIVLConstants.webO)) {
updatePeriod = CIVLConstants.webUpdatePeriod;
updater = new WebUpdater(new File(CIVLConstants.CIVLREP), "update");
} else {
updatePeriod = CIVLConstants.consoleUpdatePeriod;
updater = new SearchUpdater();
}
stateManager.setUpdater(updater);
this.errorBoundExceeds = "Terminating search after finding "
+ this.log.errorBound() + " violation";
if (log.errorBound() > 1)
errorBoundExceeds += "s";
errorBoundExceeds += ".";
}
public Verifier(GMCConfiguration config, Model model, PrintStream out,
PrintStream err, double startTime, boolean collectOutputs)
throws CommandLineException {
this(config, model, out, err, startTime, collectOutputs, null, null);
}
public Verifier(GMCConfiguration config, Model model, PrintStream out,
PrintStream err, double startTime, String[] outputNames,
Map<BooleanExpression, Set<Pair<State, SymbolicExpression[]>>> specOutputs)
throws CommandLineException {
this(config, model, out, err, startTime, false, outputNames,
specOutputs);
}
public Verifier(GMCConfiguration config, Model model, PrintStream out,
PrintStream err, double startTime) throws CommandLineException {
this(config, model, out, err, startTime,
config.getAnonymousSection().isTrue(collectOutputO), null,
null);
}
/**
* Prints only those metrics specific to this Verifier. General metrics,
* including time, memory, symbolic expressions, etc., are dealt with in the
* general UserInterface class.
*/
public List<Pair<String, String>> getStats() {
List<Pair<String, String>> stats = new LinkedList<Pair<String, String>>();
stats.add(new Pair<String, String>("max process count",
Integer.toString(stateManager.maxProcs())));
stats.add(new Pair<String, String>("states",
Integer.toString(stateManager.numStatesExplored())));
stats.add(new Pair<String, String>("states saved",
Integer.toString(searcher.numOfSearchNodeSaved())));
stats.add(new Pair<String, String>("state matches",
Integer.toString(searcher.numStatesMatched())));
stats.add(new Pair<String, String>("transitions",
Long.toString(executor.getNumSteps())));
stats.add(new Pair<String, String>("trace steps",
Integer.toString(searcher.numTransitions())));
return stats;
}
public boolean run() throws FileNotFoundException, InterruptedException,
ExecutionException {
final int timeout = this.civlConfig.timeout();
if (timeout > 0) {
final ScheduledExecutorService verifier_scheduler = Executors
.newScheduledThreadPool(2);
final Callable<Boolean> verify_run_work = new Callable<Boolean>() {
public Boolean call() {
try {
return run_work();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return false;
}
};
final ScheduledFuture<?> verify_handler = verifier_scheduler
.schedule(verify_run_work, 0, TimeUnit.MILLISECONDS);
final Runnable timeOutWork = new Runnable() {
public void run() {
verify_handler.cancel(true);
if (result == null) {
result = "Time out.";
verificationStatus = new VerificationStatus(
stateManager.maxProcs(),
stateManager.numStatesExplored(),
searcher.numOfSearchNodeSaved(),
searcher.numStatesMatched(),
executor.getNumSteps(),
searcher.numTransitions());
}
return;
}
};
verifier_scheduler.schedule(timeOutWork, timeout, TimeUnit.SECONDS);
Object tmp = verify_handler.get();
verifier_scheduler.shutdownNow();
if (tmp != null)
return (boolean) tmp;
return false;
} else {
return run_work();
}
}
private boolean isFairCycle(StateSpaceCycleException e) {
return true;
}
public boolean run_work() throws FileNotFoundException {
try {
State initialState = stateFactory.initialState(model);
boolean violationFound = false;
updateThread = new Thread(new UpdaterRunnable(updatePeriod * 1000));
if (civlConfig.runtimeUpdate())
updateThread.start();
if (civlConfig.debugOrVerbose() || civlConfig.showStates()
|| civlConfig.showSavedStates()) {
civlConfig.out().println();
civlConfig.out().print(
symbolicAnalyzer.stateToString(initialState, 0, -1));
civlConfig.out().println();
}
try {
while (true) {
boolean workRemains;
try {
if (violationFound) {
// may throw ExcessiveErrorException...
workRemains = searcher.proceedToNewState()
? searcher.search()
: false;
} else {
// may throw ExcessiveErrorException...
workRemains = searcher.search(initialState);
}
if (!workRemains)
break;
} catch (StateSpaceCycleException e) {
if (civlConfig
.isPropertyToggled(CIVLProperty.TERMINATION)
&& (!civlConfig.isFair() || isFairCycle(e))) {
// a cycle in state space detected:
int stackPos = e.stackPos();
int stackSize = searcher.stack().size();
searcher.stack().get(stackPos).getState()
.print(System.out);
Transition lastTran = (stackPos < stackSize - 1)
? searcher.stack().get(stackSize - 2).peek()
: searcher.stack().peek().peek();
State lastState = searcher.stack().peek()
.getState();
int pid = lastTran.pid();
String process = lastState.getProcessState(pid)
.name();
CIVLSource source = lastTran.statement()
.getSource();
CIVLExecutionException cycleException = new CIVLExecutionException(
CIVLProperty.TERMINATION,
Certainty.CONCRETE, process,
"Found cycle in state space from step "
+ e.stackPos() + " to " + stackSize
+ ". This execution will not terminate.",
lastState, pid, source);
CIVLLogEntry entry = new CIVLLogEntry(civlConfig,
config, cycleException,
evaluator.universe());
log.report(entry); // may throw
// ExcessiveErrorException
}
violationFound = true;
continue; // cycle violation logged, continue the search
}
violationFound = true;
CIVLLogEntry entry = new CIVLLogEntry(civlConfig, config,
this.predicate.getUnreportedViolation(),
evaluator.universe());
log.report(entry); // may throw ExcessiveErrorException
}
} catch (ExcessiveErrorException e) {
violationFound = true;
if (!civlConfig.isQuiet()) {
civlConfig.out().println(errorBoundExceeds);
}
}
terminateUpdater();
if (violationFound || log.numEntries() > 0) {
result = "The program MAY NOT be correct. See "
+ log.getLogFile();
try {
log.save();
} catch (FileNotFoundException e) {
System.err.println(
"Failed to print log file " + log.getLogFile());
}
} else {
result = "All errors marked with '+' are absent on all executions.\n";
result += civlConfig.getCheckedPropertiesSummary();
}
this.verificationStatus = new VerificationStatus(
stateManager.maxProcs(), stateManager.numStatesExplored(),
searcher.numOfSearchNodeSaved(),
searcher.numStatesMatched(), executor.getNumSteps(),
searcher.numTransitions());
return !violationFound && log.numEntries() == 0;
} catch (CIVLStateException stateException) {
throw new CIVLExecutionException(stateException.civlProperty(),
stateException.certainty(), stateException.getMessage(),
stateException.state(), stateException.source());
}
}
/**
* Terminates the update thread. This will be called automatically if
* control exits normally from {@link #run_work()}, but if an exception is
* thrown and caught elsewhere, this method should be called.
*/
public void terminateUpdater() {
alive = false;
if (updateThread != null)
updateThread.interrupt();
updateThread = null;
if (civlConfig.web()) {
// last update with final stats needed for web page...
((WebUpdater) updater).printFinal();
}
}
}