/*
* Copyright (C) 2015 Matthias Heizmann (heizmann@informatik.uni-freiburg.de)
* Copyright (C) 2015 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.hoaretriple;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
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.lib.modelcheckerutils.ModelCheckerUtils;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IAction;
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.smt.predicates.IPredicate;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.smt.predicates.IPredicateUnifier;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.IncrementalPlicationChecker.Validity;
import de.uni_freiburg.informatik.ultimate.util.InCaReCounter;
import de.uni_freiburg.informatik.ultimate.util.datastructures.IterableIntersection;
import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.NestedMap2;
import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.NestedMap3;
import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.NestedMap4;
import de.uni_freiburg.informatik.ultimate.util.statistics.IStatisticsDataProvider;
/**
* {@link IHoareTripleChecker} that caches already computed results. I also
* utilizes the cache to do the following checks. Let us assume we want to check
* the Hoare triple `{φ} st {ψ}`.
*
If the cache contains a valid Hoare triple `{φ'} st {ψ'}` such that φ⇒φ'
* and ψ'⇒ψ we return VALID.
* If the cache contains an invalid Hoare triple `{φ'} st {ψ'}` such that
* φ'⇒φ and ψ⇒ψ' we return INVALID.
*
* @author Matthias Heizmann (heizmann@informatik.uni-freiburg.de)
*
*/
public class CachingHoareTripleChecker implements IHoareTripleChecker {
protected static final boolean UNKNOWN_IF_SOME_EXTENDED_CHECK_IS_UNKNOWN = false;
private static final String UNKNOWN_CASE = "unknown case";
private static final String CASE_MUST_NOT_OCCUR = "case must not occur";
protected final ILogger mLogger;
protected final IHoareTripleChecker mComputingHoareTripleChecker;
protected final IPredicateUnifier mPredicateUnifier;
private final InCaReCounter mResultFromSolver = new InCaReCounter();
private final InCaReCounter mResultFromCache = new InCaReCounter();
private final InCaReCounter mResultFromExtendedCacheCheck = new InCaReCounter();
private final HoareTripleCheckerCache mCache;
public CachingHoareTripleChecker(final IUltimateServiceProvider services,
final IHoareTripleChecker computingHoareTripleChecker, final IPredicateUnifier predicateUnifer) {
this(services, computingHoareTripleChecker, predicateUnifer, new HoareTripleCheckerCache());
}
public CachingHoareTripleChecker(final IUltimateServiceProvider services,
final IHoareTripleChecker computingHoareTripleChecker, final IPredicateUnifier predicateUnifier,
final HoareTripleCheckerCache initialCache) {
mLogger = services.getLoggingService().getLogger(ModelCheckerUtils.PLUGIN_ID);
mComputingHoareTripleChecker = Objects.requireNonNull(computingHoareTripleChecker);
mPredicateUnifier = Objects.requireNonNull(predicateUnifier);
mCache = Objects.requireNonNull(initialCache);
}
@Override
public Validity checkInternal(final IPredicate pre, final IInternalAction act, final IPredicate succ) {
Validity result = mCache.getInternal(pre, act, succ);
if (result == null) {
result = extendedBinaryCacheCheck(pre, act, succ, mCache.getInternalCache());
if (result == null) {
result = mComputingHoareTripleChecker.checkInternal(pre, act, succ);
mResultFromSolver.incIn();
} else {
mResultFromExtendedCacheCheck.incIn();
}
mCache.putInternal(pre, act, succ, result);
} else {
mResultFromCache.incIn();
}
return result;
}
@Override
public Validity checkCall(final IPredicate pre, final ICallAction act, final IPredicate succ) {
Validity result = mCache.getCall(pre, act, succ);
if (result == null) {
result = extendedBinaryCacheCheck(pre, act, succ, mCache.getCallCache());
if (result == null) {
result = mComputingHoareTripleChecker.checkCall(pre, act, succ);
mResultFromSolver.incCa();
} else {
mResultFromExtendedCacheCheck.incCa();
}
mCache.putCall(pre, act, succ, result);
} else {
mResultFromCache.incCa();
}
return result;
}
@Override
public Validity checkReturn(final IPredicate preLin, final IPredicate preHier, final IReturnAction act,
final IPredicate succ) {
Validity result = mCache.getReturn(preLin, preHier, act, succ);
if (result == null) {
result = extendedReturnCacheCheck(preLin, preHier, act, succ, mCache.getReturnCache());
if (result == null) {
result = mComputingHoareTripleChecker.checkReturn(preLin, preHier, act, succ);
mResultFromSolver.incRe();
} else {
mResultFromExtendedCacheCheck.incRe();
}
mCache.putReturn(preLin, preHier, act, succ, result);
} else {
mResultFromCache.incRe();
}
return result;
}
@Override
public IStatisticsDataProvider getStatistics() {
return mComputingHoareTripleChecker.getStatistics();
}
public IHoareTripleChecker getProtectedHoareTripleChecker() {
return mComputingHoareTripleChecker;
}
@Override
public void releaseLock() {
mComputingHoareTripleChecker.releaseLock();
}
@Override
public String toString() {
return mComputingHoareTripleChecker.toString();
}
public HoareTripleCheckerCache getCache() {
return mCache;
}
/**
* Cache check for internal actions and call actions. Both have only one
* predecessor and one successor.
*/
private Validity extendedBinaryCacheCheck(final IPredicate pre, final IAction act, final IPredicate succ,
final NestedMap3 binaryCache) {
final NestedMap2 pred2succ = binaryCache.get(act);
if (pred2succ == null) {
// cache does not store information for our action
return null;
}
boolean someResultWasUnknown = false;
{
final Set strongerThanPre = mPredicateUnifier.getCoverageRelation().getCoveredPredicates(pre);
final Set weakerThanSucc = mPredicateUnifier.getCoverageRelation().getCoveringPredicates(succ);
final Iterable preds = new IterableIntersection<>(pred2succ.keySet(), strongerThanPre);
for (final IPredicate strongP : preds) {
final Map succ2Val = pred2succ.get(strongP);
final Iterable succs = new IterableIntersection<>(succ2Val.keySet(), weakerThanSucc);
for (final IPredicate weakS : succs) {
final Validity validity = evaluteResultStrongerThanPreAndWeakerThanSucc(succ2Val.get(weakS));
if (validity == null) {
continue;
}
switch (validity) {
case INVALID:
return Validity.INVALID;
case UNKNOWN:
someResultWasUnknown = true;
break;
case NOT_CHECKED:
case VALID:
throw new AssertionError(CASE_MUST_NOT_OCCUR);
default:
throw new AssertionError(UNKNOWN_CASE);
}
}
}
}
{
final Set weakerThanPre = mPredicateUnifier.getCoverageRelation().getCoveringPredicates(pre);
final Set strongerThanSucc = mPredicateUnifier.getCoverageRelation().getCoveredPredicates(succ);
final Iterable preds = new IterableIntersection<>(pred2succ.keySet(), weakerThanPre);
for (final IPredicate weakP : preds) {
final Map succ2Val = pred2succ.get(weakP);
final Iterable succs = new IterableIntersection<>(succ2Val.keySet(), strongerThanSucc);
for (final IPredicate strongS : succs) {
final Validity validity = evaluteResultWeakerThanPreAndStrongerThanSucc(succ2Val.get(strongS));
if (validity == null) {
continue;
}
switch (validity) {
case VALID:
return Validity.VALID;
case UNKNOWN:
someResultWasUnknown = true;
break;
case NOT_CHECKED:
case INVALID:
throw new AssertionError(CASE_MUST_NOT_OCCUR);
default:
throw new AssertionError(UNKNOWN_CASE);
}
}
}
}
if (UNKNOWN_IF_SOME_EXTENDED_CHECK_IS_UNKNOWN && someResultWasUnknown) {
// we pass this result as a warning that the corresponding check might be
// expensive
return Validity.UNKNOWN;
}
return null;
}
protected Validity extendedReturnCacheCheck(final IPredicate preLin, final IPredicate preHier, final IAction act,
final IPredicate succ,
final NestedMap4 ternaryCache) {
final NestedMap3 preHier2preLin2succ = ternaryCache.get(act);
if (preHier2preLin2succ == null) {
// cache does not store information for our action
return null;
}
boolean someResultWasUnknown = false;
{
final Set strongerThanPreHier = mPredicateUnifier.getCoverageRelation()
.getCoveredPredicates(preHier);
final Set strongerThanPreLin = mPredicateUnifier.getCoverageRelation()
.getCoveredPredicates(preLin);
final Set weakerThanSucc = mPredicateUnifier.getCoverageRelation().getCoveringPredicates(succ);
final Iterable predsHier = new IterableIntersection<>(preHier2preLin2succ.keySet(),
strongerThanPreHier);
for (final IPredicate strongPreHier : predsHier) {
final NestedMap2 preLin2succ2Val = preHier2preLin2succ
.get(strongPreHier);
final Iterable predsLin = new IterableIntersection<>(preLin2succ2Val.keySet(),
strongerThanPreLin);
for (final IPredicate strongPreLin : predsLin) {
final Map succ2Val = preLin2succ2Val.get(strongPreLin);
final Iterable succs = new IterableIntersection<>(succ2Val.keySet(), weakerThanSucc);
for (final IPredicate weakS : succs) {
final Validity validity = evaluteResultStrongerThanPreAndWeakerThanSucc(succ2Val.get(weakS));
if (validity == null) {
continue;
}
switch (validity) {
case INVALID:
return Validity.INVALID;
case UNKNOWN:
someResultWasUnknown = true;
break;
case NOT_CHECKED:
case VALID:
throw new AssertionError(CASE_MUST_NOT_OCCUR);
default:
throw new AssertionError(UNKNOWN_CASE);
}
}
}
}
}
{
final Set weakerThanPreHier = mPredicateUnifier.getCoverageRelation()
.getCoveringPredicates(preHier);
final Set weakerThanPreLin = mPredicateUnifier.getCoverageRelation()
.getCoveringPredicates(preLin);
final Set strongerThanSucc = mPredicateUnifier.getCoverageRelation().getCoveredPredicates(succ);
final Iterable predsHier = new IterableIntersection<>(preHier2preLin2succ.keySet(),
weakerThanPreHier);
for (final IPredicate weakPreHier : predsHier) {
final NestedMap2 preLin2succ2Val = preHier2preLin2succ
.get(weakPreHier);
final Iterable predsLin = new IterableIntersection<>(preLin2succ2Val.keySet(),
weakerThanPreLin);
for (final IPredicate weakPreLin : predsLin) {
final Map succ2Val = preLin2succ2Val.get(weakPreLin);
final Iterable succs = new IterableIntersection<>(succ2Val.keySet(), strongerThanSucc);
for (final IPredicate weakS : succs) {
final Validity validity = evaluteResultWeakerThanPreAndStrongerThanSucc(succ2Val.get(weakS));
if (validity == null) {
continue;
}
switch (validity) {
case VALID:
return Validity.VALID;
case UNKNOWN:
someResultWasUnknown = true;
break;
case NOT_CHECKED:
case INVALID:
throw new AssertionError(CASE_MUST_NOT_OCCUR);
default:
throw new AssertionError(UNKNOWN_CASE);
}
}
}
}
}
if (UNKNOWN_IF_SOME_EXTENDED_CHECK_IS_UNKNOWN && someResultWasUnknown) {
// we pass this result as a warning that the corresponding check might be
// expensive
return Validity.UNKNOWN;
}
return null;
}
private Validity evaluteResultWeakerThanPreAndStrongerThanSucc(final Validity validity) {
switch (validity) {
case VALID:
// pass result, if Hoare triple holds for weaker pre and for stronger succ,
// it also does not hold for original pre/succ
return validity;
case UNKNOWN:
// we pass this result as a warning that the corresponding check might be
// expensive
return validity;
case INVALID:
// information does not help
return null;
case NOT_CHECKED:
return null;
default:
throw new AssertionError(UNKNOWN_CASE);
}
}
private Validity evaluteResultStrongerThanPreAndWeakerThanSucc(final Validity validity) {
switch (validity) {
case VALID:
// information does not help
return null;
case UNKNOWN:
// we pass this result as a warning that the corresponding check might be
// expensive
return validity;
case INVALID:
// pass result, if Hoare triple does not hold for stronger pre and for weaker
// succ,
// it also does not hold for original pre/succ
return validity;
case NOT_CHECKED:
return null;
default:
throw new AssertionError(UNKNOWN_CASE);
}
}
}