de.uka.ilkd.key.proof.reference.ReferenceSearcher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of keyext.caching Show documentation
Show all versions of keyext.caching Show documentation
Caching of provable nodes to make proving with KeY faster.
The newest version!
/* This file is part of KeY - https://key-project.org
* KeY is licensed under the GNU General Public License Version 2
* SPDX-License-Identifier: GPL-2.0-only */
package de.uka.ilkd.key.proof.reference;
import java.util.*;
import java.util.stream.Collectors;
import javax.swing.*;
import de.uka.ilkd.key.logic.Semisequent;
import de.uka.ilkd.key.logic.Sequent;
import de.uka.ilkd.key.logic.SequentFormula;
import de.uka.ilkd.key.logic.Term;
import de.uka.ilkd.key.proof.Node;
import de.uka.ilkd.key.proof.Proof;
import de.uka.ilkd.key.rule.NoPosTacletApp;
import de.uka.ilkd.key.rule.merge.CloseAfterMerge;
import org.key_project.slicing.DependencyTracker;
import org.key_project.slicing.analysis.AnalysisResults;
/**
* Utility class for proof caching.
*
* @author Arne Keller
*/
public final class ReferenceSearcher {
private ReferenceSearcher() {
}
/**
* Try to find a closed branch in another proof that is equivalent to the newNode
.
*
* @param previousProofs old proofs
* @param newNode new node (must be an open goal)
* @return a reference (or null, if none found)
*/
public static ClosedBy findPreviousProof(List previousProofs, Node newNode) {
// first verify that the new node does not contain any terms that depend on external
// influences
if (!suitableForCloseByReference(newNode)) {
return null;
}
for (int i = 0; i < previousProofs.size(); i++) {
Proof p = previousProofs.get(i);
if (p == newNode.proof()) {
continue; // doesn't make sense to cache in the same proof
}
// conservative check: all user-defined rules in a previous proof
// have to also be available in the new proof
var proofFile = p.getProofFile() != null ? p.getProofFile().toString() : "////";
var tacletIndex = p.allGoals().head().ruleAppIndex().tacletIndex();
var newTacletIndex = newNode.proof().allGoals().head().ruleAppIndex().tacletIndex();
Set newTaclets = newTacletIndex.allNoPosTacletApps();
var tacletsOk = true;
for (var taclet : tacletIndex.allNoPosTacletApps().stream()
.filter(x -> x.taclet().getOrigin() != null
&& x.taclet().getOrigin().contains(proofFile))
.toList()) {
if (newTaclets.stream().noneMatch(newTaclet -> Objects
.equals(taclet.taclet().toString(), newTaclet.taclet().toString()))) {
tacletsOk = false;
break;
}
}
if (!tacletsOk) {
continue;
}
// only search in compatible proofs
if (!p.getSettings().getChoiceSettings()
.equals(newNode.proof().getSettings().getChoiceSettings())) {
continue;
}
Set checkedNodes = new HashSet<>();
Queue nodesToCheck = p.closedGoals().stream().map(goal -> {
// first, find the initial node in this branch
Node n = goal.node();
if (n.parent() != null
&& n.parent().getAppliedRuleApp().rule() == CloseAfterMerge.INSTANCE) {
// cannot reference this kind of branch
return null;
}
return n;
}).filter(Objects::nonNull).collect(Collectors.toCollection(ArrayDeque::new));
var depTracker = p.lookup(DependencyTracker.class);
AnalysisResults results = null;
// only try to get analysis results if it is a pure proof
if (depTracker != null && p.closedGoals().stream()
.noneMatch(x -> x.node().lookup(ClosedBy.class) != null)) {
try {
results = depTracker.analyze(true, false);
} catch (Exception ignored) {
// if the analysis for some reason fails, we simply proceed as usual
}
}
while (!nodesToCheck.isEmpty()) {
// for each node, check that the sequent in the reference is
// a subset of the new sequent
Node n = nodesToCheck.remove();
if (checkedNodes.contains(n) || !n.isClosed()) {
continue;
}
checkedNodes.add(n);
// find the first node in the branch
while (n.parent() != null && n.parent().childrenCount() == 1) {
n = n.parent();
}
if (n.parent() != null) {
nodesToCheck.add(n.parent());
}
Sequent seq = n.sequent();
if (results != null) {
seq = results.reduceSequent(n);
}
Semisequent ante = seq.antecedent();
Semisequent succ = seq.succedent();
Semisequent anteNew = newNode.sequent().antecedent();
Semisequent succNew = newNode.sequent().succedent();
if (!containedIn(anteNew, ante) || !containedIn(succNew, succ)) {
continue;
}
Set toSkip = new HashSet<>();
if (results != null) {
// computed skipped nodes by iterating through all nodes
AnalysisResults finalResults = results;
n.subtreeIterator().forEachRemaining(x -> {
if (!finalResults.usefulSteps.contains(x)) {
toSkip.add(x);
}
});
}
return new ClosedBy(p, n, toSkip);
}
}
return null;
}
/**
* Check whether all formulas in {@code subset} are conatined in {@code superset}.
*
* @param superset Semisequent supposed to contain {@code subset}
* @param subset Semisequent supposed to be in {@code superset}
* @return whether all formulas are present
*/
private static boolean containedIn(Semisequent superset, Semisequent subset) {
for (SequentFormula sf : subset) {
boolean found = false;
for (SequentFormula sf2 : superset) {
if (sf2.equalsModProofIrrelevancy(sf)) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
}
/**
* Check whether a node is suitable for closing by reference.
* This is not the case if it contains any terms influenced by external factors:
* Java blocks or program methods (query terms).
*
* @param node the node to check
* @return whether it can be closed by reference
*/
public static boolean suitableForCloseByReference(Node node) {
ProgramMethodFinder f = new ProgramMethodFinder();
Sequent seq = node.sequent();
for (int i = 1; i <= seq.size(); i++) {
Term term = seq.getFormulabyNr(i).formula();
// first, check for a java block
if (term.containsJavaBlockRecursive()) {
// not suitable for caching
return false;
}
// then, check for program methods
// (may expand differently depending on Java code associated with proofs)
term.execPreOrder(f);
if (f.getFoundProgramMethod()) {
return false;
}
}
return true;
}
}