/*
* Copyright (C) 2015 Matthias Heizmann (heizmann@informatik.uni-freiburg.de)
* Copyright (C) 2022 Dominik Klumpp (klumpp@informatik.uni-freiburg.de)
* Copyright (C) 2015-2022 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.Objects;
import java.util.Set;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.ModifiableGlobalsTable;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IAction;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.transitions.UnmodifiableTransFormula;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.transitions.UnmodifiableTransFormula.Infeasibility;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.variables.IProgramOldVar;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.smt.predicates.IPredicate;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.smt.predicates.IPredicateCoverageChecker;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.IncrementalPlicationChecker.Validity;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.SmtUtils;
import de.uni_freiburg.informatik.ultimate.logic.ApplicationTerm;
import de.uni_freiburg.informatik.ultimate.logic.FunctionSymbol;
import de.uni_freiburg.informatik.ultimate.logic.Term;
import de.uni_freiburg.informatik.ultimate.util.datastructures.DataStructureUtils;
/**
* Abstract class for data-flow based Hoare triple checks. Subclasses are checks for internal, call, and return. Because
* we can only override methods with the same signature (in Java) we use the 3-parameter-signature for return (with
* hierarchical state) and use null as hierarchical state for call and internal.
*
* @param
* The type of actions for which Hoare triples are checked.
*/
public abstract class SdHoareTripleCheckHelper {
protected final IPredicateCoverageChecker mCoverage;
protected final IPredicate mFalsePredicate;
protected final IPredicate mTruePredicate;
protected final HoareTripleCheckerStatisticsGenerator mStatistics;
protected final ModifiableGlobalsTable mModifiableGlobals;
/**
* @param sdHoareTripleChecker
*/
SdHoareTripleCheckHelper(final IPredicateCoverageChecker coverage, final IPredicate falsePredicate,
final IPredicate truePredicate, final HoareTripleCheckerStatisticsGenerator statistics,
final ModifiableGlobalsTable modifiableGlobals) {
mCoverage = Objects.requireNonNull(coverage);
mFalsePredicate = falsePredicate;
mTruePredicate = truePredicate;
mStatistics = statistics;
mModifiableGlobals = modifiableGlobals;
}
public Validity check(final IPredicate preLin, final IPredicate preHier, final L act, final IPredicate succ) {
if (act.getTransformula().isInfeasible() == Infeasibility.INFEASIBLE) {
return Validity.VALID;
}
boolean unknownCoverage = false;
// check if preLin is equivalent to false
final Boolean isPreFalse = isCovered(preLin, mFalsePredicate);
if (isPreFalse == null) {
unknownCoverage = true;
} else if (isPreFalse) {
return Validity.VALID;
}
// check if preHier is equivalent to false
if (preHier != null) {
final Boolean isPreHierFalse = isCovered(preHier, mFalsePredicate);
if (isPreHierFalse == null) {
unknownCoverage = true;
} else if (isPreHierFalse) {
return Validity.VALID;
}
}
// check if succ is equivalent to true
final Boolean isSuccTrue = isCovered(mTruePredicate, succ);
if (isSuccTrue == null) {
unknownCoverage = true;
} else if (isSuccTrue) {
return Validity.VALID;
}
if (unknownCoverage) {
return Validity.UNKNOWN;
}
final boolean isInductiveSelfloop = isInductiveSelfloop(preLin, preHier, act, succ);
if (isInductiveSelfloop) {
return Validity.VALID;
}
if (SmtUtils.isFalseLiteral(succ.getFormula())) {
final Validity toFalse = sdecToFalse(preLin, preHier, act);
if (toFalse == null) {
// we are unable to determine validity with SD checks
assert sdec(preLin, preHier, act, succ) == null : "inconsistent check results";
return Validity.UNKNOWN;
}
switch (toFalse) {
case INVALID:
return Validity.INVALID;
case NOT_CHECKED:
throw new AssertionError("unchecked predicate");
case UNKNOWN:
throw new AssertionError("this case should have been filtered out before");
case VALID:
throw new AssertionError("this case should have been filtered out before");
default:
throw new AssertionError("unknown case");
}
}
final Validity general;
if (SdHoareTripleChecker.LAZY_CHECKS) {
general = sdLazyEc(preLin, preHier, act, succ);
} else {
general = sdec(preLin, preHier, act, succ);
}
if (general != null) {
switch (general) {
case INVALID:
return Validity.INVALID;
case NOT_CHECKED:
throw new AssertionError("unchecked predicate");
case UNKNOWN:
throw new AssertionError("this case should have been filtered out before");
case VALID:
return Validity.VALID;
default:
throw new AssertionError("unknown case");
}
}
return Validity.UNKNOWN;
}
private Boolean isCovered(final IPredicate lhs, final IPredicate rhs) {
switch (mCoverage.isCovered(lhs, rhs)) {
case INVALID:
return false;
case NOT_CHECKED:
throw new AssertionError("unchecked predicate");
case UNKNOWN:
return null;
case VALID:
return true;
default:
throw new AssertionError("unknown case");
}
}
public abstract Validity sdecToFalse(IPredicate preLin, IPredicate preHier, L act);
public abstract boolean isInductiveSelfloop(IPredicate preLin, IPredicate preHier, L act, IPredicate succ);
public abstract Validity sdec(IPredicate preLin, IPredicate preHier, L act, IPredicate succ);
public abstract Validity sdLazyEc(IPredicate preLin, IPredicate preHier, L act, IPredicate succ);
protected static boolean varsDisjointFromInVars(final IPredicate state, final UnmodifiableTransFormula tf) {
return DataStructureUtils.haveEmptyIntersection(state.getVars(), tf.getInVars().keySet());
}
protected static boolean varsDisjointFromOutVars(final IPredicate state, final UnmodifiableTransFormula tf) {
return DataStructureUtils.haveEmptyIntersection(state.getVars(), tf.getOutVars().keySet());
}
protected static boolean varsDisjointFromAssignedVars(final IPredicate state, final UnmodifiableTransFormula tf) {
return DataStructureUtils.haveEmptyIntersection(state.getVars(), tf.getAssignedVars());
}
protected static boolean disjointFunctions(final IPredicate state, final UnmodifiableTransFormula tf) {
// TODO Use tf.getNonTheoryFunctions() once it is supported
return DataStructureUtils.haveEmptyIntersection(state.getFuns(), (Set) tf.getNonTheoryConsts());
}
protected static boolean disjointFunctions(final IPredicate pred1, final IPredicate pred2) {
return DataStructureUtils.haveEmptyIntersection(pred1.getFuns(), pred2.getFuns());
}
/**
* Checks if the predicate contains both a non-modifiable global variable x and old(x).
*
* In such cases, we can often not not give a verdict, because a formula such as (= x |old(x)|) always evaluates to
* true within the context of the procedure, but this is not detected by mCoverage (because it does not take
* procedure context into account). Similarly, a formula such as (distinct x |old(x)|) essentially behaves like it
* is inconsistent, but mCoverage does not detect this.
*
* @param proc
* The procedure within which the predicate is evaluated
* @param p
* The predicate to examine
* @return (as described above)
*/
protected boolean containsConflictingNonModifiableOldVars(final String proc, final IPredicate p) {
for (final var pv : p.getVars()) {
if (pv.isOldvar()) {
final var pnov = ((IProgramOldVar) pv).getNonOldVar();
if (!mModifiableGlobals.isModifiable(pnov, proc) && p.getVars().contains(pnov)) {
return true;
}
}
}
return false;
}
/**
* Returns true if the formula of this predicate is an or-term or an ite-term.
*/
protected static boolean isOrIteFormula(final IPredicate p) {
final Term formula = p.getFormula();
if (formula instanceof ApplicationTerm) {
final ApplicationTerm appTerm = (ApplicationTerm) formula;
final FunctionSymbol symbol = appTerm.getFunction();
return "or".equals(symbol.getName()) || "ite".equals(symbol.getName());
}
return false;
}
}