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

Java tutorial

Introduction

Here is the source code for org.sosy_lab.cpachecker.cpa.bam.BAMTransferRelationWithFixPointForRecursion.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.cpa.bam;

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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;

import javax.annotation.Nonnull;

import org.sosy_lab.common.Pair;
import org.sosy_lab.common.ShutdownNotifier;
import org.sosy_lab.common.Triple;
import org.sosy_lab.common.configuration.Configuration;
import org.sosy_lab.common.configuration.InvalidConfigurationException;
import org.sosy_lab.common.configuration.Option;
import org.sosy_lab.common.configuration.Options;
import org.sosy_lab.common.log.LogManager;
import org.sosy_lab.cpachecker.cfa.blocks.Block;
import org.sosy_lab.cpachecker.cfa.model.CFANode;
import org.sosy_lab.cpachecker.cfa.model.FunctionEntryNode;
import org.sosy_lab.cpachecker.cfa.model.FunctionExitNode;
import org.sosy_lab.cpachecker.core.interfaces.AbstractState;
import org.sosy_lab.cpachecker.core.interfaces.Precision;
import org.sosy_lab.cpachecker.core.interfaces.pcc.ProofChecker;
import org.sosy_lab.cpachecker.core.reachedset.ReachedSet;
import org.sosy_lab.cpachecker.cpa.arg.ARGState;
import org.sosy_lab.cpachecker.exceptions.CPAException;
import org.sosy_lab.cpachecker.exceptions.CPATransferException;

import com.google.common.collect.Iterables;

@Options(prefix = "cpa.bam")
public class BAMTransferRelationWithFixPointForRecursion extends BAMTransferRelation {

    @Option(secure = true, description = "if we connot determine a repeating/covering call-state, "
            + "we will run into CallStackOverflowException. Thus we bound the stack size (unsound!). "
            + "This option only limits non-covered recursion, but not a recursion "
            + "where we find a coverage and re-use the cached block several times. "
            + "The value '-1' disables this option.")
    private int maximalDepthForExplicitRecursion = -1;

    // flags of the fixpoint-algorithm for recursion
    private boolean recursionSeen = false;
    private boolean resultStatesChanged = false;
    private boolean targetFound = false;
    final Map<AbstractState, Triple<AbstractState, Precision, Block>> potentialRecursionUpdateStates = new HashMap<>();

    public BAMTransferRelationWithFixPointForRecursion(Configuration pConfig, LogManager pLogger, BAMCPA bamCpa,
            ProofChecker wrappedChecker, BAMDataManager data, ShutdownNotifier pShutdownNotifier)
            throws InvalidConfigurationException {
        super(pConfig, pLogger, bamCpa, wrappedChecker, data, pShutdownNotifier);
    }

    @Override
    protected Collection<? extends AbstractState> getAbstractSuccessorsWithoutWrapping(final AbstractState pState,
            final Precision pPrecision) throws CPAException, InterruptedException {

        final CFANode node = extractLocation(pState);

        if (stack.isEmpty() && isHeadOfMainFunction(node)) {
            // we are at the start of the program (root-node of CFA).
            return doFixpointIterationForRecursion(pState, pPrecision, node);
        }

        if (maximalDepthForExplicitRecursion != -1 && stack.size() > maximalDepthForExplicitRecursion) {
            return Collections.emptySet();
        }

        return super.getAbstractSuccessorsWithoutWrapping(pState, pPrecision);
    }

    @Override
    protected boolean startNewBlockAnalysis(final AbstractState pState, final CFANode node) {
        return partitioning.isCallNode(node) && !((ARGState) pState).getParents().isEmpty() // if no parents, we have already started a new block
                && (!partitioning.getBlockForCallNode(node).equals(currentBlock)
                        || isFunctionBlock(partitioning.getBlockForCallNode(node)));
    }

    @Override
    protected boolean exitBlockAnalysis(final AbstractState pState, final CFANode node) {
        // special case: returning from a recursive function is only allowed once per state.
        return super.exitBlockAnalysis(pState, node) && !data.alreadyReturnedFromSameBlock(pState, currentBlock);
    }

    /** recursion is handled by callstack-reduction, so we do not check it here. */
    @Override
    protected boolean isRecursiveCall(final CFANode node) {
        return false;
    }

    private Collection<? extends AbstractState> doFixpointIterationForRecursion(
            final AbstractState pHeadOfMainFunctionState, final Precision pPrecision,
            final CFANode pHeadOfMainFunction) throws CPAException, InterruptedException {

        assert isHeadOfMainFunction(pHeadOfMainFunction) && stack.isEmpty();

        Collection<? extends AbstractState> resultStates;
        int iterationCounter = 0;
        while (true) { // fixpoint-iteration to handle recursive functions

            if (!targetFound) {
                // The target might be outside the recursive function.
                // If CEGAR removes the target through refinement,
                // we might miss the recursive function, if we reset the flags. So we do not reset them in that case.

                recursionSeen = false; // might be changed in recursive analysis
                resultStatesChanged = false; // might be changed in recursive analysis
                potentialRecursionUpdateStates.clear();
            }

            logger.log(Level.FINEST, "Starting recursive analysis of main-block");

            resultStates = doRecursiveAnalysis(pHeadOfMainFunctionState, pPrecision, pHeadOfMainFunction);

            logger.log(Level.FINEST, "Finished recursive analysis of main-block");

            // EITHER: result is an target-state, return it 'as is' and let CEGAR-algorithm perform a refinement, if needed.
            // OR:     we have completely analyzed the main-block and have not found an target-state.
            //         now we check, if we need to unwind recursive calls further until a fixpoint is reached.

            targetFound = Iterables.any(resultStates, IS_TARGET_STATE);
            if (targetFound) {
                // not really a fixpoint, but we return and let CEGAR check the target-state
                logger.log(Level.INFO, "fixpoint-iteration aborted, because there was a target state.");
                break;
            }

            if (!resultStatesChanged) {
                logger.log(Level.INFO,
                        "fixpoint-iteration aborted, because we did not get new states (fixpoint reached).");
                break;
            }

            logger.log(Level.INFO, "fixpoint was not reached, starting new iteration", ++iterationCounter);

            reAddStatesForFixPointIteration(pHeadOfMainFunctionState);

            // continue;
        }

        return resultStates;
    }

    /** update waitlists of all reachedsets to re-explore the previously found recursive function-call. */
    private void reAddStatesForFixPointIteration(final AbstractState pHeadOfMainFunctionState) {
        for (final AbstractState recursionUpdateState : potentialRecursionUpdateStates.keySet()) {
            for (final ReachedSet reachedSet : data.bamCache.getAllCachedReachedStates()) {
                if (reachedSet.contains(recursionUpdateState)) {
                    logger.log(Level.FINEST, "re-adding state", recursionUpdateState);
                    reachedSet.reAddToWaitlist(recursionUpdateState);
                }
                // else if (pHeadOfMainFunctionState == recursionUpdateState) {
                // special case: this is the root-state of the whole program, it is in the main-reachedset.
                // we have no possibility to re-add it,because we do not have access to the main-reachedset.
                // however we do not need to re-add it, because it is the initial-state of current transfer-relation.
                // }
            }
        }
    }

    /** returns a covering level or Null, if no such level is found. */
    private Triple<AbstractState, Precision, Block> getCoveringLevel(
            final List<Triple<AbstractState, Precision, Block>> stack,
            final Triple<AbstractState, Precision, Block> currentLevel) throws CPAException, InterruptedException {
        for (Triple<AbstractState, Precision, Block> level : stack.subList(0, stack.size() - 1)) {
            if (level.getThird() == currentLevel.getThird()
                    // && level.getSecond().equals(currentLevel.getSecond())
                    && bamCPA.isCoveredBy(currentLevel.getFirst(), level.getFirst())) {
                // previously reached state contains 'less' information, it is a super-state of the currentState.
                // From currentState we could reach the levelState again (with further unrolling).
                // Thus we would have found an endless recursion (with current information (precision/state)).

                // TODO how to compare precisions? equality would be enough
                return level;
            }
        }
        return null;
    }

    /** check, if all base-states are covered by the covering-states.
     * examples:
     * {x} is covered by {x,y,z}
     * {} is covered by {x,y,z}
     * {x} is covered by {(x or y),z}
     * {(x and y),z} is covered by {x,z}
     */
    private Collection<AbstractState> getStatesNotCoveredBy(@Nonnull final Collection<AbstractState> baseStates,
            @Nonnull final Collection<AbstractState> coveringStates) throws CPAException, InterruptedException {
        final Collection<AbstractState> notCoveredStates = new ArrayList<>();
        for (final AbstractState baseState : baseStates) {
            if (!isCoveredByAny(baseState, coveringStates)) {
                notCoveredStates.add(baseState);
            }
        }
        return notCoveredStates;
    }

    /** is there any covering-state, that covers the base-state? */
    private boolean isCoveredByAny(@Nonnull final AbstractState baseState,
            @Nonnull final Collection<AbstractState> coveringStates) throws CPAException, InterruptedException {
        if (coveringStates.contains(baseState)) {
            return true;
        }
        for (final AbstractState coveringState : coveringStates) {
            if (bamCPA.isCoveredBy(baseState, coveringState)) {
                return true;
            }
        }
        return false;
    }

    @Override
    protected Collection<AbstractState> analyseBlockAndExpand(final AbstractState initialState,
            final Precision pPrecision, final Block outerSubtree, final AbstractState reducedInitialState,
            final Precision reducedInitialPrecision)
            throws CPAException, InterruptedException, CPATransferException {

        final CFANode node = currentBlock.getCallNode();
        final Collection<AbstractState> resultStates;

        if (!(node instanceof FunctionEntryNode) || isHeadOfMainFunction(node)) {
            // block is a loop-block
            // or we are at the start of the main-block, we do not need the rebuilding-part for main,
            // so we use a simplified version of function-handling and handle it like a loop-block
            resultStates = super.analyseBlockAndExpand(initialState, pPrecision, outerSubtree, reducedInitialState,
                    reducedInitialPrecision);

        } else {
            // function-entry and old block --> begin new block
            // TODO match only recursive function, not all functions

            resultStates = analyseBlockAndExpandForRecursion(initialState, pPrecision, node, outerSubtree,
                    reducedInitialState, reducedInitialPrecision);

            // current location is "before" the function-return-edge.
        }

        if (recursionSeen) {
            // we are returning from an recursive call.
            // if we would need to do further analysis for recursion later (fixpoint-analysis!),
            // we need to know, where to update the reachedset --> store initialState for later usage
            potentialRecursionUpdateStates.put(initialState, Iterables.getLast(stack));
        }

        return resultStates;
    }

    private Collection<AbstractState> analyseBlockAndExpandForRecursion(final AbstractState initialState,
            final Precision pPrecision, final CFANode node, final Block outerSubtree,
            final AbstractState reducedInitialState, final Precision reducedInitialPrecision)
            throws CPAException, InterruptedException, CPATransferException {
        assert !((ARGState) initialState).getParents().isEmpty();

        // get the rootState, that is the abstract state of the functioncall.
        Collection<ARGState> possibleRootStates = ((ARGState) initialState).getParents();
        assert possibleRootStates.size() == 1 : "too many functioncalls: " + possibleRootStates;
        AbstractState rootState = possibleRootStates.iterator().next();

        final Collection<AbstractState> expandedFunctionReturnStates;

        final Triple<AbstractState, Precision, Block> coveringLevel = getCoveringLevel(stack,
                Iterables.getLast(stack));
        if (coveringLevel != null) {
            // if level is twice in stack, we have endless recursion.
            // with current knowledge we would never abort unrolling the recursion.
            // lets skip the function and return only a short "summary" of the function.
            // this summary is the result of a previous analysis of this block from the cache.
            logger.logf(Level.FINEST,
                    "recursion will cause endless unrolling (with current precision), "
                            + "aborting call of function '%s' at state %s",
                    node.getFunctionName(), reducedInitialState);

            expandedFunctionReturnStates = analyseRecursiveBlockAndExpand(initialState, pPrecision, outerSubtree,
                    reducedInitialState, coveringLevel);

        } else {
            // enter block of function-call and start recursive analysis
            expandedFunctionReturnStates = super.analyseBlockAndExpand(initialState, pPrecision, outerSubtree,
                    reducedInitialState, reducedInitialPrecision);
        }

        final Collection<AbstractState> rebuildStates = new ArrayList<>(expandedFunctionReturnStates.size());
        for (final AbstractState expandedState : expandedFunctionReturnStates) {
            rebuildStates.add(getRebuildState(rootState, initialState, expandedState));
        }

        return rebuildStates;
    }

    /** This method analyses a recursive procedure and
     * returns (expanded) abstract states from the cache, that match the current procedure,
     * or an empty set, if no cache entry was found. */
    private Collection<AbstractState> analyseRecursiveBlockAndExpand(final AbstractState initialState,
            final Precision pPrecision, final Block pOuterSubtree, final AbstractState pReducedInitialState,
            final Triple<AbstractState, Precision, Block> pCoveringLevel) throws CPATransferException {

        recursionSeen = true;
        // after this point we have to check all returnStates for changes.
        // If no changes are found, we have found the fixpoint.

        // try to get previously computed states from cache
        final Pair<ReachedSet, Collection<AbstractState>> pair =
                //argCache.get(reducedInitialState, reducedInitialPrecision, currentBlock);
                data.bamCache.get(pCoveringLevel.getFirst(), pCoveringLevel.getSecond(), pCoveringLevel.getThird());
        final ReachedSet reached = pair.getFirst();
        final Collection<AbstractState> previousResult = pair.getSecond();
        final Collection<Pair<AbstractState, Precision>> reducedResult;

        assert reached != null : "cached entry has no reached set";
        if (previousResult == null) {
            // outer block was not finished, abort recursion
            reducedResult = Collections.emptySet();
            logger.logf(Level.FINEST, "skipping recursive call with new empty result (root is %s)",
                    reached.getFirstState());
        } else {
            // use previously computed outer block as inner block,
            // this is equal to 'add one recursive step' in the recursion
            reducedResult = imbueAbstractStatesWithPrecision(reached, previousResult);
            logger.logf(Level.FINEST, "skipping recursive call with cached result (root is %s)",
                    reached.getFirstState());
        }

        data.initialStateToReachedSet.put(initialState, reached);

        addBlockAnalysisInfo(pReducedInitialState);

        return expandResultStates(reducedResult, pOuterSubtree, initialState, pPrecision);
    }

    /** We try to get a smaller set of states for further analysis.
     * We check, which states were already analyzed further in a previous iteration of the fixpoint-algorithm.
     * Those states are excluded from further analysis. */
    @Override
    protected Collection<AbstractState> filterResultStatesForFurtherAnalysis(
            final Collection<AbstractState> reducedResult, final Collection<AbstractState> cachedReturnStates)
            throws CPAException, InterruptedException {
        final Collection<AbstractState> statesForFurtherAnalysis;
        if (cachedReturnStates == null) {
            logger.log(Level.FINEST, "there was no cache-entry for result-states.");
            resultStatesChanged = true;
            statesForFurtherAnalysis = reducedResult;

        } else {
            // this is the result from a previous analysis of a recursive function-call
            // now we check, if we really get new states or if all new states (= reducedResult) are
            final Collection<AbstractState> newStates = getStatesNotCoveredBy(reducedResult, cachedReturnStates);
            if (newStates.isEmpty()) {
                // analysis of recursive function did not produce more states.
                logger.log(Level.FINEST,
                        "all previous return-states are covering the current new states, no new states found.");
            } else {
                // new states found, set flag for fixpoint-analysis and return (and later update the cache).
                logger.log(Level.FINEST, "some cached result-states are not covered. returning new result-states.");
                resultStatesChanged = true;
            }

            // we are in an the fixpoint-algorithm for recursion,
            // we have already analyzed all covered states in the previous iteration,
            // thus we only need to analyze the remaining states.
            // this is an optimization,
            statesForFurtherAnalysis = newStates;
        }
        return statesForFurtherAnalysis;
    }

    /** Reconstruct the resulting state from root-, entry- and expanded-state.
     * Also cleanup and update the ARG with the new build state. */
    private AbstractState getRebuildState(final AbstractState rootState, final AbstractState entryState,
            final AbstractState expandedState) {
        logger.log(Level.ALL, "rebuilding state with root state", rootState);
        logger.log(Level.ALL, "rebuilding state with entry state", entryState);
        logger.log(Level.ALL, "rebuilding state with expanded state", expandedState);

        final CFANode location = extractLocation(expandedState);
        if (!(location instanceof FunctionExitNode)) {
            logger.log(Level.ALL, "rebuilding skipped because of non-function-exit-location");
            assert isTargetState(expandedState) : "only target states are returned without rebuild";
            return expandedState;
        }

        final AbstractState rebuildState = wrappedReducer.rebuildStateAfterFunctionCall(rootState, entryState,
                expandedState, (FunctionExitNode) location);
        logger.log(Level.ALL, "rebuilding finished with state", rebuildState);

        // in the ARG of the outer block we have now the connection "rootState <-> expandedState"
        assert ((ARGState) expandedState).getChildren().isEmpty()
                && ((ARGState) expandedState).getParents().size() == 1 : "unexpected expanded state: "
                        + expandedState;
        assert ((ARGState) entryState).getChildren().contains(expandedState) : "successor of entry state "
                + entryState + " must be expanded state " + expandedState;

        // we replace this connection with "rootState <-> rebuildState"
        ((ARGState) expandedState).removeFromARG();
        ((ARGState) rebuildState).addParent((ARGState) entryState);

        // also clean up local data structures
        data.replaceStateInCaches(expandedState, rebuildState, true);

        return rebuildState;
    }
}