/* * Copyright (C) 2015 Matthias Heizmann (heizmann@informatik.uni-freiburg.de) * Copyright (C) 2015 University of Freiburg * * This file is part of the ULTIMATE ModelCheckerUtils Library. * * The ULTIMATE ModelCheckerUtils Library 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 ModelCheckerUtils Library 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 ModelCheckerUtils Library. If not, see . * * Additional permission under GNU GPL version 3 section 7: * If you modify the ULTIMATE ModelCheckerUtils Library, 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 ModelCheckerUtils Library grant you additional permission * to convey the resulting work. */ package de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.hoaretriple; import java.util.HashSet; import java.util.Set; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.CfgSmtToolkit; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.ModifiableGlobalsTable; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.ICallAction; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IInternalAction; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IReturnAction; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.transitions.UnmodifiableTransFormula; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.variables.IProgramNonOldVar; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.variables.IProgramVar; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.smt.predicates.IPredicate; import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.smt.predicates.PredicateUtils; import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.IncrementalPlicationChecker; import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.IncrementalPlicationChecker.Validity; import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.ManagedScript; import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.SmtUtils; import de.uni_freiburg.informatik.ultimate.logic.ReasonUnknown; import de.uni_freiburg.informatik.ultimate.logic.SMTLIBException; import de.uni_freiburg.informatik.ultimate.logic.Script; import de.uni_freiburg.informatik.ultimate.logic.Script.LBool; import de.uni_freiburg.informatik.ultimate.logic.Term; import de.uni_freiburg.informatik.ultimate.util.datastructures.ScopedHashMap; public class MonolithicHoareTripleChecker implements IHoareTripleChecker { private final CfgSmtToolkit mCsToolkit; private final ManagedScript mManagedScript; private final ModifiableGlobalsTable mModifiableGlobals; private final HoareTripleCheckerStatisticsGenerator mHoareTripleCheckerStatistics; private ScopedHashMap mIndexedConstants; // TODO: Remove these variables or use them private int mTrivialSatQueries = 0; private int mNontrivialSatQueries = 0; private long mSatCheckSolverTime = 0; private final int mTrivialCoverQueries = 0; private final int mNontrivialCoverQueries = 0; private long mSatCheckTime = 0; public MonolithicHoareTripleChecker(final CfgSmtToolkit csToolkit) { mCsToolkit = csToolkit; mManagedScript = csToolkit.getManagedScript(); mModifiableGlobals = csToolkit.getModifiableGlobalsTable(); mHoareTripleCheckerStatistics = new HoareTripleCheckerStatisticsGenerator(); } @Override public Validity checkInternal(final IPredicate pre, final IInternalAction act, final IPredicate succ) { mHoareTripleCheckerStatistics.continueEdgeCheckerTime(); final Validity result = IncrementalPlicationChecker.convertLBool2Validity(isInductive(pre, act, succ)); mHoareTripleCheckerStatistics.stopEdgeCheckerTime(); switch (result) { case INVALID: mHoareTripleCheckerStatistics.getSolverCounterSat().incIn(); break; case UNKNOWN: mHoareTripleCheckerStatistics.getSolverCounterUnknown().incIn(); break; case VALID: mHoareTripleCheckerStatistics.getSolverCounterUnsat().incIn(); break; default: throw new AssertionError("unknown case"); } return result; } @Override public Validity checkCall(final IPredicate pre, final ICallAction act, final IPredicate succ) { mHoareTripleCheckerStatistics.continueEdgeCheckerTime(); final Validity result = IncrementalPlicationChecker.convertLBool2Validity(isInductiveCall(pre, act, succ)); mHoareTripleCheckerStatistics.stopEdgeCheckerTime(); switch (result) { case INVALID: mHoareTripleCheckerStatistics.getSolverCounterSat().incCa(); break; case UNKNOWN: mHoareTripleCheckerStatistics.getSolverCounterUnknown().incCa(); break; case VALID: mHoareTripleCheckerStatistics.getSolverCounterUnsat().incCa(); break; default: throw new AssertionError("unknown case"); } return result; } @Override public Validity checkReturn(final IPredicate preLin, final IPredicate preHier, final IReturnAction act, final IPredicate succ) { mHoareTripleCheckerStatistics.continueEdgeCheckerTime(); final Validity result = IncrementalPlicationChecker.convertLBool2Validity(isInductiveReturn(preLin, preHier, act, succ)); mHoareTripleCheckerStatistics.stopEdgeCheckerTime(); switch (result) { case INVALID: mHoareTripleCheckerStatistics.getSolverCounterSat().incRe(); break; case UNKNOWN: mHoareTripleCheckerStatistics.getSolverCounterUnknown().incRe(); break; case VALID: mHoareTripleCheckerStatistics.getSolverCounterUnsat().incRe(); break; default: throw new AssertionError("unknown case"); } return result; } @Override public HoareTripleCheckerStatisticsGenerator getStatistics() { return mHoareTripleCheckerStatistics; } @Override public void releaseLock() { // do nothing, since objects of this class do not lock the solver } public LBool isInductive(final IPredicate ps1, final IInternalAction ta, final IPredicate ps2) { return isInductive(ps1, ta, ps2, false); } // TODO less renaming possible e.g. keep var names in ps1 /** * Check if post(sf1,tf) is a subset of sf2. * * @param ps1 * a set of states represented by a Predicate * @param tf * a transition relation represented by a TransFormula * @param ps2 * a set of states represented by a Predicate * @return The result of a theorem prover query, where SMT_UNSAT means yes to our question, SMT_SAT means no to our * question and SMT_UNKNOWN means that the theorem prover was not able give an answer to our question. */ public LBool isInductive(final IPredicate ps1, final IInternalAction ta, final IPredicate ps2, final boolean expectUnsat) { mManagedScript.lock(this); final long startTime = System.nanoTime(); if (isDontCare(ps1) || isDontCare(ps2)) { mTrivialSatQueries++; mManagedScript.unlock(this); return Script.LBool.UNKNOWN; } if (SmtUtils.isFalseLiteral(ps1.getFormula()) || SmtUtils.isTrueLiteral(ps2.getFormula())) { mTrivialSatQueries++; mManagedScript.unlock(this); return Script.LBool.UNSAT; } // if (simpleSelfloopCheck(ps1, ta, ps2)) { // mTrivialSatQueries = mTrivialSatQueries + 10000000; // return Script.LBool.UNSAT; // } final UnmodifiableTransFormula tf = ta.getTransformula(); final String procPred = ta.getPrecedingProcedure(); final String procSucc = ta.getSucceedingProcedure(); // assert proc.equals(ta.getSucceedingProcedure()) : "different procedure before and after"; final Set modifiableGlobalsPred = mModifiableGlobals.getModifiedBoogieVars(procPred); final Set modifiableGlobalsSucc = mModifiableGlobals.getModifiedBoogieVars(procSucc); final LBool result = PredicateUtils.isInductiveHelper(mManagedScript.getScript(), ps1, ps2, tf, modifiableGlobalsPred, modifiableGlobalsSucc); if (expectUnsat) { assert result == Script.LBool.UNSAT || result == Script.LBool.UNKNOWN : "From " + ps1.getFormula().toStringDirect() + "Statements " + ta.toString() + "To " + ps2.getFormula().toStringDirect() + "Not inductive!"; } mSatCheckTime += System.nanoTime() - startTime; mManagedScript.unlock(this); return result; } public LBool isInductiveCall(final IPredicate ps1, final ICallAction ta, final IPredicate ps2) { return isInductiveCall(ps1, ta, ps2, false); } public LBool isInductiveCall(final IPredicate ps1, final ICallAction ta, final IPredicate ps2, final boolean expectUnsat) { mManagedScript.lock(this); final long startTime = System.nanoTime(); if (isDontCare(ps1) || isDontCare(ps2)) { mTrivialSatQueries++; mManagedScript.unlock(this); return Script.LBool.UNKNOWN; } if (SmtUtils.isFalseLiteral(ps1.getFormula()) || SmtUtils.isTrueLiteral(ps2.getFormula())) { mTrivialSatQueries++; mManagedScript.unlock(this); return Script.LBool.UNSAT; } mManagedScript.getScript().push(1); mIndexedConstants = new ScopedHashMap<>(); // OldVars not renamed if modifiable. // All variables get index 0. final String caller = ta.getPrecedingProcedure(); final Set modifiableGlobalsCaller = mModifiableGlobals.getModifiedBoogieVars(caller); final Term ps1renamed = PredicateUtils.formulaWithIndexedVars(ps1, new HashSet(0), 4, 0, Integer.MIN_VALUE, null, -5, 0, mIndexedConstants, mManagedScript.getScript(), modifiableGlobalsCaller); final UnmodifiableTransFormula tf = ta.getLocalVarsAssignment(); final Set assignedVars = new HashSet<>(); final Term fTrans = PredicateUtils.formulaWithIndexedVars(tf, 0, 1, assignedVars, mIndexedConstants, mManagedScript.getScript()); // OldVars renamed to index 0 // GlobalVars renamed to index 0 // Other vars get index 1 final String callee = ta.getSucceedingProcedure(); final Set modifiableGlobalsCallee = mModifiableGlobals.getModifiedBoogieVars(callee); final Term ps2renamed = PredicateUtils.formulaWithIndexedVars(ps2, new HashSet(0), 4, 1, 0, null, 23, 0, mIndexedConstants, mManagedScript.getScript(), modifiableGlobalsCallee); // We want to return true if (fState1 && fTrans)-> fState2 is valid // This is the case if (fState1 && fTrans && !fState2) is unsatisfiable Term f = SmtUtils.not(mManagedScript.getScript(), ps2renamed); f = SmtUtils.and(mManagedScript.getScript(), fTrans, f); f = SmtUtils.and(mManagedScript.getScript(), ps1renamed, f); final LBool result = checkSatisfiable(f); mIndexedConstants = null; mManagedScript.getScript().pop(1); if (expectUnsat) { assert result == Script.LBool.UNSAT || result == Script.LBool.UNKNOWN : "call statement not inductive"; } mSatCheckTime += System.nanoTime() - startTime; mManagedScript.unlock(this); return result; } public LBool isInductiveReturn(final IPredicate ps1, final IPredicate psk, final IReturnAction ta, final IPredicate ps2) { return isInductiveReturn(ps1, psk, ta, ps2, false); } public LBool isInductiveReturn(final IPredicate ps1, final IPredicate psk, final IReturnAction ta, final IPredicate ps2, final boolean expectUnsat) { mManagedScript.lock(this); final long startTime = System.nanoTime(); if (isDontCare(ps1) || isDontCare(ps2) || isDontCare(psk)) { mTrivialSatQueries++; mManagedScript.unlock(this); return Script.LBool.UNKNOWN; } if (SmtUtils.isFalseLiteral(ps1.getFormula()) || SmtUtils.isFalseLiteral(psk.getFormula()) || SmtUtils.isTrueLiteral(ps2.getFormula())) { mTrivialSatQueries++; mManagedScript.unlock(this); return Script.LBool.UNSAT; } mManagedScript.getScript().push(1); mIndexedConstants = new ScopedHashMap<>(); final UnmodifiableTransFormula tfReturn = ta.getAssignmentOfReturn(); final Set assignedVarsOnReturn = new HashSet<>(); final Term fReturn = PredicateUtils.formulaWithIndexedVars(tfReturn, 1, 2, assignedVarsOnReturn, mIndexedConstants, mManagedScript.getScript()); // fReturn = (new FormulaUnLet()).unlet(fReturn); final UnmodifiableTransFormula tfCall = ta.getLocalVarsAssignmentOfCall(); final Set assignedVarsOnCall = new HashSet<>(); final Term fCall = PredicateUtils.formulaWithIndexedVars(tfCall, 0, 1, assignedVarsOnCall, mIndexedConstants, mManagedScript.getScript()); // fCall = (new FormulaUnLet()).unlet(fCall); final String callee = ta.getPrecedingProcedure(); final Set modifiableGlobalsCallee = mModifiableGlobals.getModifiedBoogieVars(callee); final String caller = ta.getSucceedingProcedure(); final Set modifiableGlobalsCaller = mModifiableGlobals.getModifiedBoogieVars(caller); // oldVars not renamed if modifiable // other variables get index 0 final Term pskrenamed = PredicateUtils.formulaWithIndexedVars(psk, new HashSet(0), 23, 0, Integer.MIN_VALUE, null, 23, 0, mIndexedConstants, mManagedScript.getScript(), modifiableGlobalsCaller); // oldVars get index 0 // modifiable globals get index 2, unless they are modified on return then they get index 1 // not modifiable globals get index 0 // other variables get index 1 final Set modifiableGlobalsAssignedOnReturn = new HashSet<>(); for (final IProgramVar assignedVar : tfReturn.getOutVars().keySet()) { if (modifiableGlobalsCallee.contains(assignedVar)) { modifiableGlobalsAssignedOnReturn.add(assignedVar); } } final Term ps1renamed = PredicateUtils.formulaWithIndexedVars(ps1, modifiableGlobalsAssignedOnReturn, 1, 1, 0, modifiableGlobalsCallee, 2, 0, mIndexedConstants, mManagedScript.getScript(), modifiableGlobalsCallee); // oldVars not renamed if modifiable // modifiable globals get index 2 // variables assigned on return get index 2 // other variables get index 0 final Term ps2renamed = PredicateUtils.formulaWithIndexedVars(ps2, assignedVarsOnReturn, 2, 0, Integer.MIN_VALUE, modifiableGlobalsCallee, 2, 0, mIndexedConstants, mManagedScript.getScript(), modifiableGlobalsCaller); // We want to return true if (fState1 && fTrans)-> fState2 is valid // This is the case if (fState1 && fTrans && !fState2) is unsatisfiable Term f = SmtUtils.not(mManagedScript.getScript(), ps2renamed); f = SmtUtils.and(mManagedScript.getScript(), fReturn, f); f = SmtUtils.and(mManagedScript.getScript(), ps1renamed, f); f = SmtUtils.and(mManagedScript.getScript(), fCall, f); f = SmtUtils.and(mManagedScript.getScript(), pskrenamed, f); final LBool result = checkSatisfiable(f); mManagedScript.getScript().pop(1); mIndexedConstants = null; if (expectUnsat) { assert result == Script.LBool.UNSAT || result == Script.LBool.UNKNOWN : "From " + ps1.getFormula().toStringDirect() + "Caller " + psk.getFormula().toStringDirect() + "Statements " + ta + "To " + ps2.getFormula().toStringDirect() + "Not inductive!"; } mSatCheckTime += System.nanoTime() - startTime; mManagedScript.unlock(this); return result; } public LBool assertTerm(final Term term) { final long startTime = System.nanoTime(); LBool result = null; result = mManagedScript.getScript().assertTerm(term); mSatCheckSolverTime += System.nanoTime() - startTime; return result; } LBool checkSatisfiable(final Term f) { final long startTime = System.nanoTime(); LBool result = null; try { assertTerm(f); } catch (final SMTLIBException e) { if (e.getMessage().equals("Unsupported non-linear arithmetic")) { return LBool.UNKNOWN; } throw e; } result = mManagedScript.getScript().checkSat(); mSatCheckSolverTime += System.nanoTime() - startTime; mNontrivialSatQueries++; if (result == LBool.UNKNOWN) { final Object info = mManagedScript.getScript().getInfo(":reason-unknown"); if (!(info instanceof ReasonUnknown)) { mManagedScript.getScript().getInfo(":reason-unknown"); } final ReasonUnknown reason = (ReasonUnknown) info; switch (reason) { case CRASHED: throw new AssertionError("Solver crashed"); case MEMOUT: throw new AssertionError("Solver out of memory, " + "solver might be in inconsistent state"); default: break; } } return result; } private static boolean isDontCare(final IPredicate ps2) { // yet I don't know how to check for don't care // avoid proper implementation if not needed return false; } }