Java tutorial
/* * CPAchecker is a tool for configurable software verification. * This file is part of CPAchecker. * * Copyright (C) 2007-2014 Dirk Beyer * All rights reserved. * * 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. * * * CPAchecker web page: * http://cpachecker.sosy-lab.org */ package org.sosy_lab.cpachecker.util.predicates.interpolation; import static com.google.common.base.Preconditions.*; import static com.google.common.collect.FluentIterable.from; import static org.sosy_lab.cpachecker.util.statistics.StatisticsUtils.div; import java.io.PrintStream; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Deque; import java.util.HashSet; import java.util.List; import java.util.ListIterator; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.logging.Level; import org.sosy_lab.common.Appender; import org.sosy_lab.common.Classes.UnexpectedCheckedException; import org.sosy_lab.common.Pair; import org.sosy_lab.common.Triple; import org.sosy_lab.common.concurrency.Threads; import org.sosy_lab.common.configuration.Configuration; import org.sosy_lab.common.configuration.InvalidConfigurationException; import org.sosy_lab.common.configuration.Option; import org.sosy_lab.common.configuration.Options; import org.sosy_lab.common.configuration.TimeSpanOption; import org.sosy_lab.common.io.Path; import org.sosy_lab.common.log.LogManager; import org.sosy_lab.common.time.TimeSpan; import org.sosy_lab.common.time.Timer; import org.sosy_lab.cpachecker.cfa.model.CFANode; import org.sosy_lab.cpachecker.cfa.model.FunctionEntryNode; import org.sosy_lab.cpachecker.cfa.model.FunctionExitNode; import org.sosy_lab.cpachecker.core.CPAcheckerResult.Result; import org.sosy_lab.cpachecker.core.ShutdownNotifier; import org.sosy_lab.cpachecker.core.counterexample.Model; import org.sosy_lab.cpachecker.core.interfaces.AbstractState; import org.sosy_lab.cpachecker.core.reachedset.ReachedSet; import org.sosy_lab.cpachecker.cpa.arg.ARGState; import org.sosy_lab.cpachecker.exceptions.CPAException; import org.sosy_lab.cpachecker.exceptions.CPATransferException; import org.sosy_lab.cpachecker.exceptions.RefinementFailedException; import org.sosy_lab.cpachecker.exceptions.RefinementFailedException.Reason; import org.sosy_lab.cpachecker.exceptions.SolverException; import org.sosy_lab.cpachecker.util.AbstractStates; import org.sosy_lab.cpachecker.util.predicates.Solver; import org.sosy_lab.cpachecker.util.predicates.interfaces.BasicProverEnvironment; import org.sosy_lab.cpachecker.util.predicates.interfaces.BooleanFormula; import org.sosy_lab.cpachecker.util.predicates.interfaces.InterpolatingProverEnvironment; import org.sosy_lab.cpachecker.util.predicates.interfaces.PathFormulaManager; import org.sosy_lab.cpachecker.util.predicates.interfaces.ProverEnvironment; import org.sosy_lab.cpachecker.util.predicates.interfaces.view.BooleanFormulaManagerView; import org.sosy_lab.cpachecker.util.predicates.interfaces.view.FormulaManagerView; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultiset; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @Options(prefix = "cpa.predicate.refinement") public final class InterpolationManager { private final Timer cexAnalysisTimer = new Timer(); private final Timer satCheckTimer = new Timer(); private final Timer getInterpolantTimer = new Timer(); private final Timer cexAnalysisGetUsefulBlocksTimer = new Timer(); private final Timer interpolantVerificationTimer = new Timer(); private int reusedFormulasOnSolverStack = 0; public void printStatistics(PrintStream out, Result result, ReachedSet reached) { out.println(" Counterexample analysis: " + cexAnalysisTimer + " (Max: " + cexAnalysisTimer.getMaxTime().formatAs(TimeUnit.SECONDS) + ", Calls: " + cexAnalysisTimer.getNumberOfIntervals() + ")"); if (cexAnalysisGetUsefulBlocksTimer.getNumberOfIntervals() > 0) { out.println(" Cex.focusing: " + cexAnalysisGetUsefulBlocksTimer + " (Max: " + cexAnalysisGetUsefulBlocksTimer.getMaxTime().formatAs(TimeUnit.SECONDS) + ")"); } out.println(" Refinement sat check: " + satCheckTimer); if (reuseInterpolationEnvironment && satCheckTimer.getNumberOfIntervals() > 0) { out.println(" Reused formulas on solver stack: " + reusedFormulasOnSolverStack + " (Avg: " + div(reusedFormulasOnSolverStack, satCheckTimer.getNumberOfIntervals()) + ")"); } out.println(" Interpolant computation: " + getInterpolantTimer); if (interpolantVerificationTimer.getNumberOfIntervals() > 0) { out.println(" Interpolant verification: " + interpolantVerificationTimer); } } private final LogManager logger; private final ShutdownNotifier shutdownNotifier; private final FormulaManagerView fmgr; private final BooleanFormulaManagerView bfmgr; private final PathFormulaManager pmgr; private final Solver solver; private final Interpolator<?> interpolator; @Option(secure = true, description = "apply deletion-filter to the abstract counterexample, to get " + "a minimal set of blocks, before applying interpolation-based refinement") private boolean getUsefulBlocks = false; @Option(secure = true, name = "incrementalCexTraceCheck", description = "use incremental search in counterexample analysis, " + "to find the minimal infeasible prefix") private boolean incrementalCheck = false; @Option(secure = true, name = "cexTraceCheckDirection", description = "Direction for doing counterexample analysis: from start of trace, from end of trace, or alternatingly from start and end of the trace towards the middle") private CexTraceAnalysisDirection direction = CexTraceAnalysisDirection.FORWARDS; private static enum CexTraceAnalysisDirection { FORWARDS, BACKWARDS, ZIGZAG,; } @Option(secure = true, description = "Strategy how to interact woith the intepolating prover. " + "If a strategy starts with 'CPACHECKER_', we use our own implementation and do not use the solver's method. " + "In our own implementation the properties of interpolants are guaranteed for special cases only." + "\n- CPACHECKER_SEQ: We simply return each interpolant for i={0..n-1} for the partitions A=[0 .. i] and B=[i+1 .. n]. " + "The result is similar to INDUCTIVE_SEQ, but we do not guarantee the 'inductiveness', i.e. the solver has to generate nice interpolants. " + "\n- INDUCTIVE_SEQ: Generate an inductive sequence of interpolants the partitions [1,...n]. " + "\n- CPACHECKER_WELLSCOPED: We return each interpolant for i={0..n-1} for the partitions " + "A=[lastFunctionEntryIndex .. i] and B=[0 .. lastFunctionEntryIndex-1 , i+1 .. n]." + "\n- NESTED: use callstack and previous interpolants for next interpolants (see 'Nested Interpolants').") private InterpolationStrategy strategy = InterpolationStrategy.CPACHECKER_SEQ; private static enum InterpolationStrategy { CPACHECKER_SEQ, INDUCTIVE_SEQ, CPACHECKER_WELLSCOPED, NESTED } @Option(secure = true, description = "dump all interpolation problems") private boolean dumpInterpolationProblems = false; @Option(secure = true, description = "verify if the interpolants fulfill the interpolant properties") private boolean verifyInterpolants = false; @Option(secure = true, name = "timelimit", description = "time limit for refinement (use milliseconds or specify a unit; 0 for infinite)") @TimeSpanOption(codeUnit = TimeUnit.MILLISECONDS, defaultUserUnit = TimeUnit.MILLISECONDS, min = 0) private TimeSpan itpTimeLimit = TimeSpan.ofMillis(0); @Option(secure = true, description = "skip refinement if input formula is larger than " + "this amount of bytes (ignored if 0)") private int maxRefinementSize = 0; @Option(secure = true, description = "Use a single SMT solver environment for several interpolation queries") private boolean reuseInterpolationEnvironment = false; private final ExecutorService executor; public InterpolationManager(PathFormulaManager pPmgr, Solver pSolver, Configuration config, ShutdownNotifier pShutdownNotifier, LogManager pLogger) throws InvalidConfigurationException { config.inject(this, InterpolationManager.class); logger = pLogger; shutdownNotifier = pShutdownNotifier; fmgr = pSolver.getFormulaManager(); bfmgr = fmgr.getBooleanFormulaManager(); pmgr = pPmgr; solver = pSolver; if (itpTimeLimit.isEmpty()) { executor = null; } else { // important to use daemon threads here, because we never have the chance to stop the executor executor = Executors.newSingleThreadExecutor(Threads.threadFactoryBuilder().setDaemon(true).build()); } if (reuseInterpolationEnvironment) { interpolator = new Interpolator<>(); } else { interpolator = null; } } public Appender dumpCounterexample(CounterexampleTraceInfo cex) { return fmgr.dumpFormula(bfmgr.and(cex.getCounterExampleFormulas())); } /** * Counterexample analysis. * This method is just an helper to delegate the actual work * This is used to detect timeouts for interpolation * * @param pFormulas the formulas for the path * @param pAbstractionStates the abstraction states between the formulas and the last state of the path. * The first state (root) of the path is missing, because it is always TRUE. * (can be empty, if well-scoped interpolation is disabled or not required) * @param elementsOnPath the ARGElements on the path (may be empty if no branching information is required) * @throws CPAException * @throws InterruptedException */ public CounterexampleTraceInfo buildCounterexampleTrace(final List<BooleanFormula> pFormulas, final List<AbstractState> pAbstractionStates, final Set<ARGState> elementsOnPath, final boolean computeInterpolants) throws CPAException, InterruptedException { assert pAbstractionStates.isEmpty() || pFormulas.size() == pAbstractionStates.size(); // if we don't want to limit the time given to the solver if (itpTimeLimit.isEmpty()) { return buildCounterexampleTrace0(pFormulas, pAbstractionStates, elementsOnPath, computeInterpolants); } assert executor != null; Callable<CounterexampleTraceInfo> tc = new Callable<CounterexampleTraceInfo>() { @Override public CounterexampleTraceInfo call() throws CPAException, InterruptedException { return buildCounterexampleTrace0(pFormulas, pAbstractionStates, elementsOnPath, computeInterpolants); } }; Future<CounterexampleTraceInfo> future = executor.submit(tc); try { // here we get the result of the post computation but there is a time limit // given to complete the task specified by timeLimit return future.get(itpTimeLimit.asNanos(), TimeUnit.NANOSECONDS); } catch (TimeoutException e) { logger.log(Level.SEVERE, "SMT-solver timed out during interpolation process"); throw new RefinementFailedException(Reason.TIMEOUT, null); } catch (ExecutionException e) { Throwable t = e.getCause(); Throwables.propagateIfPossible(t, CPAException.class, InterruptedException.class); throw new UnexpectedCheckedException("interpolation", t); } } public CounterexampleTraceInfo buildCounterexampleTrace(final List<BooleanFormula> pFormulas, final List<AbstractState> pAbstractionStates, final Set<ARGState> elementsOnPath) throws CPAException, InterruptedException { return buildCounterexampleTrace(pFormulas, pAbstractionStates, elementsOnPath, true); } public CounterexampleTraceInfo buildCounterexampleTrace(final List<BooleanFormula> pFormulas) throws CPAException, InterruptedException { return buildCounterexampleTrace(pFormulas, Collections.<AbstractState>emptyList(), Collections.<ARGState>emptySet(), true); } private CounterexampleTraceInfo buildCounterexampleTrace0(final List<BooleanFormula> pFormulas, final List<AbstractState> pAbstractionStates, final Set<ARGState> elementsOnPath, final boolean computeInterpolants) throws CPAException, InterruptedException { logger.log(Level.FINEST, "Building counterexample trace"); cexAnalysisTimer.start(); try { // Final adjustments to the list of formulas List<BooleanFormula> f = new ArrayList<>(pFormulas); // copy because we will change the list if (fmgr.useBitwiseAxioms()) { addBitwiseAxioms(f); } f = Collections.unmodifiableList(f); logger.log(Level.ALL, "Counterexample trace formulas:", f); // now f is the DAG formula which is satisfiable iff there is a // concrete counterexample // Check if refinement problem is not too big if (maxRefinementSize > 0) { int size = fmgr.dumpFormula(bfmgr.and(f)).toString().length(); if (size > maxRefinementSize) { logger.log(Level.FINEST, "Skipping refinement because input formula is", size, "bytes large."); throw new RefinementFailedException(Reason.TooMuchUnrolling, null); } } final Interpolator<?> currentInterpolator; if (reuseInterpolationEnvironment) { currentInterpolator = checkNotNull(interpolator); } else { currentInterpolator = new Interpolator<>(); } try { try { return currentInterpolator.buildCounterexampleTrace(f, pAbstractionStates, elementsOnPath, computeInterpolants); } finally { if (!reuseInterpolationEnvironment) { currentInterpolator.close(); } } } catch (SolverException e) { logger.logUserException(Level.FINEST, e, "Interpolation failed, attempting to solve without interpolation"); // Maybe the solver can handle the formulas if we do not attempt to interpolate try (ProverEnvironment prover = solver.newProverEnvironmentWithModelGeneration()) { for (BooleanFormula block : f) { prover.push(block); } if (!prover.isUnsat()) { return getErrorPath(f, prover, elementsOnPath); } } catch (SolverException e2) { // in case of exception throw original one below logger.logDebugException(e2, "Solving trace failed even without interpolation"); } throw new RefinementFailedException(Reason.InterpolationFailed, null, e); } } finally { cexAnalysisTimer.stop(); } } /** * Add axioms about bitwise operations to a list of formulas, if such operations * are used. This is probably not that helpful currently, we would have to the * tell the solver that these are axioms. * * The axioms are added to the last part of the list of formulas. * * @param f The list of formulas to scan for bitwise operations. */ private void addBitwiseAxioms(List<BooleanFormula> f) { BooleanFormula bitwiseAxioms = bfmgr.makeBoolean(true); for (BooleanFormula fm : f) { BooleanFormula a = fmgr.getBitwiseAxioms(fm); if (!bfmgr.isTrue(a)) { bitwiseAxioms = fmgr.getBooleanFormulaManager().and(bitwiseAxioms, a); } } if (!bfmgr.isTrue(bitwiseAxioms)) { logger.log(Level.ALL, "DEBUG_3", "ADDING BITWISE AXIOMS TO THE", "LAST GROUP: ", bitwiseAxioms); int lastIndex = f.size() - 1; f.set(lastIndex, bfmgr.and(f.get(lastIndex), bitwiseAxioms)); } } /** * Try to find out which formulas out of a list of formulas are relevant for * making the conjunction unsatisfiable. * This method honors the {@link #direction} configuration option. * * @param f The list of formulas to check. * @return A sublist of f that contains the useful formulas. */ private List<BooleanFormula> getUsefulBlocks(List<BooleanFormula> f) throws SolverException, InterruptedException { cexAnalysisGetUsefulBlocksTimer.start(); // try to find a minimal-unsatisfiable-core of the trace (as Blast does) try (ProverEnvironment thmProver = solver.newProverEnvironment()) { logger.log(Level.ALL, "DEBUG_1", "Calling getUsefulBlocks on path", "of length:", f.size()); final BooleanFormula[] needed = new BooleanFormula[f.size()]; for (int i = 0; i < needed.length; ++i) { needed[i] = bfmgr.makeBoolean(true); } final boolean backwards = direction == CexTraceAnalysisDirection.BACKWARDS; final int start = backwards ? f.size() - 1 : 0; final int increment = backwards ? -1 : 1; int toPop = 0; while (true) { boolean consistent = true; // 1. assert all the needed constraints for (int i = 0; i < needed.length; ++i) { if (!bfmgr.isTrue(needed[i])) { thmProver.push(needed[i]); ++toPop; } } // 2. if needed is inconsistent, then return it if (thmProver.isUnsat()) { f = Arrays.asList(needed); break; } // 3. otherwise, assert one block at a time, until we get an // inconsistency if (direction == CexTraceAnalysisDirection.ZIGZAG) { int s = 0; int e = f.size() - 1; boolean fromStart = false; while (true) { int i = fromStart ? s++ : e--; fromStart = !fromStart; BooleanFormula t = f.get(i); thmProver.push(t); ++toPop; if (thmProver.isUnsat()) { // add this block to the needed ones, and repeat needed[i] = t; logger.log(Level.ALL, "DEBUG_1", "Found needed block: ", i, ", term: ", t); // pop all while (toPop > 0) { --toPop; thmProver.pop(); } // and go to the next iteration of the while loop consistent = false; break; } if (e < s) { break; } } } else { for (int i = start; backwards ? i >= 0 : i < f.size(); i += increment) { BooleanFormula t = f.get(i); thmProver.push(t); ++toPop; if (thmProver.isUnsat()) { // add this block to the needed ones, and repeat needed[i] = t; logger.log(Level.ALL, "DEBUG_1", "Found needed block: ", i, ", term: ", t); // pop all while (toPop > 0) { --toPop; thmProver.pop(); } // and go to the next iteration of the while loop consistent = false; break; } } } if (consistent) { // if we get here, the trace is consistent: // this is a real counterexample! break; } } while (toPop > 0) { --toPop; thmProver.pop(); } } logger.log(Level.ALL, "DEBUG_1", "Done getUsefulBlocks"); cexAnalysisGetUsefulBlocksTimer.stop(); return f; } /** * Put the list of formulas into the order in which they should be given to * the solver, as defined by the {@link #direction} configuration option. * @param traceFormulas The list of formulas to check. * @return The same list of formulas in different order, * and each formula has its position in the original list as third element of the pair. */ private List<Triple<BooleanFormula, AbstractState, Integer>> orderFormulas( final List<BooleanFormula> traceFormulas, final List<AbstractState> pAbstractionStates) { // In this list are all formulas together with their position in the original list ImmutableList.Builder<Triple<BooleanFormula, AbstractState, Integer>> orderedFormulas = ImmutableList .builder(); if (direction == CexTraceAnalysisDirection.ZIGZAG) { int e = traceFormulas.size() - 1; int s = 0; boolean fromStart = false; while (s <= e) { int i = fromStart ? s++ : e--; fromStart = !fromStart; orderedFormulas.add(Triple.of(traceFormulas.get(i), pAbstractionStates.get(i), i)); } } else { final boolean backwards = direction == CexTraceAnalysisDirection.BACKWARDS; final int increment = backwards ? -1 : 1; for (int i = backwards ? traceFormulas.size() - 1 : 0; backwards ? i >= 0 : i < traceFormulas.size(); i += increment) { orderedFormulas.add(Triple.of(traceFormulas.get(i), pAbstractionStates.get(i), i)); } } ImmutableList<Triple<BooleanFormula, AbstractState, Integer>> result = orderedFormulas.build(); assert traceFormulas.size() == result.size(); assert ImmutableMultiset.copyOf(from(result).transform(Triple.getProjectionToFirst())) .equals(ImmutableMultiset.copyOf( traceFormulas)) : "Ordered list does not contain the same formulas with the same count"; return result; } /** * Get the interpolants from the solver after the formulas have been proved * to be unsatisfiable. * * @param pItpProver The solver. * @param itpGroupsIds The references to the interpolation groups * @param orderedFormulas list of formulas with their (nullable) successor-state and the index in the "correct" order. * @return A list of all the interpolants. */ private <T> List<BooleanFormula> getInterpolants(final Interpolator<T> interpolator, List<T> itpGroupsIds, final List<Triple<BooleanFormula, AbstractState, Integer>> orderedFormulas) throws InterruptedException, SolverException { assert itpGroupsIds.size() == orderedFormulas.size(); // The counterexample is spurious. Get the interpolants. checkState( strategy == InterpolationStrategy.CPACHECKER_SEQ || direction == CexTraceAnalysisDirection.FORWARDS, "well-scoped or nested interpolants are based on function-scopes and need to traverse the error-trace in forward direction."); switch (strategy) { case CPACHECKER_SEQ: { final List<BooleanFormula> interpolants = new ArrayList<>(); for (int end_of_A = 0; end_of_A < itpGroupsIds.size() - 1; end_of_A++) { // last iteration is left out because B would be empty final int start_of_A = 0; interpolants.add( getInterpolantFromSublist(interpolator.itpProver, itpGroupsIds, start_of_A, end_of_A, 0)); } return interpolants; } case INDUCTIVE_SEQ: { // wrap into singleton to match interface-type final List<Set<T>> itpGroups = new ArrayList<>(); for (T f : itpGroupsIds) { itpGroups.add(Collections.singleton(f)); } return interpolator.itpProver.getSeqInterpolants(itpGroups); } case CPACHECKER_WELLSCOPED: { // TODO not fully working and not used final List<BooleanFormula> interpolants = new ArrayList<>(); final Deque<Pair<Integer, CFANode>> callstack = new ArrayDeque<>(); for (int end_of_A = 0; end_of_A < itpGroupsIds.size() - 1; end_of_A++) { // last iteration is left out because B would be empty final int start_of_A = getWellScopedStartOfA(orderedFormulas, callstack, end_of_A); interpolants.add(getInterpolantFromSublist(interpolator.itpProver, itpGroupsIds, start_of_A, end_of_A, callstack.size())); } return interpolants; } case NESTED: { final List<BooleanFormula> interpolants = new ArrayList<>(); BooleanFormula lastItp = bfmgr.makeBoolean(true); // PSI_0 = True final Deque<Triple<BooleanFormula, BooleanFormula, CFANode>> callstack = new ArrayDeque<>(); for (int positionOfA = 0; positionOfA < orderedFormulas.size() - 1; positionOfA++) { lastItp = getNestedInterpolant(orderedFormulas, interpolants, callstack, interpolator, positionOfA, lastItp); } return interpolants; } default: throw new AssertionError("unknown intepolation strategy"); } } private int getWellScopedStartOfA(List<Triple<BooleanFormula, AbstractState, Integer>> orderedFormulas, Deque<Pair<Integer, CFANode>> callstack, int end_of_A) { checkState(direction == CexTraceAnalysisDirection.FORWARDS, "well-scoped predicated need to traverse the error-trace in a normal way."); // TODO check backward (should be working) and ZIGZAG (???) // TODO the following code relies on the fact that there is always an abstraction on function-calls and -returns if (end_of_A == 0) { return 0; } // If we have entered or exited a function, update the stack of entry points final AbstractState abstractionState = checkNotNull(orderedFormulas.get(end_of_A - 1).getSecond()); final CFANode node = AbstractStates.extractLocation(abstractionState); if (node instanceof FunctionEntryNode) { callstack.addLast(Pair.of(end_of_A, node)); } // we are returning from a function, // TODO one node to early or to late in the CFA? or only for BAM? -> see BlockOperator for BlockEnd! // TODO matching node can be wrong in case of recursion of multiple calls to the same function? if (!callstack.isEmpty()) { final CFANode lastEntryNode = callstack.getLast().getSecond(); if ((node instanceof FunctionExitNode && ((FunctionExitNode) node).getEntryNode() == lastEntryNode) //|| (node.getEnteringSummaryEdge() != null // && node.getEnteringSummaryEdge().getPredecessor().getLeavingEdge(0).getSuccessor() == lastEntryNode) ) { callstack.removeLast(); } } if (callstack.isEmpty()) { return 0; // we are in the main-function, so A starts at 0. } else { return callstack.getLast().getFirst(); } } /** This function implements the paper "Nested Interpolants" with a small modification: * instead of a return-edge, we use dummy-edges with simple pathformula "true". * Actually the implementation does not use "true", but omits it completely and * returns the conjunction of the two interpolants (before and after the (non-existing) dummy edge). */ private <T> BooleanFormula getNestedInterpolant( final List<Triple<BooleanFormula, AbstractState, Integer>> orderedFormulas, final List<BooleanFormula> interpolants, final Deque<Triple<BooleanFormula, BooleanFormula, CFANode>> callstack, final Interpolator<T> interpolator, int positionOfA, BooleanFormula lastItp) throws InterruptedException, SolverException { // use a new prover, because we use several distinct queries try (final InterpolatingProverEnvironment<T> itpProver = interpolator.newEnvironment()) { final List<T> A = new ArrayList<>(); final List<T> B = new ArrayList<>(); // If we have entered or exited a function, update the stack of entry points final AbstractState abstractionState = checkNotNull(orderedFormulas.get(positionOfA).getSecond()); final CFANode node = AbstractStates.extractLocation(abstractionState); if (node instanceof FunctionEntryNode && callHasReturn(orderedFormulas, positionOfA)) { // && (positionOfA > 0)) { // case 2 from paper final BooleanFormula call = orderedFormulas.get(positionOfA).getFirst(); callstack.addLast(Triple.of(lastItp, call, node)); final BooleanFormula itp = bfmgr.makeBoolean(true); interpolants.add(itp); return itp; // PSIminus = True --> PSI = True, for the 3rd rule ITP is True } A.add(itpProver.push(lastItp)); A.add(itpProver.push(orderedFormulas.get(positionOfA).getFirst())); // add all remaining PHI_j for (Triple<BooleanFormula, AbstractState, Integer> t : Iterables.skip(orderedFormulas, positionOfA + 1)) { B.add(itpProver.push(t.getFirst())); } // add all previous function calls for (Triple<BooleanFormula, BooleanFormula, CFANode> t : callstack) { B.add(itpProver.push(t.getFirst())); // add PSI_k B.add(itpProver.push(t.getSecond())); // ... and PHI_k } // update prover with new formulas. // this is the expensive step, that is distinct from other strategies. // TODO improve! example: reverse ordering of formulas for re-usage of the solver-stack boolean unsat = itpProver.isUnsat(); assert unsat : "formulas were unsat before, they have to be unsat now."; // get interpolant of A and B, for B we use the complementary set of A final BooleanFormula itp = itpProver.getInterpolant(A); if (!callstack.isEmpty() && node instanceof FunctionExitNode) { // case 4, we are returning from a function, rule 4 Triple<BooleanFormula, BooleanFormula, CFANode> scopingItp = callstack.removeLast(); final InterpolatingProverEnvironment<T> itpProver2 = interpolator.newEnvironment(); final List<T> A2 = new ArrayList<>(); final List<T> B2 = new ArrayList<>(); A2.add(itpProver2.push(itp)); //A2.add(itpProver2.push(orderedFormulas.get(positionOfA).getFirst())); A2.add(itpProver2.push(scopingItp.getFirst())); A2.add(itpProver2.push(scopingItp.getSecond())); // add all remaining PHI_j for (Triple<BooleanFormula, AbstractState, Integer> t : Iterables.skip(orderedFormulas, positionOfA + 1)) { B2.add(itpProver2.push(t.getFirst())); } // add all previous function calls for (Triple<BooleanFormula, BooleanFormula, CFANode> t : callstack) { B2.add(itpProver2.push(t.getFirst())); // add PSI_k B2.add(itpProver2.push(t.getSecond())); // ... and PHI_k } boolean unsat2 = itpProver2.isUnsat(); assert unsat2 : "formulas2 were unsat before, they have to be unsat now."; // get interpolant of A and B, for B we use the complementary set of A BooleanFormula itp2 = itpProver2.getInterpolant(A2); itpProver2.close(); interpolants.add(rebuildInterpolant(itp, itp2)); return itp2; } else { interpolants.add(itp); return itp; } } } /** * We need all atoms of both interpolants in one formula, * If one of the formulas is True or False, we do not get Atoms from it. Thus we remove those cases. */ private BooleanFormula rebuildInterpolant(BooleanFormula functionSummary, BooleanFormula functionExecution) { final BooleanFormula rebuildItp; if (bfmgr.isTrue(functionSummary) || bfmgr.isFalse(functionSummary)) { rebuildItp = functionExecution; } else if (bfmgr.isTrue(functionExecution) || bfmgr.isFalse(functionExecution)) { rebuildItp = functionSummary; } else { // TODO operation OR is weak, we could also use AND. // There is no difference for the atoms later, because we filter out True and False here. rebuildItp = bfmgr.or(functionSummary, functionExecution); } return rebuildItp; } /** check, if there exists a function-exit-node to the current call-node. */ private boolean callHasReturn(List<Triple<BooleanFormula, AbstractState, Integer>> orderedFormulas, int callIndex) { // TODO caching as optimisation to reduce from k*O(n) to O(n)+k*O(1) ? final Deque<CFANode> callstack = new ArrayDeque<>(); { final AbstractState abstractionState = orderedFormulas.get(callIndex).getSecond(); final CFANode node = AbstractStates.extractLocation(abstractionState); assert (node instanceof FunctionEntryNode) : "call needed as input param"; callstack.addLast(node); } // walk along path and track the callstack for (Triple<BooleanFormula, AbstractState, Integer> t : Iterables.skip(orderedFormulas, callIndex + 1)) { assert !callstack.isEmpty() : "should have returned when callstack is empty"; final AbstractState abstractionState = checkNotNull(t.getSecond()); final CFANode node = AbstractStates.extractLocation(abstractionState); if (node instanceof FunctionEntryNode) { callstack.addLast(node); } final CFANode lastEntryNode = callstack.getLast(); if ((node instanceof FunctionExitNode && ((FunctionExitNode) node).getEntryNode() == lastEntryNode) //|| (node.getEnteringSummaryEdge() != null // && node.getEnteringSummaryEdge().getPredecessor().getLeavingEdge(0).getSuccessor() == lastEntryNode) ) { callstack.removeLast(); // we found the function exit for the input param if (callstack.isEmpty()) { return true; } } } return false; } /** * Precondition: The solver-stack contains all formulas and is UNSAT. * Get the interpolant between the Sublist of formulas and the other formulas on the solver-stack. * Each formula is identified by its GroupId, * The sublist is taken from the list of GroupIds, including both start and end of A. */ private <T> BooleanFormula getInterpolantFromSublist(final InterpolatingProverEnvironment<T> pItpProver, final List<T> itpGroupsIds, final int start_of_A, final int end_of_A, final int depth) throws SolverException, InterruptedException { shutdownNotifier.shutdownIfNecessary(); logger.log(Level.ALL, "Looking for interpolant for formulas from", start_of_A, "to", end_of_A, "(depth", depth, ")"); getInterpolantTimer.start(); final BooleanFormula itp = pItpProver.getInterpolant(itpGroupsIds.subList(start_of_A, end_of_A + 1)); getInterpolantTimer.stop(); logger.log(Level.ALL, "Received interpolant", itp); if (dumpInterpolationProblems) { dumpFormulaToFile("interpolant", itp, end_of_A); } return itp; } private <T> void verifyInterpolants(List<BooleanFormula> interpolants, List<BooleanFormula> formulas, InterpolatingProverEnvironment<T> prover) throws SolverException, InterruptedException { interpolantVerificationTimer.start(); try { final int n = interpolants.size(); assert n == (formulas.size() - 1); // The following three properties need to be checked: // (A) true & f_0 => itp_0 // (B) \forall i \in [1..n-1] : itp_{i-1} & f_i => itp_i // (C) itp_{n-1} & f_n => false // Check (A) if (!solver.implies(formulas.get(0), interpolants.get(0))) { throw new SolverException("First interpolant is not implied by first formula"); } // Check (B). for (int i = 1; i <= (n - 1); i++) { BooleanFormula conjunct = bfmgr.and(interpolants.get(i - 1), formulas.get(i)); if (!solver.implies(conjunct, interpolants.get(i))) { throw new SolverException( "Interpolant " + interpolants.get(i) + " is not implied by previous part of the path"); } } // Check (C). BooleanFormula conjunct = bfmgr.and(interpolants.get(n - 1), formulas.get(n)); if (!solver.implies(conjunct, bfmgr.makeBoolean(false))) { throw new SolverException("Last interpolant fails to prove infeasibility of the path"); } // Furthermore, check if the interpolants contains only the allowed variables List<Set<String>> variablesInFormulas = Lists.newArrayListWithExpectedSize(formulas.size()); for (BooleanFormula f : formulas) { variablesInFormulas.add(fmgr.extractVariableNames(f)); } for (int i = 0; i < interpolants.size(); i++) { Set<String> variablesInA = new HashSet<>(); for (int j = 0; j <= i; j++) { // formula i is in group A variablesInA.addAll(variablesInFormulas.get(j)); } Set<String> variablesInB = new HashSet<>(); for (int j = i + 1; j < formulas.size(); j++) { // formula i is in group A variablesInB.addAll(variablesInFormulas.get(j)); } Set<String> allowedVariables = Sets.intersection(variablesInA, variablesInB).immutableCopy(); Set<String> variablesInInterpolant = fmgr.extractVariableNames(interpolants.get(i)); variablesInInterpolant.removeAll(allowedVariables); if (!variablesInInterpolant.isEmpty()) { throw new SolverException("Interpolant " + interpolants.get(i) + " contains forbidden variable(s) " + variablesInInterpolant); } } } finally { interpolantVerificationTimer.stop(); } } /** * Get information about the error path from the solver after the formulas * have been proved to be satisfiable. * * @param f The list of formulas on the path. * @param pProver The solver. * @param elementsOnPath The ARGElements of the paths represented by f. * @return Information about the error path, including a satisfying assignment. * @throws CPATransferException * @throws InterruptedException */ private CounterexampleTraceInfo getErrorPath(List<BooleanFormula> f, BasicProverEnvironment<?> pProver, Set<ARGState> elementsOnPath) throws CPATransferException, SolverException, InterruptedException { // get the branchingFormula // this formula contains predicates for all branches we took // this way we can figure out which branches make a feasible path BooleanFormula branchingFormula = pmgr.buildBranchingFormula(elementsOnPath); if (bfmgr.isTrue(branchingFormula)) { return CounterexampleTraceInfo.feasible(f, getModel(pProver), ImmutableMap.<Integer, Boolean>of()); } // add formula to solver environment pProver.push(branchingFormula); // need to ask solver for satisfiability again, // otherwise model doesn't contain new predicates boolean stillSatisfiable = !pProver.isUnsat(); if (stillSatisfiable) { Model model = getModel(pProver); return CounterexampleTraceInfo.feasible(f, model, pmgr.getBranchingPredicateValuesFromModel(model)); } else { // this should not happen logger.log(Level.WARNING, "Could not get precise error path information because of inconsistent reachingPathsFormula!"); dumpInterpolationProblem(f); dumpFormulaToFile("formula", branchingFormula, f.size()); return CounterexampleTraceInfo.feasible(f, Model.empty(), ImmutableMap.<Integer, Boolean>of()); } } private Model getModel(BasicProverEnvironment<?> pItpProver) { try { return pItpProver.getModel(); } catch (SolverException e) { logger.log(Level.WARNING, "Solver could not produce model, variable assignment of error path can not be dumped."); logger.logDebugException(e); return Model.empty(); } } /** * Helper method to dump a list of formulas to files. */ private void dumpInterpolationProblem(List<BooleanFormula> f) { int k = 0; for (BooleanFormula formula : f) { dumpFormulaToFile("formula", formula, k++); } } private void dumpFormulaToFile(String name, BooleanFormula f, int i) { Path dumpFile = formatFormulaOutputFile(name, i); fmgr.dumpFormulaToFile(f, dumpFile); } private Path formatFormulaOutputFile(String formula, int index) { return fmgr.formatFormulaOutputFile("interpolation", cexAnalysisTimer.getNumberOfIntervals(), formula, index); } /** * This class encapsulates the used SMT solver for interpolation, * and keeps track of the formulas that are currently on the solver stack. * * An instance of this class can be used for several interpolation queries * in a row, and it will try to keep as many formulas as possible in the * SMT solver between those queries (so that the solver may reuse information * from previous queries, and hopefully might even return similar interpolants). * * When an instance won't be used anymore, call {@link #close()}. * * @param <T> */ private class Interpolator<T> { private InterpolatingProverEnvironment<T> itpProver; private final List<Triple<BooleanFormula, AbstractState, T>> currentlyAssertedFormulas = new ArrayList<>(); Interpolator() { itpProver = newEnvironment(); } @SuppressWarnings("unchecked") private InterpolatingProverEnvironment<T> newEnvironment() { // This is safe because we don't actually care about the value of T, // only the InterpolatingProverEnvironment itself cares about it. return (InterpolatingProverEnvironment<T>) solver.newProverEnvironmentWithInterpolation(); } /** * Counterexample analysis and predicate discovery. * @param pFormulas the formulas for the path * @param elementsOnPath the ARGElements on the path (may be empty if no branching information is required) * @param itpProver interpolation solver used * @return counterexample info with predicated information * @throws CPAException */ private CounterexampleTraceInfo buildCounterexampleTrace(List<BooleanFormula> f, List<AbstractState> pAbstractionStates, Set<ARGState> elementsOnPath, boolean computeInterpolants) throws CPAException, InterruptedException { // Check feasibility of counterexample logger.log(Level.FINEST, "Checking feasibility of counterexample trace"); satCheckTimer.start(); boolean spurious; List<T> itpGroupsIds; List<Triple<BooleanFormula, AbstractState, Integer>> orderedFormulas; if (pAbstractionStates.isEmpty()) { pAbstractionStates = new ArrayList<>(Collections.<AbstractState>nCopies(f.size(), null)); } assert pAbstractionStates.size() == f.size() : "each pathFormula must end with an abstract State"; try { if (getUsefulBlocks) { f = Collections.unmodifiableList(getUsefulBlocks(f)); } if (dumpInterpolationProblems) { dumpInterpolationProblem(f); } // re-order formulas if needed orderedFormulas = orderFormulas(f, pAbstractionStates); assert orderedFormulas.size() == f.size(); // initialize all interpolation group ids with "null" itpGroupsIds = new ArrayList<>(Collections.<T>nCopies(f.size(), null)); // ask solver for satisfiability spurious = checkInfeasabilityOfTrace(orderedFormulas, itpGroupsIds); assert itpGroupsIds.size() == f.size(); assert !itpGroupsIds.contains(null); // has to be filled completely } finally { satCheckTimer.stop(); } logger.log(Level.FINEST, "Counterexample trace is", (spurious ? "infeasible" : "feasible")); // Get either interpolants or error path information CounterexampleTraceInfo info; if (spurious) { if (computeInterpolants) { List<BooleanFormula> interpolants = getInterpolants(this, itpGroupsIds, orderedFormulas); if (verifyInterpolants) { verifyInterpolants(interpolants, f, itpProver); } if (logger.wouldBeLogged(Level.ALL)) { int i = 1; for (BooleanFormula itp : interpolants) { logger.log(Level.ALL, "For step", i++, "got:", "interpolant", itp); } } info = CounterexampleTraceInfo.infeasible(interpolants); } else { info = CounterexampleTraceInfo.infeasibleNoItp(); } } else { // this is a real bug info = getErrorPath(f, itpProver, elementsOnPath); } logger.log(Level.ALL, "Counterexample information:", info); return info; } /** * Check the satisfiability of a list of formulas, using them in the given order. * This method honors the {@link #incrementalCheck} configuration option. * It also updates the SMT solver stack and the {@link #currentlyAssertedFormulas} * list that is used if {@link #reuseInterpolationEnvironment} is enabled. * * @param traceFormulas The list of formulas to check, each formula with its index of where it should be added in the list of interpolation groups. * @param itpGroupsIds The list where to store the references to the interpolation groups. This is just a list of 'identifiers' for the formulas. * @param itpProver The solver to use. * @return True if the formulas are unsatisfiable. * @throws InterruptedException */ private boolean checkInfeasabilityOfTrace( List<Triple<BooleanFormula, AbstractState, Integer>> traceFormulas, List<T> itpGroupsIds) throws InterruptedException, SolverException { // first identify which formulas are already on the solver stack, // which formulas need to be removed from the solver stack, // and which formulas need to be added to the solver stack ListIterator<Triple<BooleanFormula, AbstractState, Integer>> todoIterator = traceFormulas .listIterator(); ListIterator<Triple<BooleanFormula, AbstractState, T>> assertedIterator = currentlyAssertedFormulas .listIterator(); int firstBadIndex = -1; // index of first mis-matching formula in both lists while (assertedIterator.hasNext()) { Triple<BooleanFormula, AbstractState, T> assertedFormula = assertedIterator.next(); if (!todoIterator.hasNext()) { firstBadIndex = assertedIterator.previousIndex(); break; } Triple<BooleanFormula, AbstractState, Integer> todoFormula = todoIterator.next(); if (todoFormula.getFirst().equals(assertedFormula.getFirst())) { // formula is already in solver stack in correct location @SuppressWarnings("unchecked") T itpGroupId = assertedFormula.getThird(); itpGroupsIds.set(todoFormula.getThird(), itpGroupId); } else { firstBadIndex = assertedIterator.previousIndex(); // rewind iterator by one so that todoFormula will be added to stack todoIterator.previous(); break; } } // now remove the formulas from the solver stack where necessary if (firstBadIndex == -1) { // solver stack was already empty, nothing do to } else if (firstBadIndex == 0) { // Create a new environment instead of cleaning up the old one // if no formulas need to be reused. itpProver.close(); itpProver = newEnvironment(); currentlyAssertedFormulas.clear(); } else { assert firstBadIndex > 0; // list with all formulas on solver stack that we need to remove // (= remaining formulas in currentlyAssertedFormulas list) List<Triple<BooleanFormula, AbstractState, T>> toDeleteFormulas = currentlyAssertedFormulas .subList(firstBadIndex, currentlyAssertedFormulas.size()); // remove formulas from solver stack for (int i = 0; i < toDeleteFormulas.size(); i++) { itpProver.pop(); } toDeleteFormulas.clear(); // this removes from currentlyAssertedFormulas reusedFormulasOnSolverStack += currentlyAssertedFormulas.size(); } boolean isStillFeasible = true; if (incrementalCheck && !currentlyAssertedFormulas.isEmpty()) { if (itpProver.isUnsat()) { isStillFeasible = false; } } // add remaining formulas to the solver stack while (todoIterator.hasNext()) { Triple<BooleanFormula, AbstractState, Integer> p = todoIterator.next(); BooleanFormula f = p.getFirst(); AbstractState state = p.getSecond(); int index = p.getThird(); assert itpGroupsIds.get(index) == null; T itpGroupId = itpProver.push(f); itpGroupsIds.set(index, itpGroupId); currentlyAssertedFormulas.add(Triple.of(f, state, itpGroupId)); if (incrementalCheck && isStillFeasible && !bfmgr.isTrue(f)) { if (itpProver.isUnsat()) { // We need to iterate through the full loop // to add all formulas, but this prevents us from doing further sat checks. isStillFeasible = false; } } } assert Iterables.elementsEqual(from(traceFormulas).transform(Triple.getProjectionToFirst()), from(currentlyAssertedFormulas).transform(Triple.getProjectionToFirst())); if (incrementalCheck) { // we did unsat checks return !isStillFeasible; } else { return itpProver.isUnsat(); } } private void close() { itpProver.close(); itpProver = null; currentlyAssertedFormulas.clear(); } } }