org.sosy_lab.cpachecker.util.refinement.InterpolationTree.java Source code

Java tutorial

Introduction

Here is the source code for org.sosy_lab.cpachecker.util.refinement.InterpolationTree.java

Source

/*
 * CPAchecker is a tool for configurable software verification.
 *  This file is part of CPAchecker.
 *
 *  Copyright (C) 2007-2015  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.util.refinement;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
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.io.Files;
import org.sosy_lab.common.io.PathTemplate;
import org.sosy_lab.common.log.LogManager;
import org.sosy_lab.cpachecker.cfa.model.CFANode;
import org.sosy_lab.cpachecker.core.interfaces.AbstractState;
import org.sosy_lab.cpachecker.cpa.arg.ARGPath;
import org.sosy_lab.cpachecker.cpa.arg.ARGState;
import org.sosy_lab.cpachecker.cpa.arg.MutableARGPath;
import org.sosy_lab.cpachecker.util.AbstractStates;
import org.sosy_lab.cpachecker.util.CFAUtils;
import org.sosy_lab.cpachecker.util.states.MemoryLocation;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;

/**
 * This class represents an interpolation tree, i.e. a set of states connected through a successor-predecessor-relation.
 * The tree is built from traversing backwards from error states. It can be used to retrieve paths from the root of the
 * tree to error states, in a way, that only path not yet excluded by previous path interpolation need to be interpolated.
 */
public class InterpolationTree<S extends AbstractState, I extends Interpolant<S>> {
    /**
     * the logger in use
     */
    private final LogManager logger;

    /**
     * the counter to count interpolation queries
     */
    private int interpolationCounter = 0;

    /**
     * the predecessor relation of the states contained in this tree
     */
    private final Map<ARGState, ARGState> predecessorRelation = Maps.newLinkedHashMap();

    /**
     * the successor relation of the states contained in this tree
     */
    private final ListMultimap<ARGState, ARGState> successorRelation = ArrayListMultimap.create();

    /**
     * the mapping from state to the identified interpolants
     *
     * this has to be a linked hash map, because the ImpactRefiner has to iterate over this in insertion-order
     */
    private final Map<ARGState, I> interpolants = new LinkedHashMap<>();

    /**
     * the root of the tree
     */
    private final ARGState root;

    /**
     * the strategy on how to select paths for interpolation
     */
    private final InterpolationStrategy<I> strategy;

    private final InterpolantManager<S, I> interpolantManager;

    /**
     * the path denoting the empty path
     */
    public static final ARGPath EMPTY_PATH = null;

    /**
     * This method acts as constructor of the interpolation tree.
     *
     * @param pInterpolantManager the interpolant manager for creating interpolants
     * @param pLogger the logger to use
     * @param pTargetPaths the set of target paths from which to build the interpolation tree
     * @param useTopDownInterpolationStrategy the flag to choose the strategy to apply
     */
    public InterpolationTree(final InterpolantManager<S, I> pInterpolantManager, final LogManager pLogger,
            final List<ARGPath> pTargetPaths, final boolean useTopDownInterpolationStrategy) {
        logger = pLogger;
        interpolantManager = pInterpolantManager;

        root = build(pTargetPaths);

        if (useTopDownInterpolationStrategy) {
            strategy = new TopDownInterpolationStrategy();
        } else {
            strategy = new BottomUpInterpolationStrategy(extractTargets(pTargetPaths));
        }
    }

    /**
     * This method creates the successor and predecessor relations using the target paths.
     *
     * @return the root of the tree
     */
    private ARGState build(final Collection<ARGPath> targetPaths) {
        return (targetPaths.size() == 1) ? buildTreeFromSinglePath(Iterables.getOnlyElement(targetPaths))
                : buildTreeFromMultiplePaths(targetPaths);
    }

    /**
     * This method builds a (linear) tree from a single path.
     *
     * Note that, while this is just a special case of {@link buildTreeFromMultiplePaths},
     * this is the preferred way, because the given path could come from any analysis,
     * e.g., a predicate analysis, and the exact given path should be used for interpolation.
     * This is not guaranteed by the more general approach given in {@link buildTreeFromMultiplePaths},
     * because there the interpolation tree is build from a (non-unambiguous) set of states.
     */
    private ARGState buildTreeFromSinglePath(final ARGPath targetPath) {
        ImmutableList<ARGState> states = targetPath.asStatesList();

        for (int i = 0; i < states.size() - 1; i++) {
            ARGState predecessorState = states.get(i);

            ARGState successorState = states.get(i + 1);
            predecessorRelation.put(successorState, predecessorState);
            successorRelation.put(predecessorState, successorState);
        }

        return states.get(0);
    }

    /**
     * This method builds an actual tree from multiple path.
     */
    private ARGState buildTreeFromMultiplePaths(final Collection<ARGPath> targetPaths) {
        ARGState itpTreeRoot = null;
        Deque<ARGState> todo = new ArrayDeque<>(extractTargets(targetPaths));

        // build the tree, bottom-up, starting from the target states
        while (!todo.isEmpty()) {
            final ARGState currentState = todo.removeFirst();

            if (currentState.getParents().iterator().hasNext()) {

                if (!predecessorRelation.containsKey(currentState)) {
                    ARGState parentState = currentState.getParents().iterator().next();

                    predecessorRelation.put(currentState, parentState);
                    successorRelation.put(parentState, currentState);

                    todo.addFirst(parentState);
                }

            } else if (itpTreeRoot == null) {
                itpTreeRoot = currentState;
            }
        }

        return itpTreeRoot;
    }

    /**
     * This method extracts all targets states from the target paths.
     */
    private Set<ARGState> extractTargets(final Collection<ARGPath> targetsPaths) {
        return FluentIterable.from(targetsPaths).transform(new Function<ARGPath, ARGState>() {
            @Override
            public ARGState apply(ARGPath targetsPath) {
                return targetsPath.getLastState();
            }
        }).toSet();
    }

    public ARGState getRoot() {
        return root;
    }

    /**
     * This method decides whether or not there are more paths left for interpolation.
     *
     * @return true if there are more paths left for interpolation, else false
     */
    public boolean hasNextPathForInterpolation() {
        interpolationCounter++;
        return strategy.hasNextPathForInterpolation();
    }

    /**
     * This method exports the current representation in dot format to the given file.
     *
     * @param Path file the file to write to
     */
    public void exportToDot(PathTemplate file, int refinementCounter) {
        StringBuilder result = new StringBuilder().append("digraph tree {" + "\n");
        for (Map.Entry<ARGState, ARGState> current : successorRelation.entries()) {
            if (interpolants.containsKey(current.getKey())) {
                StringBuilder sb = new StringBuilder();

                sb.append("itp is " + interpolants.get(current.getKey()));

                result.append(current.getKey().getStateId() + " [label=\""
                        + (current.getKey().getStateId() + " / " + AbstractStates.extractLocation(current.getKey()))
                        + " has itp " + (sb.toString()) + "\"]" + "\n");
                result.append(current.getKey().getStateId() + " -> " + current.getValue().getStateId() + "\n");// + " [label=\"" + current.getKey().getEdgeToChild(current.getValue()).getRawStatement().replace("\n", "") + "\"]\n");

            } else {
                result.append(current.getKey().getStateId() + " [label=\"" + current.getKey().getStateId()
                        + " has itp NA\"]" + "\n");
                result.append(current.getKey().getStateId() + " -> " + current.getValue().getStateId() + "\n");// + " [label=\"" + current.getKey().getEdgeToChild(current.getValue()).getRawStatement().replace("\n", "") + "\"]\n");
            }

            if (current.getValue().isTarget()) {
                result.append(current.getValue().getStateId() + " [style=filled, fillcolor=\"red\"]" + "\n");
            }

            assert (!current.getKey().isTarget());
        }
        result.append("}");

        try (Writer w = Files.openOutputFile(file.getPath(refinementCounter, interpolationCounter))) {
            w.write(result.toString());
        } catch (IOException e) {
            logger.logUserException(Level.WARNING, e, "Could not write interpolation tree to file");
        }
    }

    /**
     * This method returns the next error path for interpolation.
     *
     * @param current the current root of the error path to retrieve for a subsequent interpolation
     * @param interpolationRoots the mutable stack of interpolation roots, which might be added to within this method
     * @return the next error path for a subsequent interpolation
     */
    public ARGPath getNextPathForInterpolation() {
        return strategy.getNextPathForInterpolation();
    }

    /**
     * This method returns the interpolant to be used for interpolation of the given path.
     *
     * @param errorPath the path for which to obtain the initial interpolant
     * @return the initial interpolant for the given path
     */
    public I getInitialInterpolantForPath(ARGPath errorPath) {
        return strategy.getInitialInterpolantForRoot(errorPath.getFirstState());
    }

    /**
     * This method updates the mapping from states to interpolants.
     *
     * @param interpolantsToAdd the new mapping to add
     */
    public void addInterpolants(Map<ARGState, I> interpolantsToAdd) {
        for (Map.Entry<ARGState, I> entry : interpolantsToAdd.entrySet()) {
            ARGState state = entry.getKey();
            I itp = entry.getValue();

            if (interpolants.containsKey(state)) {
                interpolants.put(state, interpolants.get(state).join(itp));
            } else {
                interpolants.put(state, itp);
            }
        }
    }

    /**
     * This method extracts the precision increment for the given refinement root.
     * It does so by collection all non-trivial interpolants in the subtree of the given refinement root.
     *
     * @return the precision increment for the given refinement root
     */
    public Multimap<CFANode, MemoryLocation> extractPrecisionIncrement(ARGState pRefinementRoot) {
        Multimap<CFANode, MemoryLocation> increment = HashMultimap.create();

        Deque<ARGState> todo = new ArrayDeque<>(Collections.singleton(predecessorRelation.get(pRefinementRoot)));

        while (!todo.isEmpty()) {
            final ARGState currentState = todo.removeFirst();

            if (stateHasNonTrivialInterpolant(currentState) && !currentState.isTarget()) {
                I itp = interpolants.get(currentState);
                for (MemoryLocation memoryLocation : itp.getMemoryLocations()) {
                    increment.put(AbstractStates.extractLocation(currentState), memoryLocation);
                }
            }

            if (!stateHasFalseInterpolant(currentState)) {
                todo.addAll(successorRelation.get(currentState));
            }
        }

        return increment;
    }

    /**
     * This method obtains the refinement roots, i.e., for each disjunct path from target states
     * to the root, it collects the highest state that has a non-trivial interpolant associated.
     * With non-lazy abstraction, the root of the interpolation tree is used as refinement root.
     *
     * @param whether to perform lazy abstraction or not
     * @return the set of refinement roots
     */
    public Collection<ARGState> obtainRefinementRoots(GenericRefiner.RestartStrategy strategy) {
        if (strategy == GenericRefiner.RestartStrategy.ROOT) {
            assert successorRelation.get(root).size() == 1 : "ARG root has more than one successor";
            return new HashSet<>(Collections.singleton(successorRelation.get(root).iterator().next()));
        }

        ARGState commonRoot = null;

        Collection<ARGState> refinementRoots = new HashSet<>();

        Deque<ARGState> todo = new ArrayDeque<>(Collections.singleton(root));
        while (!todo.isEmpty()) {
            final ARGState currentState = todo.removeFirst();

            // determine the first branching point, which is the lowest node common to all refinement roots
            if (commonRoot == null && successorRelation.get(currentState).size() > 1) {
                commonRoot = currentState;
            }

            if (stateHasNonTrivialInterpolant(currentState)) {
                refinementRoots.add(currentState);

                if (strategy == GenericRefiner.RestartStrategy.COMMON && refinementRoots.size() > 2) {
                    assert commonRoot != null : "common root not yet set";
                    return new HashSet<>(Collections.singleton(commonRoot));
                }
                continue;
            }

            Collection<ARGState> successors = successorRelation.get(currentState);
            todo.addAll(successors);
        }

        return refinementRoots;
    }

    /**
     * This method obtains, for the IMPACT-like approach, the cut-off roots,
     * i.e., for each disjunct path from target states to the root, it collects
     * the highest state that has a false interpolant associated.
     *
     * @return the set of cut-off roots
     */
    public Collection<ARGState> obtainCutOffRoots() {
        Collection<ARGState> refinementRoots = new HashSet<>();

        Deque<ARGState> todo = new ArrayDeque<>(Collections.singleton(root));
        while (!todo.isEmpty()) {
            final ARGState currentState = todo.removeFirst();

            if (stateHasFalseInterpolant(currentState)) {
                refinementRoots.add(currentState);
                continue;

            }

            Collection<ARGState> successors = successorRelation.get(currentState);
            todo.addAll(successors);
        }

        return refinementRoots;
    }

    /**
     * This method returns the target states in the subtree of the given state.
     *
     * @param state the state for which to collect the target states in its subtree.
     * @return target states in the subtree of the given state
     */
    public Collection<ARGState> getTargetsInSubtree(ARGState state) {
        Collection<ARGState> targetStates = new HashSet<>();

        Deque<ARGState> todo = new ArrayDeque<>(Collections.singleton(state));
        while (!todo.isEmpty()) {
            final ARGState currentState = todo.removeFirst();

            if (currentState.isTarget()) {
                targetStates.add(currentState);
                continue;
            }

            Collection<ARGState> successors = successorRelation.get(currentState);
            todo.addAll(successors);
        }

        return targetStates;
    }

    /**
     * This method returns, from the subtree of the given state, the target states
     * that have an interpolant, i.e., that were interpolated.
     *
     * @param state the state for which to collect the interpolated target states in its subtree.
     * @return the target states that were interpolated
     */
    public Collection<ARGState> getInterpolatedTargetsInSubtree(ARGState state) {
        return FluentIterable.from(getTargetsInSubtree(state)).filter(new Predicate<ARGState>() {
            @Override
            public boolean apply(ARGState targetState) {
                return interpolants.containsKey(targetState);
            }
        }).toSet();
    }

    /**
     * This method checks if for the given state a non-trivial interpolant is present.
     *
     * @param currentState the state for which to check
     * @return true, if a non-trivial interpolant is present, else false
     */
    private boolean stateHasNonTrivialInterpolant(final ARGState currentState) {
        return interpolants.containsKey(currentState) && !interpolants.get(currentState).isTrivial();
    }

    /**
     * This method checks if for the given state a false interpolant is present.
     *
     * @param currentState the state for which to check
     * @return true, if a false interpolant is present, else false
     */
    private boolean stateHasFalseInterpolant(final ARGState currentState) {
        return interpolants.containsKey(currentState) && interpolants.get(currentState).isFalse();
    }

    /**
     * This method checks if an interpolant is associated with the given state.
     *
     * @param currentState the state for which to check for associated interpolants
     * @return true, if an interpolant is associated with the given state
     */
    public boolean hasInterpolantForState(final ARGState currentState) {
        return interpolants.containsKey(currentState);
    }

    public Set<Map.Entry<ARGState, I>> getInterpolantMapping() {
        return interpolants.entrySet();
    }

    /**
     * This method returns, if any, the interpolant associated with the given state.
     *
     * @param currentState the state for which to return the associated interpolant
     * @return the interpolant associated with the given state
     */
    public I getInterpolantForState(final ARGState currentState) {
        return interpolants.get(currentState);
    }

    public Collection<ARGState> getSuccessors(final ARGState pState) {
        return successorRelation.get(pState);
    }

    public ARGState getPredecessor(final ARGState pState) {
        return predecessorRelation.get(pState);
    }

    private interface InterpolationStrategy<I extends Interpolant<?>> {

        ARGPath getNextPathForInterpolation();

        boolean hasNextPathForInterpolation();

        I getInitialInterpolantForRoot(ARGState root);
    }

    private class TopDownInterpolationStrategy implements InterpolationStrategy<I> {

        /**
         * the states that are the sources for obtaining (partial) error paths
         */
        private Deque<ARGState> sources = new ArrayDeque<>(Collections.singleton(root));

        @Override
        public ARGPath getNextPathForInterpolation() {
            MutableARGPath errorPath = new MutableARGPath();

            ARGState current = sources.pop();

            if (!isValidInterpolationRoot(predecessorRelation.get(current))) {
                logger.log(Level.FINEST, "interpolant of predecessor of ", current.getStateId(),
                        " is already false, so return empty path");
                return EMPTY_PATH;
            }

            // if the current state is not the root, it is a child of a branch , however, the path should not start with the
            // child, but with the branching node (children are stored on the stack because this needs less book-keeping)
            if (current != root) {
                errorPath.add(Pair.of(predecessorRelation.get(current),
                        predecessorRelation.get(current).getEdgeToChild(current)));
            }

            while (successorRelation.get(current).iterator().hasNext()) {
                Iterator<ARGState> children = successorRelation.get(current).iterator();
                ARGState child = children.next();
                errorPath.add(Pair.of(current, current.getEdgeToChild(child)));

                // push all other children of the current state, if any, onto the stack for later interpolations
                int size = 1;
                while (children.hasNext()) {
                    size++;
                    ARGState sibling = children.next();
                    logger.log(Level.FINEST, "\tpush new root ", sibling.getStateId(), " onto stack for parent ",
                            predecessorRelation.get(sibling).getStateId());
                    sources.push(sibling);
                }
                assert (size <= 2);

                current = child;

                // add out-going edges of final state, too (just for compatibility reasons to compare to DelegatingRefiner)
                if (!successorRelation.get(current).iterator().hasNext()) {
                    errorPath.add(Pair.of(current,
                            CFAUtils.leavingEdges(AbstractStates.extractLocation(current)).first().orNull()));
                }
            }

            return errorPath.immutableCopy();
        }

        /**
         * The given state is not a valid interpolation root if it is associated with a interpolant representing "false"
         */
        private boolean isValidInterpolationRoot(ARGState root) {
            return !stateHasFalseInterpolant(root);
        }

        @Override
        public I getInitialInterpolantForRoot(ARGState root) {
            I initialInterpolant = interpolants.get(root);

            if (initialInterpolant == null) {
                initialInterpolant = interpolantManager.createInitialInterpolant();

                assert (interpolants.size() == 0) : "initial interpolant was null after initial interpolation!";
            }

            return initialInterpolant;
        }

        @Override
        public boolean hasNextPathForInterpolation() {
            return !sources.isEmpty();
        }
    }

    private class BottomUpInterpolationStrategy implements InterpolationStrategy<I> {

        /**
         * the states that are the sources for obtaining error paths
         */
        private List<ARGState> sources;

        public BottomUpInterpolationStrategy(Set<ARGState> pTargets) {
            sources = new ArrayList<>(pTargets);
        }

        @Override
        public ARGPath getNextPathForInterpolation() {
            ARGState current = sources.remove(0);

            assert current.isTarget() : "current element is not a target";

            MutableARGPath errorPath = new MutableARGPath();

            errorPath.addFirst(Pair.of(current,
                    CFAUtils.leavingEdges(AbstractStates.extractLocation(current)).first().orNull()));

            while (predecessorRelation.get(current) != null) {

                ARGState parent = predecessorRelation.get(current);

                if (stateHasFalseInterpolant(parent)) {
                    logger.log(Level.FINEST, "interpolant on path, namely for state ", parent.getStateId(),
                            " is already false, so return empty path");
                    return EMPTY_PATH;
                }

                errorPath.addFirst(Pair.of(parent, parent.getEdgeToChild(current)));

                current = parent;
            }

            return errorPath.immutableCopy();
        }

        @Override
        public I getInitialInterpolantForRoot(ARGState root) {
            return interpolantManager.createInitialInterpolant();
        }

        @Override
        public boolean hasNextPathForInterpolation() {
            return !sources.isEmpty();
        }
    }
}