/* * Copyright (C) 2019 Daniel Dietsch (dietsch@informatik.uni-freiburg.de) * Copyright (C) 2016 Christian Schilling (schillic@informatik.uni-freiburg.de) * Copyright (C) 2016-2019 University of Freiburg * * This file is part of the ULTIMATE TraceAbstraction plug-in. * * The ULTIMATE TraceAbstraction plug-in is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ULTIMATE TraceAbstraction plug-in is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ULTIMATE TraceAbstraction plug-in. If not, see . * * Additional permission under GNU GPL version 3 section 7: * If you modify the ULTIMATE TraceAbstraction plug-in, or any covered work, by linking * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), * containing parts covered by the terms of the Eclipse Public License, the * licensors of the ULTIMATE TraceAbstraction plug-in grant you additional permission * to convey the resulting work. */ package de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.tracehandling; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import de.uni_freiburg.informatik.ultimate.core.lib.exceptions.ToolchainCanceledException; import de.uni_freiburg.informatik.ultimate.core.model.services.ILogger; import de.uni_freiburg.informatik.ultimate.core.model.services.IUltimateServiceProvider; import de.uni_freiburg.informatik.ultimate.core.model.translation.IProgramExecution; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IAction; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.hoaretriple.IHoareTripleChecker; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.smt.interpolant.InterpolantComputationStatus; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.smt.interpolant.QualifiedTracePredicates; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.smt.predicates.IPredicate; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.smt.predicates.IPredicateUnifier; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.smt.tracecheck.TraceCheckReasonUnknown; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.smt.tracecheck.TraceCheckReasonUnknown.ExceptionHandlingCategory; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.tracehandling.IRefinementEngineResult.BasicRefinementEngineResult; import de.uni_freiburg.informatik.ultimate.logic.Script.LBool; import de.uni_freiburg.informatik.ultimate.logic.Term; import de.uni_freiburg.informatik.ultimate.util.Lazy; /** * Checks a trace for feasibility and, if infeasible, constructs an interpolant automaton. * * @see IRefinementStrategy * * @author Daniel Dietsch (dietsch@informatik.uni-freiburg.de) * @author Christian Schilling (schillic@informatik.uni-freiburg.de) */ public final class AutomatonFreeRefinementEngine implements IRefinementEngine> { private final ILogger mLogger; private final IRefinementStrategy mStrategy; private final RefinementEngineStatisticsGenerator mRefinementEngineStatistics; private final LBool mFeasibility; private IProgramExecution mIcfgProgramExecution; private List mUsedTracePredicates; private boolean mSomePerfectSequenceFound; private List mQualifiedTracePredicates; private String mUsedTraceCheckFingerprint; private final IUltimateServiceProvider mServices; public AutomatonFreeRefinementEngine(final IUltimateServiceProvider services, final ILogger logger, final IRefinementStrategy strategy) { mServices = services; mLogger = logger; mStrategy = strategy; mRefinementEngineStatistics = new RefinementEngineStatisticsGenerator(); mFeasibility = executeStrategy(); mRefinementEngineStatistics.finishRefinementEngineRun(); } private IHoareTripleChecker getHoareTripleChecker() { final IHoareTripleChecker strategyHtc = mStrategy.getHoareTripleChecker(this); if (strategyHtc != null) { mLogger.info("Using hoare triple checker %s provided by strategy", strategyHtc.getClass().getSimpleName()); } return strategyHtc; } private IPredicateUnifier getPredicateUnifier() { final IPredicateUnifier strategyUnifier = mStrategy.getPredicateUnifier(this); assert strategyUnifier != null; mLogger.info("Using predicate unifier %s provided by strategy %s", strategyUnifier.getClass().getSimpleName(), mStrategy.getName()); return strategyUnifier; } @Override public RefinementEngineStatisticsGenerator getRefinementEngineStatistics() { return mRefinementEngineStatistics; } /** * This method is the heart of the refinement engine.
* It first checks feasibility of the counterexample. If infeasible, the method tries to find a perfect interpolant * sequence. If unsuccessful, it collects all tested sequences. In the end an interpolant automaton is created. * * @return counterexample feasibility */ private LBool executeStrategy() { mLogger.info("Executing refinement strategy %s", mStrategy.getName()); // first, check for feasibility final LBool feasibilityResult = checkFeasibility(); if (feasibilityResult == LBool.UNKNOWN) { abortIfTimeout(String.format("Timeout during %s", mStrategy.getName())); mLogger.warn("Strategy %s was unsuccessful and could not determine trace feasibility", mStrategy.getName()); return feasibilityResult; } // trace was feasible, return if (feasibilityResult == LBool.SAT) { mLogger.info("Strategy %s found a feasible trace", mStrategy.getName()); return feasibilityResult; } // trace is infeasible, extract a proof if (feasibilityResult == LBool.UNSAT) { mLogger.info("Strategy %s found an infeasible trace", mStrategy.getName()); return generateProof(); } throw new UnsupportedOperationException("Unknown LBool: " + feasibilityResult); } private LBool generateProof() { final List perfectIpps = new ArrayList<>(); final List imperfectIpps = new ArrayList<>(); while (mStrategy.hasNextInterpolantGenerator(Collections.unmodifiableList(perfectIpps), Collections.unmodifiableList(imperfectIpps))) { final IIpgStrategyModule interpolantGenerator = tryExecuteInterpolantGenerator(); if (interpolantGenerator == null) { continue; } final Collection newPeIpSeq = interpolantGenerator.getPerfectInterpolantSequences(); perfectIpps.addAll(newPeIpSeq); final Collection newImIpSeq = interpolantGenerator.getImperfectInterpolantSequences(); imperfectIpps.addAll(newImIpSeq); mLogger.info("%s provided %s perfect and %s imperfect interpolant sequences", getModuleFingerprintString(interpolantGenerator), newPeIpSeq.size(), newImIpSeq.size()); interpolantGenerator.aggregateStatistics(mRefinementEngineStatistics); } // strategy has finished providing interpolants, use them to construct the // interpolant automaton logStats(perfectIpps, imperfectIpps); if (!perfectIpps.isEmpty()) { mSomePerfectSequenceFound = true; } if (perfectIpps.isEmpty() && imperfectIpps.isEmpty()) { mLogger.error("Strategy %s failed to provide any proof altough trace is infeasible", mStrategy.getName()); mQualifiedTracePredicates = null; return LBool.UNKNOWN; } mQualifiedTracePredicates = mStrategy.mergeInterpolants(perfectIpps, imperfectIpps); mUsedTracePredicates = mQualifiedTracePredicates; return LBool.UNSAT; } private void logStats(final List perfectIpps, final List imperfectIpps) { if (!mLogger.isInfoEnabled()) { return; } mLogger.info("Found %s perfect and %s imperfect interpolant sequences.", perfectIpps.size(), imperfectIpps.size()); final List numberInterpolantsPerfect = new ArrayList<>(); final Set allInterpolants = new HashSet<>(); for (final QualifiedTracePredicates qtp : perfectIpps) { numberInterpolantsPerfect.add(new HashSet<>(qtp.getPredicates()).size()); allInterpolants.addAll(qtp.getPredicates()); } final List numberInterpolantsImperfect = new ArrayList<>(); for (final QualifiedTracePredicates qtp : imperfectIpps) { numberInterpolantsImperfect.add(new HashSet<>(qtp.getPredicates()).size()); allInterpolants.addAll(qtp.getPredicates()); } mLogger.info("Number of different interpolants: perfect sequences " + numberInterpolantsPerfect + " imperfect sequences " + numberInterpolantsImperfect + " total " + allInterpolants.size()); } private LBool checkFeasibility() { while (mStrategy.hasNextFeasilibityCheck()) { final ITraceCheckStrategyModule currentTraceCheck = mStrategy.nextFeasibilityCheck(); abortIfTimeout("Timeout during feasibility check between " + mUsedTraceCheckFingerprint + " and " + getModuleFingerprintString(currentTraceCheck)); mUsedTraceCheckFingerprint = getModuleFingerprintString(currentTraceCheck); logModule("Using trace check", currentTraceCheck); final LBool feasibilityResult = currentTraceCheck.isCorrect(); if (feasibilityResult == LBool.SAT) { if (currentTraceCheck.providesRcfgProgramExecution()) { mIcfgProgramExecution = currentTraceCheck.getRcfgProgramExecution(); } currentTraceCheck.aggregateStatistics(mRefinementEngineStatistics); return feasibilityResult; } if (feasibilityResult == LBool.UNSAT) { currentTraceCheck.aggregateStatistics(mRefinementEngineStatistics); return feasibilityResult; } abortIfNecessaryOnUnknown(currentTraceCheck.getTraceCheckReasonUnknown()); currentTraceCheck.aggregateStatistics(mRefinementEngineStatistics); } // no trace checker could determine the feasibility of the trace, need to abort return LBool.UNKNOWN; } private void abortIfTimeout(final String taskDesc) { if (!mServices.getProgressMonitorService().continueProcessing()) { throw new ToolchainCanceledException(getClass(), taskDesc); } } private void abortIfNecessaryOnUnknown(final TraceCheckReasonUnknown tcra) { if (tcra.getException() == null) { return; } final ExceptionHandlingCategory exceptionCategory = tcra.getExceptionHandlingCategory(); switch (exceptionCategory) { case KNOWN_IGNORE: case KNOWN_DEPENDING: case KNOWN_THROW: if (mLogger.isErrorEnabled()) { mLogger.error("Caught known exception: " + tcra.getException().getMessage()); } break; case UNKNOWN: if (mLogger.isErrorEnabled()) { mLogger.error("Caught unknown exception: " + tcra.getException().getMessage()); } break; default: throw new IllegalArgumentException("Unknown exception category: " + exceptionCategory); } throwIfNecessary(tcra.getExceptionHandlingCategory(), tcra.getException()); } private IIpgStrategyModule tryExecuteInterpolantGenerator() { final IIpgStrategyModule interpolantGenerator = mStrategy.nextInterpolantGenerator(); abortIfTimeout( "Timeout during proof generation before using " + getModuleFingerprintString(interpolantGenerator)); final InterpolantComputationStatus status; try { logModule("Using interpolant generator", interpolantGenerator); status = interpolantGenerator.getInterpolantComputationStatus(); if (status == null) { mLogger.fatal("No interpolant computation status provided, assuming failure"); throw new AssertionError( interpolantGenerator.getClass() + " provided no interpolant computation status"); } if (status.wasComputationSuccessful()) { return interpolantGenerator; } } catch (final ToolchainCanceledException tce) { throw tce; } catch (final Exception e) { if (ExceptionHandlingCategory.UNKNOWN.throwException(mStrategy.getExceptionBlacklist())) { mLogger.fatal("Exception during interpolant generation: " + e.getClass().getSimpleName()); throw e; } mLogger.fatal("Ignoring exception!", e); return null; } final ExceptionHandlingCategory category; switch (status.getStatus()) { case ALGORITHM_FAILED: category = ExceptionHandlingCategory.KNOWN_IGNORE; break; case OTHER: category = ExceptionHandlingCategory.UNKNOWN; break; case SMT_SOLVER_CANNOT_INTERPOLATE_INPUT: category = ExceptionHandlingCategory.KNOWN_IGNORE; break; case SMT_SOLVER_CRASH: category = ExceptionHandlingCategory.KNOWN_DEPENDING; break; case TRACE_FEASIBLE: final String msg = String.format("Tracecheck %s said UNSAT, interpolant generator %s failed with %s", mUsedTraceCheckFingerprint, getModuleFingerprintString(interpolantGenerator), status.getStatus()); throw new IllegalStateException(msg); default: throw new AssertionError("unknown case : " + status.getStatus()); } throwIfNecessary(category, status.getException()); final String message = status.getException() == null ? String.valueOf(status.getStatus()) : status.getException().getMessage(); mLogger.warn("Interpolation failed due to " + category + ": " + message); return null; } private void throwIfNecessary(final ExceptionHandlingCategory category, final Throwable t) { // note: you only need to use this function if you did not get the throwable // from a catch block! final boolean throwException = category.throwException(mStrategy.getExceptionBlacklist()); if (throwException) { mLogger.warn("Global settings require throwing the following exception"); // throw unchecked exceptions directly, wrap checked exceptions in // AssertionError if (t instanceof Error) { throw (Error) t; } if (t instanceof RuntimeException) { throw (RuntimeException) t; } throw new AssertionError(t); } } private void logModule(final String msg, final Object module) { mLogger.info("%s %s", msg, getModuleFingerprintString(module)); } private static String getModuleFingerprintString(final Object obj) { return String.format("%s [%s]", obj.getClass().getSimpleName(), obj.hashCode()); } @Override public IRefinementEngineResult> getResult() { return new BasicRefinementEngineResult<>(mFeasibility, mQualifiedTracePredicates, mIcfgProgramExecution, mSomePerfectSequenceFound, mUsedTracePredicates, new Lazy<>(this::getHoareTripleChecker), new Lazy<>(this::getPredicateUnifier)); } }