hu.bme.mit.theta.cfa.cli.CfaCli Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of theta-cfa-cli Show documentation
Show all versions of theta-cfa-cli Show documentation
Cfa Cli subproject in the Theta model checking framework
/*
* Copyright 2024 Budapest University of Technology and Economics
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package hu.bme.mit.theta.cfa.cli;
import static com.google.common.base.Preconditions.checkNotNull;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.google.common.base.Stopwatch;
import hu.bme.mit.theta.analysis.Trace;
import hu.bme.mit.theta.analysis.algorithm.SafetyResult;
import hu.bme.mit.theta.analysis.algorithm.arg.ARG;
import hu.bme.mit.theta.analysis.algorithm.bounded.BoundedChecker;
import hu.bme.mit.theta.analysis.algorithm.bounded.BoundedCheckerBuilderKt;
import hu.bme.mit.theta.analysis.algorithm.bounded.MonolithicExpr;
import hu.bme.mit.theta.analysis.algorithm.cegar.CegarStatistics;
import hu.bme.mit.theta.analysis.expl.ExplState;
import hu.bme.mit.theta.analysis.expr.refinement.PruneStrategy;
import hu.bme.mit.theta.cfa.CFA;
import hu.bme.mit.theta.cfa.analysis.CfaAction;
import hu.bme.mit.theta.cfa.analysis.CfaState;
import hu.bme.mit.theta.cfa.analysis.CfaToMonolithicExprKt;
import hu.bme.mit.theta.cfa.analysis.CfaTraceConcretizer;
import hu.bme.mit.theta.cfa.analysis.config.CfaConfig;
import hu.bme.mit.theta.cfa.analysis.config.CfaConfigBuilder;
import hu.bme.mit.theta.cfa.analysis.config.CfaConfigBuilder.*;
import hu.bme.mit.theta.cfa.analysis.utils.CfaVisualizer;
import hu.bme.mit.theta.cfa.dsl.CfaDslManager;
import hu.bme.mit.theta.common.CliUtils;
import hu.bme.mit.theta.common.OsHelper;
import hu.bme.mit.theta.common.logging.ConsoleLogger;
import hu.bme.mit.theta.common.logging.Logger;
import hu.bme.mit.theta.common.logging.Logger.Level;
import hu.bme.mit.theta.common.logging.NullLogger;
import hu.bme.mit.theta.common.table.BasicTableWriter;
import hu.bme.mit.theta.common.table.TableWriter;
import hu.bme.mit.theta.common.visualization.Graph;
import hu.bme.mit.theta.common.visualization.writer.GraphvizWriter;
import hu.bme.mit.theta.solver.SolverFactory;
import hu.bme.mit.theta.solver.SolverManager;
import hu.bme.mit.theta.solver.smtlib.SmtLibSolverManager;
import hu.bme.mit.theta.solver.z3legacy.Z3LegacySolverFactory;
import hu.bme.mit.theta.solver.z3legacy.Z3SolverManager;
import java.io.*;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
/** A command line interface for running a CEGAR configuration on a CFA. */
public class CfaCli {
private static final String JAR_NAME = "theta-cfa-cli.jar";
private final String[] args;
private final TableWriter writer;
@Parameter(
names = {"--algorithm"},
description = "Algorithm")
Algorithm algorithm = Algorithm.CEGAR;
@Parameter(names = "--domain", description = "Abstract domain")
Domain domain = Domain.PRED_CART;
@Parameter(names = "--refinement", description = "Refinement strategy")
Refinement refinement = Refinement.SEQ_ITP;
@Parameter(names = "--search", description = "Search strategy")
Search search = Search.BFS;
@Parameter(
names = "--predsplit",
description = "Predicate splitting (for predicate abstraction)")
PredSplit predSplit = PredSplit.WHOLE;
@Parameter(
names = "--solver",
description =
"Sets the underlying SMT solver to use for both the abstraction and the"
+ " refinement process. Enter in format :, see"
+ " theta-smtlib-cli.jar for more details. Enter \"Z3\" to use the legacy"
+ " z3 solver.")
String solver = "Z3";
@Parameter(
names = "--abstraction-solver",
description =
"Sets the underlying SMT solver to use for the abstraction process. Enter in"
+ " format :, see theta-smtlib-cli.jar for"
+ " more details. Enter \"Z3\" to use the legacy z3 solver.")
String abstractionSolver;
@Parameter(
names = "--refinement-solver",
description =
"Sets the underlying SMT solver to use for the refinement process. Enter in"
+ " format :, see theta-smtlib-cli.jar for"
+ " more details. Enter \"Z3\" to use the legacy z3 solver.")
String refinementSolver;
@Parameter(names = "--home", description = "The path of the solver registry")
String home = SmtLibSolverManager.HOME.toAbsolutePath().toString();
@Parameter(names = "--model", description = "Path of the input CFA model", required = true)
String model;
@Parameter(names = "--errorloc", description = "Error (target) location")
String errorLoc = "";
@Parameter(names = "--precgranularity", description = "Precision granularity")
PrecGranularity precGranularity = PrecGranularity.GLOBAL;
@Parameter(names = "--encoding", description = "Block encoding")
Encoding encoding = Encoding.LBE;
@Parameter(
names = "--maxenum",
description = "Maximal number of explicitly enumerated successors (0: unlimited)")
Integer maxEnum = 10;
@Parameter(names = "--initprec", description = "Initial precision of abstraction")
InitPrec initPrec = InitPrec.EMPTY;
@Parameter(
names = "--prunestrategy",
description = "Strategy for pruning the ARG after refinement")
PruneStrategy pruneStrategy = PruneStrategy.LAZY;
@Parameter(names = "--loglevel", description = "Detailedness of logging")
Logger.Level logLevel = Level.SUBSTEP;
@Parameter(names = "--benchmark", description = "Benchmark mode (only print metrics)")
Boolean benchmarkMode = false;
@Parameter(names = "--cex", description = "Write concrete counterexample to a file")
String cexfile = null;
@Parameter(
names = "--header",
description = "Print only a header (for benchmarks)",
help = true)
boolean headerOnly = false;
@Parameter(
names = "--visualize",
description = "Visualize CFA to this file without running the algorithm")
String visualize = null;
@Parameter(
names = "--metrics",
description = "Print metrics about the CFA without running the algorithm")
boolean metrics = false;
@Parameter(names = "--stacktrace", description = "Print full stack trace in case of exception")
boolean stacktrace = false;
@Parameter(names = "--version", description = "Display version", help = true)
boolean versionInfo = false;
private Logger logger;
public CfaCli(final String[] args) {
this.args = args;
writer = new BasicTableWriter(System.out, ",", "\"", "\"");
}
public static void main(final String[] args) {
final CfaCli mainApp = new CfaCli(args);
mainApp.run();
}
private void run() {
try {
JCommander.newBuilder().addObject(this).programName(JAR_NAME).build().parse(args);
logger = benchmarkMode ? NullLogger.getInstance() : new ConsoleLogger(logLevel);
} catch (final ParameterException ex) {
System.out.println("Invalid parameters, details:");
System.out.println(ex.getMessage());
ex.usage();
return;
}
if (headerOnly) {
printHeader();
return;
}
if (versionInfo) {
CliUtils.printVersion(System.out);
return;
}
try {
SolverManager.registerSolverManager(Z3SolverManager.create());
if (OsHelper.getOs().equals(OsHelper.OperatingSystem.LINUX)) {
final var homePath = Path.of(home);
final var smtLibSolverManager = SmtLibSolverManager.create(homePath, logger);
SolverManager.registerSolverManager(smtLibSolverManager);
}
final Stopwatch sw = Stopwatch.createStarted();
final CFA cfa = loadModel();
if (visualize != null) {
final Graph graph = CfaVisualizer.visualize(cfa);
GraphvizWriter.getInstance().writeFileAutoConvert(graph, visualize);
return;
}
if (metrics) {
CfaMetrics.printMetrics(logger, cfa);
return;
}
CFA.Loc errLoc = null;
if (cfa.getErrorLoc().isPresent()) {
errLoc = cfa.getErrorLoc().get();
}
if (!errorLoc.isEmpty()) {
errLoc = null;
for (CFA.Loc running : cfa.getLocs()) {
if (running.getName().equals(errorLoc)) {
errLoc = running;
}
}
checkNotNull(errLoc, "Location '" + errorLoc + "' not found in CFA");
}
checkNotNull(errLoc, "Error location must be specified in CFA or as argument");
final SolverFactory abstractionSolverFactory;
if (abstractionSolver != null) {
abstractionSolverFactory = SolverManager.resolveSolverFactory(abstractionSolver);
} else {
abstractionSolverFactory = SolverManager.resolveSolverFactory(solver);
}
final SolverFactory refinementSolverFactory;
if (refinementSolver != null) {
refinementSolverFactory = SolverManager.resolveSolverFactory(refinementSolver);
} else {
refinementSolverFactory = SolverManager.resolveSolverFactory(solver);
}
final SafetyResult, ? extends Trace, ?>> status;
if (algorithm == Algorithm.CEGAR) {
final CfaConfig, ?, ?> configuration =
buildConfiguration(
cfa, errLoc, abstractionSolverFactory, refinementSolverFactory);
status = check(configuration);
} else if (algorithm == Algorithm.BMC
|| algorithm == Algorithm.KINDUCTION
|| algorithm == Algorithm.IMC) {
final BoundedChecker, ?> checker =
buildBoundedChecker(cfa, abstractionSolverFactory);
status = checker.check(null);
} else {
throw new UnsupportedOperationException(
"Algorithm " + algorithm + " not supported");
}
sw.stop();
printResult(status, sw.elapsed(TimeUnit.MILLISECONDS));
if (status.isUnsafe() && cexfile != null) {
writeCex(status.asUnsafe());
}
} catch (final Throwable ex) {
printError(ex);
System.exit(1);
}
}
private void printHeader() {
Stream.of(
"Result",
"TimeMs",
"AlgoTimeMs",
"AbsTimeMs",
"RefTimeMs",
"Iterations",
"ArgSize",
"ArgDepth",
"ArgMeanBranchFactor",
"CexLen")
.forEach(writer::cell);
writer.newRow();
}
private CFA loadModel() throws Exception {
try (InputStream inputStream = new FileInputStream(model)) {
try {
return CfaDslManager.createCfa(inputStream);
} catch (final Exception ex) {
throw new Exception("Could not parse CFA: " + ex.getMessage(), ex);
}
}
}
private CfaConfig, ?, ?> buildConfiguration(
final CFA cfa,
final CFA.Loc errLoc,
final SolverFactory abstractionSolverFactory,
final SolverFactory refinementSolverFactory)
throws Exception {
try {
return new CfaConfigBuilder(
domain, refinement, abstractionSolverFactory, refinementSolverFactory)
.precGranularity(precGranularity)
.search(search)
.predSplit(predSplit)
.encoding(encoding)
.maxEnum(maxEnum)
.initPrec(initPrec)
.pruneStrategy(pruneStrategy)
.logger(logger)
.build(cfa, errLoc);
} catch (final Exception ex) {
throw new Exception("Could not create configuration: " + ex.getMessage(), ex);
}
}
private BoundedChecker, ?> buildBoundedChecker(
final CFA cfa, final SolverFactory abstractionSolverFactory) {
final MonolithicExpr monolithicExpr = CfaToMonolithicExprKt.toMonolithicExpr(cfa);
final BoundedChecker, ?> checker;
switch (algorithm) {
case BMC ->
checker =
BoundedCheckerBuilderKt.buildBMC(
monolithicExpr,
abstractionSolverFactory.createSolver(),
val -> CfaToMonolithicExprKt.valToState(cfa, val),
(val1, val2) ->
CfaToMonolithicExprKt.valToAction(cfa, val1, val2),
logger);
case KINDUCTION ->
checker =
BoundedCheckerBuilderKt.buildKIND(
monolithicExpr,
abstractionSolverFactory.createSolver(),
abstractionSolverFactory.createSolver(),
val -> CfaToMonolithicExprKt.valToState(cfa, val),
(val1, val2) ->
CfaToMonolithicExprKt.valToAction(cfa, val1, val2),
logger);
case IMC ->
checker =
BoundedCheckerBuilderKt.buildIMC(
monolithicExpr,
abstractionSolverFactory.createSolver(),
abstractionSolverFactory.createItpSolver(),
val -> CfaToMonolithicExprKt.valToState(cfa, val),
(val1, val2) ->
CfaToMonolithicExprKt.valToAction(cfa, val1, val2),
logger);
default ->
throw new UnsupportedOperationException(
"Algorithm " + algorithm + " not supported");
}
return checker;
}
private SafetyResult extends ARG, ?>, ? extends Trace, ?>> check(
CfaConfig, ?, ?> configuration) throws Exception {
try {
return configuration.check();
} catch (final Exception ex) {
String message = ex.getMessage() == null ? "(no message)" : ex.getMessage();
throw new Exception(
"Error while running algorithm: "
+ ex.getClass().getSimpleName()
+ " "
+ message,
ex);
}
}
private void printResult(
final SafetyResult, ? extends Trace, ?>> status, final long totalTimeMs) {
final CegarStatistics stats =
(CegarStatistics) status.getStats().orElse(new CegarStatistics(0, 0, 0, 0));
if (benchmarkMode) {
writer.cell(status.isSafe());
writer.cell(totalTimeMs);
writer.cell(stats.getAlgorithmTimeMs());
writer.cell(stats.getAbstractorTimeMs());
writer.cell(stats.getRefinerTimeMs());
writer.cell(stats.getIterations());
if (status.getProof() instanceof ARG, ?> arg) {
writer.cell(arg.size());
writer.cell(arg.getDepth());
writer.cell(arg.getMeanBranchingFactor());
} else {
writer.cell("");
writer.cell("");
writer.cell("");
}
if (status.isUnsafe()) {
writer.cell(status.asUnsafe().getCex().length() + "");
} else {
writer.cell("");
}
writer.newRow();
}
}
private void printError(final Throwable ex) {
final String message = ex.getMessage() == null ? "" : ex.getMessage();
if (benchmarkMode) {
writer.cell("[EX] " + ex.getClass().getSimpleName() + ": " + message);
writer.newRow();
} else {
logger.write(
Level.RESULT,
"%s occurred, message: %s%n",
ex.getClass().getSimpleName(),
message);
if (stacktrace) {
final StringWriter errors = new StringWriter();
ex.printStackTrace(new PrintWriter(errors));
logger.write(Level.RESULT, "Trace:%n%s%n", errors.toString());
} else {
logger.write(Level.RESULT, "Use --stacktrace for stack trace%n");
}
}
}
private void writeCex(final SafetyResult.Unsafe, ?> status) throws FileNotFoundException {
@SuppressWarnings("unchecked")
final Trace, CfaAction> trace = (Trace, CfaAction>) status.getCex();
final Trace, CfaAction> concrTrace =
CfaTraceConcretizer.concretize(trace, Z3LegacySolverFactory.getInstance());
final File file = new File(cexfile);
PrintWriter printWriter = null;
try {
printWriter = new PrintWriter(file);
printWriter.write(concrTrace.toString());
} finally {
if (printWriter != null) {
printWriter.close();
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy