org.sosy_lab.cpachecker.util.blocking.BlockedCFAReducer.java Source code

Java tutorial

Introduction

Here is the source code for org.sosy_lab.cpachecker.util.blocking.BlockedCFAReducer.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.util.blocking;

import static com.google.common.base.Preconditions.checkNotNull;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.logging.Level;

import org.sosy_lab.common.configuration.Configuration;
import org.sosy_lab.common.configuration.FileOption;
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.io.Files;
import org.sosy_lab.common.io.Path;
import org.sosy_lab.common.io.Paths;
import org.sosy_lab.common.log.LogManager;
import org.sosy_lab.cpachecker.cfa.CFA;
import org.sosy_lab.cpachecker.cfa.model.CFAEdge;
import org.sosy_lab.cpachecker.cfa.model.CFANode;
import org.sosy_lab.cpachecker.cfa.model.FunctionEntryNode;
import org.sosy_lab.cpachecker.cfa.model.c.CFunctionCallEdge;
import org.sosy_lab.cpachecker.util.CFAUtils;
import org.sosy_lab.cpachecker.util.blocking.interfaces.BlockComputer;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;

@Options(prefix = "blockreducer")
public class BlockedCFAReducer implements BlockComputer {

    @Option(secure = true, description = "Do at most n summarizations on a node.")
    private int reductionThreshold = 100;

    @Option(secure = true, description = "Allow reduction of loop heads; calculate abstractions always at loop heads?")
    private boolean allowReduceLoopHeads = false;

    @Option(secure = true, description = "Allow reduction of function entries; calculate abstractions always at function entries?")
    private boolean allowReduceFunctionEntries = true;

    @Option(secure = true, description = "Allow reduction of function exits; calculate abstractions always at function exits?")
    private boolean allowReduceFunctionExits = true;

    @Option(secure = true, name = "reducedCfaFile", description = "write the reduced cfa to the specified file.")
    @FileOption(FileOption.Type.OUTPUT_FILE)
    private Path reducedCfaFile = Paths.get("ReducedCfa.rsf");

    private int functionCallSeq = 0;
    private final Deque<FunctionEntryNode> inliningStack;

    private final LogManager logger;

    public BlockedCFAReducer(Configuration pConfig, LogManager pLogger) throws InvalidConfigurationException {
        pConfig.inject(this);

        this.logger = checkNotNull(pLogger);
        this.inliningStack = new ArrayDeque<>();
    }

    private boolean isAbstractionNode(ReducedNode pNode) {
        return (pNode.isLoopHead() && !allowReduceLoopHeads)
                || (pNode.isFunctionEntry() && !allowReduceFunctionEntries)
                || (pNode.isFunctionExit() && !allowReduceFunctionExits);
    }

    /**
     * Increment the number of summarizations that are done
     * with pNode as the root-node.
     */
    private void incSummarizationsOnNode(ReducedNode pNode, int pIncBy) {
        assert (reductionThreshold > 0);
        pNode.incSummarizations(pIncBy);
    }

    private int getSummarizationsOnNode(ReducedNode pNode) {
        return pNode.getSummarizations();
    }

    @VisibleForTesting
    boolean applySequenceRule(ReducedFunction pApplyTo) {
        boolean result = false;
        // TODO: ensure that this function is not applied across the scope of a loop.

        Queue<ReducedNode> toTraverse = new ArrayDeque<>();
        Set<ReducedEdge> traverseDone = new HashSet<>();

        toTraverse.add(pApplyTo.getEntryNode());
        while (toTraverse.size() > 0) {
            ReducedNode u = toTraverse.remove();

            for (ReducedEdge e : pApplyTo.getLeavingEdges(u)) {
                ReducedNode v = e.getPointsTo();

                if (!traverseDone.add(e)) {
                    continue;
                }

                if (u == v) {
                    continue;
                }

                List<ReducedEdge> vLeavingEdges = pApplyTo.getLeavingEdges(v);
                if (vLeavingEdges.isEmpty()) {
                    continue;
                }

                if (getSummarizationsOnNode(u) + vLeavingEdges.size() > reductionThreshold) {
                    toTraverse.add(v);
                    continue;
                }

                if (pApplyTo.getNumEnteringEdges(v) != 1) {
                    toTraverse.add(v);
                    continue;
                }

                if (isAbstractionNode(v)) {
                    toTraverse.add(v);
                    continue;
                }

                boolean uvRemoved = false;
                for (ReducedEdge f : vLeavingEdges) {
                    ReducedNode w = f.getPointsTo();

                    assert (u != v);
                    assert (v != w);

                    ReducedEdge sumEdge = new ReducedEdge(w);
                    sumEdge.addEdge(e);
                    sumEdge.addEdge(f);

                    pApplyTo.removeEdge(v, w, f);
                    if (!uvRemoved) {
                        pApplyTo.removeEdge(u, v, e);
                        this.incSummarizationsOnNode(u, v.getSummarizations());
                        uvRemoved = true;
                    }
                    pApplyTo.addEdge(u, w, sumEdge);

                    this.incSummarizationsOnNode(u, 1);
                }

                toTraverse.clear();
                traverseDone.clear();
                toTraverse.add(u);
                result = true;
            }
        }

        return result;
    }

    @VisibleForTesting
    boolean applyChoiceRule(ReducedFunction pApplyTo) {
        boolean result = false;

        Deque<ReducedNode> toTraverse = new ArrayDeque<>();
        Set<ReducedNode> traverseDone = new HashSet<>();

        toTraverse.add(pApplyTo.getEntryNode());
        while (toTraverse.size() > 0) {
            ReducedNode u = toTraverse.removeFirst();
            if (traverseDone.contains(u)) {
                continue;
            }

            List<ReducedEdge> leavingEdges = pApplyTo.getLeavingEdges(u);
            if (leavingEdges.size() < 2 || getSummarizationsOnNode(u) >= reductionThreshold) {
                for (ReducedEdge e : leavingEdges) {
                    toTraverse.add(e.getPointsTo());
                }
                traverseDone.add(u);
                continue;
            }

            // Find pairs of leaving edges, that point to the same target.
            boolean onePairMerged = false;
            for (int x = 0; x < leavingEdges.size() && !onePairMerged; x++) {
                for (int y = x + 1; y < leavingEdges.size() && !onePairMerged; y++) {
                    ReducedEdge edgeX = leavingEdges.get(x);
                    ReducedEdge edgeY = leavingEdges.get(y);

                    ReducedNode v1 = edgeX.getPointsTo();
                    ReducedNode v2 = edgeY.getPointsTo();

                    if (v1 == v2) {
                        ReducedEdge sumEdge = new ReducedEdge(v1);
                        sumEdge.addEdge(edgeX);
                        sumEdge.addEdge(edgeY);

                        pApplyTo.removeEdge(u, v1, edgeX);
                        pApplyTo.removeEdge(u, v2, edgeY);
                        pApplyTo.addEdge(u, v1, sumEdge);

                        incSummarizationsOnNode(u, 1);

                        onePairMerged = true;
                    } else {
                        toTraverse.add(v1);
                        toTraverse.add(v2);
                    }
                }
            }

            if (onePairMerged) {
                toTraverse.clear();
                traverseDone.clear();
                toTraverse.add(u);
                result = true;
            } else {
                traverseDone.add(u);
            }
        }

        return result;
    }

    private static class FunctionNodeManager {
        private final CFA cfa;
        private final Map<CFANode, ReducedNode> nodeMapping = new HashMap<>();
        private int functionCallId;

        public ReducedNode getWrapper(CFANode pNode) {
            ReducedNode result = nodeMapping.get(pNode);
            if (result == null) {
                boolean isLoopHead = cfa.getAllLoopHeads().get().contains(pNode);
                result = new ReducedNode(pNode, isLoopHead);
                result.setFunctionCallId(this.functionCallId);
                this.nodeMapping.put(pNode, result);
            }
            return result;
        }

        public FunctionNodeManager(int pFunctionCallId, CFA pCfa) {
            this.functionCallId = pFunctionCallId;
            this.cfa = pCfa;
        }
    }

    /**
     * Returns the summarized subgraph described by the function
     * and the outgoing function calls that get inlined.
     *
     */
    private ReducedFunction inlineAndSummarize(FunctionEntryNode pFunctionNode, CFA cfa) {
        this.functionCallSeq++;
        this.inliningStack.push(pFunctionNode);

        Set<CFAEdge> traversed = new HashSet<>();
        Deque<ReducedNode> openEndpoints = new ArrayDeque<>();
        FunctionNodeManager functionNodes = new FunctionNodeManager(this.functionCallSeq, cfa);

        ReducedNode entryNode = functionNodes.getWrapper(pFunctionNode);
        ReducedNode exitNode = functionNodes.getWrapper(pFunctionNode.getExitNode());
        ReducedFunction result = new ReducedFunction(entryNode, exitNode);

        // First: Inline called functions in a summarized version.
        //        --> recursion

        // Start traversing at the entry node of the function
        openEndpoints.add(result.getEntryNode());
        while (openEndpoints.size() > 0) {
            ReducedNode uSn = openEndpoints.removeFirst();

            // Look at all leaving edges.
            for (CFAEdge e : CFAUtils.leavingEdges(uSn.getWrapped())) {
                if (!traversed.add(e)) {
                    continue;
                }

                // Depending on the type of the edge...
                if (e instanceof CFunctionCallEdge) {
                    CFunctionCallEdge callEdge = (CFunctionCallEdge) e;
                    ReducedNode callReturnTarget = functionNodes
                            .getWrapper(callEdge.getSummaryEdge().getSuccessor());
                    FunctionEntryNode calledFunction = callEdge.getSuccessor();

                    if (inliningStack.contains(calledFunction)) {
                        // Ignoring recursion of
                        result.addEdge(uSn, callReturnTarget);
                    } else {
                        ReducedFunction functionSum = inlineAndSummarize(calledFunction, cfa);

                        result.insertFunctionSum(functionSum);
                        result.addEdge(uSn, functionSum.getEntryNode());

                        // it is possible, that a function never returns e.g. if there is a loop
                        // in the form of "labelXYZ: goto labelXYZ;"
                        // --> integrate the exit of the function to the control flow only if it has entering edges!
                        if (functionSum.getExitNode().getWrapped().getNumEnteringEdges() > 0) {
                            result.addEdge(functionSum.getExitNode(), callReturnTarget);
                        }
                    }

                    openEndpoints.add(callReturnTarget);
                } else {
                    if (e.getSuccessor() == pFunctionNode.getExitNode()) {
                        result.addEdge(uSn, exitNode);
                    } else {
                        ReducedNode vSn = functionNodes.getWrapper(e.getSuccessor());
                        result.addEdge(uSn, vSn);
                        openEndpoints.add(vSn);
                    }
                }
            }
        }

        // Second: Summarize this function if the
        //         summarization-threshold is not already reached.
        applyReductionSequences(result);

        inliningStack.pop();
        return result;
    }

    @VisibleForTesting
    void applyReductionSequences(ReducedFunction pApplyTo) {
        // Summarize the given function if the summarization-threshold is not already reached.
        boolean sequenceApplied, choiceApplied;
        do {
            sequenceApplied = applySequenceRule(pApplyTo);
            choiceApplied = applyChoiceRule(pApplyTo);
        } while (sequenceApplied || choiceApplied);
    }

    private String getRsfEntryFor(ReducedNode pNode) {
        return String.format("%s_%s_%d_%d", pNode.getWrapped().getFunctionName(), pNode.getNodeKindText(),
                pNode.getFunctionCallId(), pNode.getWrapped().getNodeNumber());
    }

    /**
     * Write the in-lined version of the CFA to the given output.
     */
    @VisibleForTesting
    void printInlinedCfa(Map<ReducedNode, Map<ReducedNode, Set<ReducedEdge>>> pInlinedCfa, Writer pOut)
            throws IOException {
        for (Entry<ReducedNode, Map<ReducedNode, Set<ReducedEdge>>> outerEntry : pInlinedCfa.entrySet()) {
            ReducedNode u = outerEntry.getKey();
            Map<ReducedNode, Set<ReducedEdge>> uTarget = outerEntry.getValue();
            for (Entry<ReducedNode, Set<ReducedEdge>> entry : uTarget.entrySet()) {
                ReducedNode v = entry.getKey();
                for (int i = 0; i < entry.getValue().size(); i++) {
                    pOut.append("REL\t").append(getRsfEntryFor(u)).append('\t').append(getRsfEntryFor(v))
                            .append(System.lineSeparator());
                }
            }
        }
    }

    /**
     * Compute the nodes of the given CFA that should be abstraction-nodes.
     */
    @Override
    public ImmutableSet<CFANode> computeAbstractionNodes(final CFA pCfa) {
        assert (pCfa != null);
        assert (this.inliningStack.size() == 0);
        assert (this.functionCallSeq == 0);

        this.functionCallSeq = 0;
        ReducedFunction reducedProgram = inlineAndSummarize(pCfa.getMainFunction(), pCfa);

        if (reducedCfaFile != null) {
            Map<ReducedNode, Map<ReducedNode, Set<ReducedEdge>>> inlinedCfa = reducedProgram.getInlinedCfa();
            try (Writer w = Files.openOutputFile(reducedCfaFile)) {
                printInlinedCfa(inlinedCfa, w);
            } catch (IOException e) {
                logger.logUserException(Level.WARNING, e, "Could not write the reduced CFA to file");
            }
        }

        Set<ReducedNode> abstractionNodes = reducedProgram.getAllActiveNodes();
        Set<CFANode> result = Sets.newHashSetWithExpectedSize(abstractionNodes.size());
        for (ReducedNode n : abstractionNodes) {
            result.add(n.getWrapped());
        }

        return ImmutableSet.copyOf(result);
    }
}