org.sosy_lab.cpachecker.cpa.bam.ARGSubtreeRemover.java Source code

Java tutorial

Introduction

Here is the source code for org.sosy_lab.cpachecker.cpa.bam.ARGSubtreeRemover.java

Source

/*
 *  CPAchecker is a tool for configurable software verification.
 *  This file is part of CPAchecker.
 *
 *  Copyright (C) 2007-2014  Dirk Beyer
 *  All rights reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *
 *  CPAchecker web page:
 *    http://cpachecker.sosy-lab.org
 */
package org.sosy_lab.cpachecker.cpa.bam;

import static org.sosy_lab.cpachecker.util.AbstractStates.extractLocation;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;

import org.sosy_lab.common.Pair;
import org.sosy_lab.common.log.LogManager;
import org.sosy_lab.common.time.Timer;
import org.sosy_lab.cpachecker.cfa.blocks.Block;
import org.sosy_lab.cpachecker.cfa.blocks.BlockPartitioning;
import org.sosy_lab.cpachecker.cfa.model.CFANode;
import org.sosy_lab.cpachecker.core.interfaces.AbstractState;
import org.sosy_lab.cpachecker.core.interfaces.Precision;
import org.sosy_lab.cpachecker.core.interfaces.Reducer;
import org.sosy_lab.cpachecker.core.reachedset.ReachedSet;
import org.sosy_lab.cpachecker.core.reachedset.ReachedSetFactory;
import org.sosy_lab.cpachecker.core.reachedset.UnmodifiableReachedSet;
import org.sosy_lab.cpachecker.cpa.arg.ARGPath;
import org.sosy_lab.cpachecker.cpa.arg.ARGReachedSet;
import org.sosy_lab.cpachecker.cpa.arg.ARGState;
import org.sosy_lab.cpachecker.util.Precisions;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;

public class ARGSubtreeRemover {

    private final BlockPartitioning partitioning;
    private final Reducer wrappedReducer;
    private final BAMCache bamCache;
    private final ReachedSetFactory reachedSetFactory;
    private final Map<AbstractState, ReachedSet> abstractStateToReachedSet;
    private final Timer removeCachedSubtreeTimer;
    private final LogManager logger;

    public ARGSubtreeRemover(BlockPartitioning partitioning, Reducer reducer, BAMCache bamCache,
            ReachedSetFactory reachedSetFactory, Map<AbstractState, ReachedSet> abstractStateToReachedSet,
            Timer removeCachedSubtreeTimer, LogManager logger) {
        this.partitioning = partitioning;
        this.wrappedReducer = reducer;
        this.bamCache = bamCache;
        this.reachedSetFactory = reachedSetFactory;
        this.abstractStateToReachedSet = abstractStateToReachedSet;
        this.removeCachedSubtreeTimer = removeCachedSubtreeTimer;
        this.logger = logger;
    }

    void removeSubtree(ARGReachedSet mainReachedSet, ARGPath pPath, ARGState element,
            List<Precision> pNewPrecisions, List<Predicate<? super Precision>> pNewPrecisionTypes,
            Map<ARGState, ARGState> pPathElementToReachedState) {

        List<ARGState> path = trimPath(pPath, element);
        assert path.get(path.size() - 1).equals(element);
        assert path.size() >= 2; // extreme case of length 2: [root, target]

        List<ARGState> relevantCallNodes = getRelevantDefinitionNodes(path);
        assert path.containsAll(relevantCallNodes) : "only nodes of path are relevant";
        assert relevantCallNodes.get(0) == path.get(0) : "root should be relevant";
        assert relevantCallNodes.size() >= 1 : "at least the main-function should be open at the target-state";

        Set<Pair<ARGState, ARGState>> neededRemoveCachedSubtreeCalls = new LinkedHashSet<>();

        //iterate from root to element and remove all subtrees for subgraph calls
        for (int i = 0; i < relevantCallNodes.size() - 1; i++) { // ignore root and the last element
            final ARGState pathElement = relevantCallNodes.get(i);
            final ARGState nextElement = relevantCallNodes.get(i + 1);
            neededRemoveCachedSubtreeCalls.add(Pair.of(pPathElementToReachedState.get(pathElement),
                    pPathElementToReachedState.get(nextElement)));
        }

        if (bamCache.doesAggressiveCaching()) {
            ensureExactCacheHitsOnPath(mainReachedSet, pPath, element, pNewPrecisions, pPathElementToReachedState,
                    neededRemoveCachedSubtreeCalls);
        }

        final ARGState lastRelevantNode = pPathElementToReachedState.get(Iterables.getLast(relevantCallNodes));
        final ARGState target = pPathElementToReachedState.get(element);
        for (final Pair<ARGState, ARGState> removeCachedSubtreeArguments : neededRemoveCachedSubtreeCalls) {
            final List<Precision> newPrecisions;
            if (removeCachedSubtreeArguments.getSecond() == lastRelevantNode) { // last iteration
                newPrecisions = pNewPrecisions;
            } else {
                ReachedSet nextReachedSet = abstractStateToReachedSet.get(removeCachedSubtreeArguments.getSecond());
                assert nextReachedSet != null : "call-state does not match reachedset";
                if (target.getParents().contains(nextReachedSet.getFirstState())) {
                    newPrecisions = pNewPrecisions;
                } else {
                    newPrecisions = null; // ignore newPrecisions for all iterations except the last one
                }
            }
            removeCachedSubtree(removeCachedSubtreeArguments.getFirst(), removeCachedSubtreeArguments.getSecond(),
                    newPrecisions, pNewPrecisionTypes);
        }

        removeCachedSubtree(pPathElementToReachedState.get(Iterables.getLast(relevantCallNodes)),
                pPathElementToReachedState.get(element), pNewPrecisions, pNewPrecisionTypes);

        // the main-reachedset contains only the root, exit-states and targets.
        // we assume, that the current refinement was caused by a target-state.
        final ARGState lastState = (ARGState) mainReachedSet.asReachedSet().getLastState();
        assert lastState.isTarget();
        mainReachedSet.removeSubtree(lastState);
    }

    /**
     * @return <code>true</code>, if the precision of the first element of the given reachedSet changed by this operation; <code>false</code>, otherwise.
     */
    private static boolean removeSubtree(ReachedSet reachedSet, ARGState argElement, List<Precision> newPrecisions,
            List<Predicate<? super Precision>> pPrecisionTypes) {
        ARGReachedSet argReachSet = new ARGReachedSet(reachedSet);
        boolean updateCacheNeeded = argElement.getParents().contains(reachedSet.getFirstState());
        removeSubtree(argReachSet, argElement, newPrecisions, pPrecisionTypes);
        return updateCacheNeeded;
    }

    static void removeSubtree(ARGReachedSet reachedSet, ARGState argElement) {
        if (BAMTransferRelation.isHeadOfMainFunction(extractLocation(argElement))) {
            reachedSet.removeSubtree((ARGState) reachedSet.asReachedSet().getLastState());
        } else {
            reachedSet.removeSubtree(argElement);
        }
    }

    private static void removeSubtree(ARGReachedSet reachedSet, ARGState argElement, List<Precision> newPrecisions,
            List<Predicate<? super Precision>> pPrecisionTypes) {
        if (newPrecisions == null || newPrecisions.size() == 0) {
            removeSubtree(reachedSet, argElement);
        } else {
            reachedSet.removeSubtree(argElement, newPrecisions, pPrecisionTypes);
        }
    }

    private void removeCachedSubtree(ARGState rootState, ARGState removeElement, List<Precision> pNewPrecisions,
            List<Predicate<? super Precision>> pPrecisionTypes) {
        removeCachedSubtreeTimer.start();

        try {
            CFANode rootNode = extractLocation(rootState);
            Block rootSubtree = partitioning.getBlockForCallNode(rootNode);

            logger.log(Level.FINER, "Remove cached subtree for", removeElement, "(rootNode: ", rootNode,
                    ") issued with precision", pNewPrecisions);

            AbstractState reducedRootState = wrappedReducer.getVariableReducedState(rootState, rootSubtree,
                    rootNode);
            ReachedSet reachedSet = abstractStateToReachedSet.get(rootState);

            if (removeElement.isDestroyed()) {
                logger.log(Level.FINER, "state was destroyed before");
                //apparently, removeElement was removed due to prior deletions
                return;
            }

            assert reachedSet.contains(removeElement) : "removing state from wrong reachedSet: " + removeElement;

            Precision removePrecision = reachedSet.getPrecision(removeElement);
            ArrayList<Precision> newReducedRemovePrecision = null; // TODO newReducedRemovePrecision: NullPointerException 20 lines later!

            if (pNewPrecisions != null) {
                newReducedRemovePrecision = new ArrayList<>(1);

                for (int i = 0; i < pNewPrecisions.size(); i++) {
                    removePrecision = Precisions.replaceByType(removePrecision, pNewPrecisions.get(i),
                            pPrecisionTypes.get(i));
                }

                newReducedRemovePrecision
                        .add(wrappedReducer.getVariableReducedPrecision(removePrecision, rootSubtree));
                pPrecisionTypes = new ArrayList<>();
                pPrecisionTypes.add(Predicates.instanceOf(newReducedRemovePrecision.get(0).getClass()));
            }

            assert !removeElement.getParents().isEmpty();

            Precision reducedRootPrecision = reachedSet.getPrecision(reachedSet.getFirstState());
            bamCache.removeReturnEntry(reducedRootState, reducedRootPrecision, rootSubtree);
            bamCache.removeBlockEntry(reducedRootState, reducedRootPrecision, rootSubtree);

            logger.log(Level.FINEST,
                    "Removing subtree, adding a new cached entry, and removing the former cached entries");

            if (removeSubtree(reachedSet, removeElement, newReducedRemovePrecision, pPrecisionTypes)) {
                logger.log(Level.FINER, "updating cache");
                bamCache.updatePrecisionForEntry(reducedRootState, reducedRootPrecision, rootSubtree,
                        newReducedRemovePrecision.get(0));
            }

        } finally {
            removeCachedSubtreeTimer.stop();
        }
    }

    /** returns only those states/nodes, where a new block starts that is 'open' at the end of the path. */
    private List<ARGState> getRelevantDefinitionNodes(List<ARGState> path) {
        Deque<ARGState> openCallElements = new ArrayDeque<>();
        Deque<Block> openSubtrees = new ArrayDeque<>();

        final ARGState lastState = path.get(path.size() - 1);
        for (final ARGState pathState : path) {
            assert openSubtrees.size() == openCallElements.size();
            CFANode node = extractLocation(pathState);

            // we use a loop here, because a return-node can be the exit of several blocks at once.
            while (!openSubtrees.isEmpty() && openSubtrees.getLast().isReturnNode(node) && pathState != lastState) {
                // we are leaving a block,
                // remove/pop the block and its start-state from the stacks
                openCallElements.removeLast();
                Block lastBlock = openSubtrees.removeLast();

                if (BAMTransferRelation.isFunctionBlock(lastBlock)) {
                    // we assume that leaving a function-block is only done once.
                    break;
                }
            }

            if (partitioning.isCallNode(node)
                    && !partitioning.getBlockForCallNode(node).equals(openSubtrees.peek())) {
                // we have a callnode, but current block is wrong, add new currentBlock and state as relevant.
                // the block can be equal, if this is a loop-block.
                openCallElements.addLast(pathState);
                openSubtrees.addLast(partitioning.getBlockForCallNode(node));
            }
        }

        return new ArrayList<>(openCallElements);
    }

    private void ensureExactCacheHitsOnPath(ARGReachedSet mainReachedSet, ARGPath pPath, final ARGState pElement,
            List<Precision> pNewPrecisions, Map<ARGState, ARGState> pPathElementToReachedState,
            Set<Pair<ARGState, ARGState>> neededRemoveCachedSubtreeCalls) {
        Map<ARGState, UnmodifiableReachedSet> pathElementToOuterReachedSet = new HashMap<>();
        Pair<Set<ARGState>, Set<ARGState>> pair = getCallAndReturnNodes(pPath, pathElementToOuterReachedSet,
                mainReachedSet.asReachedSet(), pPathElementToReachedState);
        Set<ARGState> callNodes = pair.getFirst();
        Set<ARGState> returnNodes = pair.getSecond();

        Deque<ARGState> remainingPathElements = new LinkedList<>(pPath.asStatesList());

        boolean starting = false;
        while (!remainingPathElements.isEmpty()) {
            ARGState currentElement = remainingPathElements.pop();

            if (currentElement.equals(pElement)) {
                starting = true;
            }

            if (starting) {
                if (callNodes.contains(currentElement)) {
                    ARGState currentReachedState = pPathElementToReachedState.get(currentElement);
                    CFANode node = extractLocation(currentReachedState);
                    Block currentBlock = partitioning.getBlockForCallNode(node);
                    AbstractState reducedState = wrappedReducer.getVariableReducedState(currentReachedState,
                            currentBlock, node);

                    removeUnpreciseCacheEntriesOnPath(currentElement, reducedState, pNewPrecisions, currentBlock,
                            remainingPathElements, pPathElementToReachedState, callNodes, returnNodes,
                            pathElementToOuterReachedSet, neededRemoveCachedSubtreeCalls);
                }
            }
        }
    }

    private Pair<Set<ARGState>, Set<ARGState>> getCallAndReturnNodes(ARGPath path,
            Map<ARGState, UnmodifiableReachedSet> pathElementToOuterReachedSet,
            UnmodifiableReachedSet mainReachedSet, Map<ARGState, ARGState> pPathElementToReachedState) {
        Set<ARGState> callNodes = new HashSet<>();
        Set<ARGState> returnNodes = new HashSet<>();

        Deque<Block> openSubtrees = new ArrayDeque<>();

        Deque<UnmodifiableReachedSet> openReachedSets = new ArrayDeque<>();
        openReachedSets.push(mainReachedSet);

        for (ARGState pathState : path.asStatesList()) {
            CFANode node = extractLocation(pathState);

            // we use a loop here, because a return-node can be the exit of several blocks at once.
            // we have to handle returnNodes before entryNodes, because some nodes can be both,
            // and the transferRelation also handles entryNodes as first case.
            while (!openSubtrees.isEmpty() && openSubtrees.peek().isReturnNode(node)) {
                openSubtrees.pop();
                openReachedSets.pop();
                returnNodes.add(pathState);
            }

            // this line comes after handling returnStates --> returnStates from path are part of the outer-block-reachedSet
            pathElementToOuterReachedSet.put(pathState, openReachedSets.peek());

            if (partitioning.isCallNode(node)
                    && !partitioning.getBlockForCallNode(node).equals(openSubtrees.peek())) {
                // the block can be equal, if this is a loop-block.
                openSubtrees.push(partitioning.getBlockForCallNode(node));
                openReachedSets.push(abstractStateToReachedSet.get(pPathElementToReachedState.get(pathState)));
                callNodes.add(pathState);
            }
        }

        return Pair.of(callNodes, returnNodes);
    }

    private boolean removeUnpreciseCacheEntriesOnPath(ARGState rootState, AbstractState reducedRootState,
            List<Precision> pNewPrecisions, Block rootBlock, Deque<ARGState> remainingPathElements,
            Map<ARGState, ARGState> pPathElementToReachedState, Set<ARGState> callNodes, Set<ARGState> returnNodes,
            Map<ARGState, UnmodifiableReachedSet> pathElementToOuterReachedSet,
            Set<Pair<ARGState, ARGState>> neededRemoveCachedSubtreeCalls) {
        UnmodifiableReachedSet outerReachedSet = pathElementToOuterReachedSet.get(rootState);

        Precision rootPrecision = outerReachedSet.getPrecision(pPathElementToReachedState.get(rootState));

        for (Precision pNewPrecision : pNewPrecisions) {
            rootPrecision = Precisions.replaceByType(rootPrecision, pNewPrecision,
                    Predicates.instanceOf(pNewPrecision.getClass()));
        }
        Precision reducedNewPrecision = wrappedReducer.getVariableReducedPrecision(rootPrecision, rootBlock);

        UnmodifiableReachedSet innerReachedSet = abstractStateToReachedSet
                .get(pPathElementToReachedState.get(rootState));
        Precision usedPrecision = innerReachedSet.getPrecision(innerReachedSet.getFirstState());

        //add precise key for new precision if needed
        if (!bamCache.containsPreciseKey(reducedRootState, reducedNewPrecision, rootBlock)) {
            ReachedSet reachedSet = createInitialReachedSet(reducedRootState, reducedNewPrecision);
            bamCache.put(reducedRootState, reducedNewPrecision, rootBlock, reachedSet);
        }

        boolean isNewPrecisionEntry = usedPrecision.equals(reducedNewPrecision);

        //fine, this block will not lead to any problems anymore, but maybe inner blocks will?
        //-> check other (inner) blocks on path
        boolean foundInnerUnpreciseEntries = false;
        while (!remainingPathElements.isEmpty()) {
            ARGState currentElement = remainingPathElements.pop();

            if (callNodes.contains(currentElement)) {
                ARGState currentReachedState = pPathElementToReachedState.get(currentElement);
                CFANode node = extractLocation(currentReachedState);
                Block currentBlock = partitioning.getBlockForCallNode(node);
                AbstractState reducedState = wrappedReducer.getVariableReducedState(currentReachedState,
                        currentBlock, node);

                boolean removedUnpreciseInnerBlock = removeUnpreciseCacheEntriesOnPath(currentElement, reducedState,
                        pNewPrecisions, currentBlock, remainingPathElements, pPathElementToReachedState, callNodes,
                        returnNodes, pathElementToOuterReachedSet, neededRemoveCachedSubtreeCalls);
                if (removedUnpreciseInnerBlock) {
                    //ok we indeed found an inner block that was unprecise
                    if (isNewPrecisionEntry && !foundInnerUnpreciseEntries) {
                        //if we are in a reached set that already uses the new precision and this is the first such entry we have to remove the subtree starting from currentElement in the rootReachedSet
                        neededRemoveCachedSubtreeCalls
                                .add(Pair.of(pPathElementToReachedState.get(rootState), currentReachedState));
                        foundInnerUnpreciseEntries = true;
                    }
                }
            }

            if (returnNodes.contains(currentElement)) {
                //our block ended. Leave..
                return foundInnerUnpreciseEntries || !isNewPrecisionEntry;
            }
        }

        return foundInnerUnpreciseEntries || !isNewPrecisionEntry;
    }

    private ReachedSet createInitialReachedSet(AbstractState initialState, Precision initialPredicatePrecision) {
        ReachedSet reached = reachedSetFactory.create();
        reached.add(initialState, initialPredicatePrecision);
        return reached;
    }

    /** remove all states after pState from path */
    private static List<ARGState> trimPath(final ARGPath pPath, final ARGState pState) {
        final List<ARGState> result = new ArrayList<>();
        for (ARGState state : pPath.asStatesList()) {
            result.add(state);
            if (state.equals(pState)) {
                return result;
            }
        }
        throw new IllegalArgumentException("State " + pState + " could not be found in path " + pPath + ".");
    }
}