/*
* Copyright (C) 2021 Daniel Dietsch (dietsch@informatik.uni-freiburg.de)
* Copyright (C) 2021 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.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import de.uni_freiburg.informatik.ultimate.core.model.services.ILogger;
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.smtlibutils.IncrementalPlicationChecker.Validity;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.ManagedScript;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.ManagedScript.ILockHolderWithVoluntaryLockRelease;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.TermClassifier;
import de.uni_freiburg.informatik.ultimate.util.CoreUtil;
import de.uni_freiburg.informatik.ultimate.util.InCaReCounter;
import de.uni_freiburg.informatik.ultimate.util.Lazy;
import de.uni_freiburg.informatik.ultimate.util.ReflectionUtil;
import de.uni_freiburg.informatik.ultimate.util.ReflectionUtil.Reflected;
import de.uni_freiburg.informatik.ultimate.util.VMUtils;
import de.uni_freiburg.informatik.ultimate.util.datastructures.DataStructureUtils;
import de.uni_freiburg.informatik.ultimate.util.statistics.AbstractStatisticsDataProvider;
import de.uni_freiburg.informatik.ultimate.util.statistics.IStatisticsDataProvider;
import de.uni_freiburg.informatik.ultimate.util.statistics.KeyType;
import de.uni_freiburg.informatik.ultimate.util.statistics.StatisticsAggregator;
import de.uni_freiburg.informatik.ultimate.util.statistics.StatisticsAggregator.Statistics;
import de.uni_freiburg.informatik.ultimate.util.statistics.TimeTracker;
/**
*
* An {@link IHoareTripleChecker} that chains different {@link IHoareTripleChecker}s together. It queries the first
* checker, and if the answer is different from {@link Validity#UNKNOWN} and {@link Validity#NOT_CHECKED}, it is
* returned. If it is {@link Validity#UNKNOWN} or {@link Validity#NOT_CHECKED}, the next checker is queried, until no
* checker remains.
*
* Benefits as opposed to other {@link IHoareTripleChecker}s include automatic statistics, protection, and review.
*
* TODO: Integrate Cache from {@link CachingHoareTripleChecker}
*
* @author Daniel Dietsch (dietsch@informatik.uni-freiburg.de)
*
*/
public class ChainingHoareTripleChecker implements IHoareTripleChecker {
private static final int LONG_CHECK_THRESHOLD_MS = 1000;
private final List mHtcs;
private final ILogger mLogger;
private static final Predicate SKIP_PRED = a -> false;
private static final Predicate SKIP_ACT = a -> false;
private ChainingHoareTripleChecker(final ILogger logger) {
this(logger, Collections.emptyList());
}
private ChainingHoareTripleChecker(final ILogger logger, final List list) {
mLogger = logger;
mHtcs = list;
DataStructureUtils.filteredCast(mHtcs.stream(), ReviewedProtectedHtc.class)
.forEach(a -> a.setLockRelease(this::releaseLock));
}
@Override
public void releaseLock() {
mHtcs.stream().forEach(IHoareTripleChecker::releaseLock);
}
@Override
public Validity checkInternal(final IPredicate pre, final IInternalAction act, final IPredicate succ) {
for (final IWrappedHoareTripleChecker htc : mHtcs) {
final Validity val = htc.checkInternal(pre, act, succ);
htc.getUnderlying().reportLongChecks(pre, act, succ, val);
if (val == Validity.INVALID || val == Validity.VALID) {
return val;
}
}
return Validity.UNKNOWN;
}
@Override
public Validity checkCall(final IPredicate pre, final ICallAction act, final IPredicate succ) {
for (final IWrappedHoareTripleChecker htc : mHtcs) {
final Validity val = htc.checkCall(pre, act, succ);
htc.getUnderlying().reportLongChecks(pre, act, succ, val);
if (val == Validity.INVALID || val == Validity.VALID) {
return val;
}
}
return Validity.UNKNOWN;
}
@Override
public Validity checkReturn(final IPredicate preLin, final IPredicate preHier, final IReturnAction act,
final IPredicate succ) {
for (final IWrappedHoareTripleChecker htc : mHtcs) {
final Validity val = htc.checkReturn(preLin, preHier, act, succ);
htc.getUnderlying().reportLongChecks(preLin, preHier, act, succ, val);
if (val == Validity.INVALID || val == Validity.VALID) {
return val;
}
}
return Validity.UNKNOWN;
}
@Override
public IStatisticsDataProvider getStatistics() {
final StatisticsAggregator aggr = new StatisticsAggregator();
for (final IWrappedHoareTripleChecker htc : mHtcs) {
aggr.aggregateBenchmarkData(htc.getStatistics());
final ProtectedHtc under = htc.getUnderlying();
final String prefix = under.mHtc.getClass().getSimpleName();
aggr.aggregateBenchmarkData(prefix, under.mStats);
}
return aggr;
}
@Override
public String toString() {
return mHtcs.stream().map(IHoareTripleChecker::toString).collect(Collectors.joining(", "));
}
/**
* Create a {@link ChainingHoareTripleChecker} with a single underlying {@link IHoareTripleChecker}.
*
* @param htc
* The underlying {@link IHoareTripleChecker}.
* @return A new {@link ChainingHoareTripleChecker}.
*/
public static ChainingHoareTripleChecker with(final ILogger logger, final IHoareTripleChecker htc) {
return new ChainingHoareTripleChecker(logger).andThen(htc);
}
public static ChainingHoareTripleChecker empty(final ILogger logger) {
return new ChainingHoareTripleChecker(logger);
}
/**
* Add another {@link IHoareTripleChecker} to the {@link ChainingHoareTripleChecker}. The added
* {@link IHoareTripleChecker} will be queried when all the {@link IHoareTripleChecker}s before answered
* {@link Validity#UNKNOWN}.
*
* @param htc
* The added {@link IHoareTripleChecker}.
* @return A new {@link ChainingHoareTripleChecker}.
*/
public ChainingHoareTripleChecker andThen(final IHoareTripleChecker htc) {
if (htc instanceof ChainingHoareTripleChecker) {
final ChainingHoareTripleChecker chain = (ChainingHoareTripleChecker) htc;
ChainingHoareTripleChecker rtr = this;
for (final IWrappedHoareTripleChecker wHtc : chain.mHtcs) {
rtr = rtr.add(wHtc.copy());
}
return rtr;
}
return add(new ProtectedHtc(mLogger, htc, SKIP_ACT, SKIP_PRED));
}
/**
* Protect the last added {@link IHoareTripleChecker} from {@link IPredicate}s with a {@link Predicate} pred. All
* {@link IPredicate}s of a Hoare triple are tested by pred, and if pred tests true, no Hoare triple check is
* performed and {@link Validity#NOT_CHECKED} is returned immediately. If the last {@link IHoareTripleChecker} is
* already protected by a {@link Predicate}, it will now be protected by a disjunction of {@link Predicate}s.
*
* @param pred
* A predicate that tests predicates of Hoare triples. If it returns true, no Hoare triple check is
* performed.
* @return A new {@link ChainingHoareTripleChecker} where the last {@link IHoareTripleChecker} is protected.
*/
public ChainingHoareTripleChecker predicatesProtectedBy(final Predicate pred) {
return replaceLast(createFromLastProtected(last -> new ProtectedHtc(last.mLogger, last.mHtc,
last.mPredActionProtection, last.mPredPredicateProtection.or(pred))));
}
/**
* Protect the last added {@link IHoareTripleChecker} from {@link IAction}s with a {@link Predicate} pred. The
* {@link IAction} of a Hoare triple is tested by pred, and if pred tests true, no Hoare triple check is performed
* and {@link Validity#NOT_CHECKED} is returned immediately. If the last {@link IHoareTripleChecker} is already
* protected by a {@link Predicate}, it will now be protected by a disjunction of {@link Predicate}s.
*
* @param pred
* A predicate that tests actions of Hoare triples. If it returns true, no Hoare triple check is
* performed.
* @return A new {@link ChainingHoareTripleChecker} where the last {@link IHoareTripleChecker} is protected.
*/
public ChainingHoareTripleChecker actionsProtectedBy(final Predicate pred) {
return replaceLast(createFromLastProtected(last -> new ProtectedHtc(last.mLogger, last.mHtc,
last.mPredActionProtection.or(pred), last.mPredPredicateProtection)));
}
/**
* Add a {@link IHoareTripleChecker} that reviews the results of the last {@link IHoareTripleChecker} when
* assertions are enabled.
*
* @param reviewHtc
* The {@link IHoareTripleChecker} that performs the reviewing.
* @return A new {@link ChainingHoareTripleChecker} where the last {@link IHoareTripleChecker} is reviewed.
*/
public ChainingHoareTripleChecker reviewWith(final IHoareTripleChecker reviewHtc) {
if (VMUtils.areAssertionsEnabled()) {
return replaceLast(createFromLastReview(reviewHtc));
}
return this;
}
private ChainingHoareTripleChecker replaceLast(final IWrappedHoareTripleChecker replacement) {
final List list = new ArrayList<>(mHtcs.size());
list.addAll(mHtcs);
list.set(list.size() - 1, replacement);
return new ChainingHoareTripleChecker(mLogger, list);
}
private ChainingHoareTripleChecker add(final IWrappedHoareTripleChecker add) {
final List list = new ArrayList<>(mHtcs.size() + 1);
list.addAll(mHtcs);
list.add(add);
return new ChainingHoareTripleChecker(mLogger, list);
}
private IWrappedHoareTripleChecker getLast() {
return mHtcs.get(mHtcs.size() - 1);
}
private IWrappedHoareTripleChecker createFromLastReview(final IHoareTripleChecker reviewHtc) {
final ProtectedHtc pHtc = getLast().getUnderlying();
return new ReviewedProtectedHtc(reviewHtc,
new ProtectedHtc(pHtc.mLogger, pHtc.mHtc, pHtc.mPredActionProtection, pHtc.mPredPredicateProtection));
}
private IWrappedHoareTripleChecker createFromLastProtected(final Function replace) {
final IWrappedHoareTripleChecker last = getLast();
return last.replaceUnderlying(replace.apply(last.getUnderlying()));
}
private interface IWrappedHoareTripleChecker extends IHoareTripleChecker {
ProtectedHtc getUnderlying();
IWrappedHoareTripleChecker replaceUnderlying(final ProtectedHtc underlying);
/**
* Create a copy that keeps everything except statistics.
*/
IWrappedHoareTripleChecker copy();
}
private static class ReviewedProtectedHtc implements IWrappedHoareTripleChecker {
private final IHoareTripleChecker mReviewHtc;
private final ProtectedHtc mHtc;
private ILockHolderWithVoluntaryLockRelease mFunReleaseLocks;
/**
*
* @param reviewHtc
* The {@link IHoareTripleChecker} used for reviewing.
* @param htc
* The actual {@link IHoareTripleChecker}
* @param funReleaseLocks
* A function that releases {@link ManagedScript} locks before reviewing.
*/
public ReviewedProtectedHtc(final IHoareTripleChecker reviewHtc, final ProtectedHtc htc) {
mReviewHtc = Objects.requireNonNull(reviewHtc);
mHtc = Objects.requireNonNull(htc);
}
private void setLockRelease(final ILockHolderWithVoluntaryLockRelease funReleaseLocks) {
mFunReleaseLocks = funReleaseLocks;
}
@Override
public void releaseLock() {
mHtc.releaseLock();
mReviewHtc.releaseLock();
}
@Override
public Validity checkInternal(final IPredicate pre, final IInternalAction act, final IPredicate succ) {
final Validity val = mHtc.checkInternal(pre, act, succ);
if (val == Validity.NOT_CHECKED || val == Validity.UNKNOWN) {
return val;
}
assert reviewInductiveInternal(pre, act, succ, val);
return val;
}
@Override
public Validity checkCall(final IPredicate pre, final ICallAction act, final IPredicate succ) {
final Validity val = mHtc.checkCall(pre, act, succ);
if (val == Validity.NOT_CHECKED || val == Validity.UNKNOWN) {
return val;
}
assert reviewInductiveCall(pre, act, succ, val);
return val;
}
@Override
public Validity checkReturn(final IPredicate preLin, final IPredicate preHier, final IReturnAction act,
final IPredicate succ) {
final Validity val = mHtc.checkReturn(preLin, preHier, act, succ);
if (val == Validity.NOT_CHECKED || val == Validity.UNKNOWN) {
return val;
}
assert reviewInductiveReturn(preLin, preHier, act, succ, val);
return val;
}
private boolean reviewInductiveInternal(final IPredicate state, final IInternalAction act,
final IPredicate succ, final Validity result) {
mFunReleaseLocks.releaseLock();
final Validity reviewResult = mReviewHtc.checkInternal(state, act, succ);
if (reviewResult(result, reviewResult)) {
mReviewHtc.releaseLock();
return true;
}
throw createAssertionError(result, reviewResult);
}
private boolean reviewInductiveCall(final IPredicate state, final ICallAction act, final IPredicate succ,
final Validity result) {
mFunReleaseLocks.releaseLock();
final Validity reviewResult = mReviewHtc.checkCall(state, act, succ);
if (reviewResult(result, reviewResult)) {
mReviewHtc.releaseLock();
return true;
}
throw createAssertionError(result, reviewResult);
}
private boolean reviewInductiveReturn(final IPredicate state, final IPredicate hier, final IReturnAction act,
final IPredicate succ, final Validity result) {
mFunReleaseLocks.releaseLock();
final Validity reviewResult = mReviewHtc.checkReturn(state, hier, act, succ);
if (reviewResult(result, reviewResult)) {
mReviewHtc.releaseLock();
return true;
}
throw createAssertionError(result, reviewResult);
}
/**
* Return true if results are compatible or one is UNKNOWN
*/
private static boolean reviewResult(final Validity result, final Validity reviewResult) {
switch (result) {
case VALID:
return reviewResult == Validity.VALID || reviewResult == Validity.UNKNOWN;
case INVALID:
return reviewResult == Validity.INVALID || reviewResult == Validity.UNKNOWN;
case UNKNOWN:
case NOT_CHECKED:
return true;
default:
throw new UnsupportedOperationException("Unknown validity: " + result);
}
}
private AssertionError createAssertionError(final Validity result, final Validity reviewResult) {
return new AssertionError(String.format(
"HoareTripleChecker results differ between %s (result: %s) and %s (result: %s)",
mHtc.mHtc.getClass().getSimpleName(), result, mReviewHtc.getClass().getSimpleName(), reviewResult));
}
@Override
public IStatisticsDataProvider getStatistics() {
return mHtc.getStatistics();
}
@Override
public String toString() {
return mHtc.toString();
}
@Override
public ProtectedHtc getUnderlying() {
return mHtc;
}
@Override
public IWrappedHoareTripleChecker replaceUnderlying(final ProtectedHtc underlying) {
return new ReviewedProtectedHtc(mReviewHtc, underlying);
}
@Override
public IWrappedHoareTripleChecker copy() {
return new ReviewedProtectedHtc(mReviewHtc, new ProtectedHtc(mHtc.getUnderlying()));
}
}
/**
*
* @author Daniel Dietsch (dietsch@informatik.uni-freiburg.de)
*
*/
private static class ProtectedHtc implements IWrappedHoareTripleChecker {
private final IHoareTripleChecker mHtc;
private final Predicate mPredPredicateProtection;
private final Predicate mPredActionProtection;
private final ValidityInCaReCounter mStats;
private final ILogger mLogger;
public ProtectedHtc(final ILogger logger, final IHoareTripleChecker htc,
final Predicate predActionProtection, final Predicate predPredicateProtection) {
mLogger = Objects.requireNonNull(logger);
mHtc = Objects.requireNonNull(htc);
mPredPredicateProtection = Objects.requireNonNull(predPredicateProtection);
mPredActionProtection = Objects.requireNonNull(predActionProtection);
mStats = new ValidityInCaReCounter();
}
public ProtectedHtc(final ProtectedHtc old) {
this(old.mLogger, old.mHtc, old.mPredActionProtection, old.mPredPredicateProtection);
}
@Override
public void releaseLock() {
mHtc.releaseLock();
}
@Override
public Validity checkInternal(final IPredicate pre, final IInternalAction act, final IPredicate succ) {
final Validity val;
mStats.start();
if (mPredPredicateProtection.test(pre) || mPredPredicateProtection.test(succ)
|| mPredActionProtection.test(act)) {
val = Validity.NOT_CHECKED;
} else {
val = mHtc.checkInternal(pre, act, succ);
}
mStats.stop();
mStats.inc(val, act);
return val;
}
@Override
public Validity checkCall(final IPredicate pre, final ICallAction act, final IPredicate succ) {
final Validity val;
mStats.start();
if (mPredPredicateProtection.test(pre) || mPredPredicateProtection.test(succ)
|| mPredActionProtection.test(act)) {
val = Validity.NOT_CHECKED;
} else {
val = mHtc.checkCall(pre, act, succ);
}
mStats.stop();
mStats.inc(val, act);
return val;
}
@Override
public Validity checkReturn(final IPredicate preLin, final IPredicate preHier, final IReturnAction act,
final IPredicate succ) {
final Validity val;
mStats.start();
if (mPredPredicateProtection.test(preLin) || mPredPredicateProtection.test(preHier)
|| mPredPredicateProtection.test(succ) || mPredActionProtection.test(act)) {
val = Validity.NOT_CHECKED;
} else {
val = mHtc.checkReturn(preLin, preHier, act, succ);
}
mStats.stop();
mStats.inc(val, act);
return val;
}
@Override
public IStatisticsDataProvider getStatistics() {
return mHtc.getStatistics();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append(mHtc.getClass().getSimpleName());
sb.append(" [");
sb.append(mStats);
sb.append("]");
return sb.toString();
}
@Override
public ProtectedHtc getUnderlying() {
return this;
}
@Override
public IWrappedHoareTripleChecker replaceUnderlying(final ProtectedHtc underlying) {
return underlying;
}
public void reportLongChecks(final IPredicate pre, final IAction act, final IPredicate succ,
final Validity result) {
reportLongChecks(pre, null, act, succ, result);
}
public void reportLongChecks(final IPredicate preLin, final IPredicate preHier, final IAction act,
final IPredicate succ, final Validity result) {
final long delta = mStats.mTime.lastDelta(TimeUnit.MILLISECONDS);
if (delta > LONG_CHECK_THRESHOLD_MS) {
final TermClassifier tc = new TermClassifier();
tc.checkTerm(act.getTransformula().getFormula());
tc.checkTerm(preLin.getFormula());
tc.checkTerm(succ.getFormula());
if (preHier != null) {
tc.checkTerm(preHier.getFormula());
}
mLogger.warn(
"%s took %s for a HTC check with result %s. Formula has sorts %s, hasArrays=%s, hasNonlinArith=%s, quantifiers %s",
mHtc.getClass().getSimpleName(), CoreUtil.humanReadableTime(delta, TimeUnit.MILLISECONDS, 2),
result, tc.getOccuringSortNames(), tc.hasArrays(), tc.hasNonlinearArithmetic(),
tc.getOccuringQuantifiers());
}
}
@Override
public IWrappedHoareTripleChecker copy() {
return new ProtectedHtc(this);
}
}
private static final class ValidityInCaReCounter extends AbstractStatisticsDataProvider {
@Reflected(prettyName = "Valid")
@Statistics(type = KeyType.IN_CA_RE_COUNTER)
private final InCaReCounter mValid = new InCaReCounter();
@Reflected(prettyName = "Invalid")
@Statistics(type = KeyType.IN_CA_RE_COUNTER)
private final InCaReCounter mInvalid = new InCaReCounter();
@Reflected(prettyName = "Unknown")
@Statistics(type = KeyType.IN_CA_RE_COUNTER)
private final InCaReCounter mUnknown = new InCaReCounter();
@Reflected(prettyName = "Unchecked")
@Statistics(type = KeyType.IN_CA_RE_COUNTER)
private final InCaReCounter mUnchecked = new InCaReCounter();
@Reflected(prettyName = "Time")
@Statistics(type = KeyType.TT_TIMER)
private final TimeTracker mTime = new TimeTracker();
@Reflected(excluded = true)
private static final Lazy> FIELDS =
new Lazy<>(() -> ReflectionUtil.instanceFields(ValidityInCaReCounter.class).stream()
.filter(f -> f.getAnnotation(Statistics.class) != null).collect(Collectors.toList()));
public ValidityInCaReCounter() {
for (final Field f : FIELDS.get()) {
final Statistics annot = f.getAnnotation(Statistics.class);
declare(ReflectionUtil.fieldPrettyName(f), () -> annot.type().convert(ReflectionUtil.access(this, f)),
annot.type());
}
}
public void start() {
mTime.start();
}
public void stop() {
mTime.stop();
}
public void inc(final Validity val, final IAction action) {
switch (val) {
case INVALID:
inc(action, mInvalid);
break;
case NOT_CHECKED:
inc(action, mUnchecked);
break;
case UNKNOWN:
inc(action, mUnknown);
break;
case VALID:
inc(action, mValid);
break;
default:
throw new UnsupportedOperationException("Unknown validity " + val);
}
}
private void inc(final IAction act, final InCaReCounter cnt) {
if (act instanceof IInternalAction) {
cnt.incIn();
} else if (act instanceof ICallAction) {
cnt.incCa();
} else if (act instanceof IReturnAction) {
cnt.incRe();
} else {
throw new UnsupportedOperationException("Unknown action " + act.getClass());
}
}
@Override
public String toString() {
return getBenchmarkType().prettyprintBenchmarkData(this);
}
}
}