org.sosy_lab.cpachecker.cfa.postprocessing.global.singleloop.CFASingleLoopTransformation.java Source code

Java tutorial

Introduction

Here is the source code for org.sosy_lab.cpachecker.cfa.postprocessing.global.singleloop.CFASingleLoopTransformation.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.cfa.postprocessing.global.singleloop;

import static com.google.common.base.Predicates.*;
import static com.google.common.collect.FluentIterable.from;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Level;

import javax.annotation.Nullable;

import org.sosy_lab.common.Pair;
import org.sosy_lab.common.collect.CopyOnWriteSortedMap;
import org.sosy_lab.common.collect.PathCopyingPersistentTreeMap;
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.CFAReversePostorder;
import org.sosy_lab.cpachecker.cfa.Language;
import org.sosy_lab.cpachecker.cfa.MutableCFA;
import org.sosy_lab.cpachecker.cfa.ast.FileLocation;
import org.sosy_lab.cpachecker.cfa.ast.c.CBinaryExpressionBuilder;
import org.sosy_lab.cpachecker.cfa.ast.c.CDeclaration;
import org.sosy_lab.cpachecker.cfa.ast.c.CFunctionCall;
import org.sosy_lab.cpachecker.cfa.ast.c.CFunctionDeclaration;
import org.sosy_lab.cpachecker.cfa.ast.c.CIdExpression;
import org.sosy_lab.cpachecker.cfa.ast.c.CInitializerExpression;
import org.sosy_lab.cpachecker.cfa.ast.c.CIntegerLiteralExpression;
import org.sosy_lab.cpachecker.cfa.ast.c.CParameterDeclaration;
import org.sosy_lab.cpachecker.cfa.ast.c.CReturnStatement;
import org.sosy_lab.cpachecker.cfa.ast.c.CVariableDeclaration;
import org.sosy_lab.cpachecker.cfa.ast.java.JVariableDeclaration;
import org.sosy_lab.cpachecker.cfa.model.BlankEdge;
import org.sosy_lab.cpachecker.cfa.model.CFAEdge;
import org.sosy_lab.cpachecker.cfa.model.CFAEdgeType;
import org.sosy_lab.cpachecker.cfa.model.CFANode;
import org.sosy_lab.cpachecker.cfa.model.CFATerminationNode;
import org.sosy_lab.cpachecker.cfa.model.FunctionCallEdge;
import org.sosy_lab.cpachecker.cfa.model.FunctionEntryNode;
import org.sosy_lab.cpachecker.cfa.model.FunctionExitNode;
import org.sosy_lab.cpachecker.cfa.model.FunctionReturnEdge;
import org.sosy_lab.cpachecker.cfa.model.FunctionSummaryEdge;
import org.sosy_lab.cpachecker.cfa.model.MultiEdge;
import org.sosy_lab.cpachecker.cfa.model.c.CAssumeEdge;
import org.sosy_lab.cpachecker.cfa.model.c.CDeclarationEdge;
import org.sosy_lab.cpachecker.cfa.model.c.CFunctionCallEdge;
import org.sosy_lab.cpachecker.cfa.model.c.CFunctionEntryNode;
import org.sosy_lab.cpachecker.cfa.model.c.CFunctionReturnEdge;
import org.sosy_lab.cpachecker.cfa.model.c.CFunctionSummaryEdge;
import org.sosy_lab.cpachecker.cfa.model.c.CFunctionSummaryStatementEdge;
import org.sosy_lab.cpachecker.cfa.model.c.CLabelNode;
import org.sosy_lab.cpachecker.cfa.model.c.CReturnStatementEdge;
import org.sosy_lab.cpachecker.cfa.model.c.CStatementEdge;
import org.sosy_lab.cpachecker.cfa.model.java.JMethodEntryNode;
import org.sosy_lab.cpachecker.cfa.types.MachineModel;
import org.sosy_lab.cpachecker.cfa.types.c.CFunctionType;
import org.sosy_lab.cpachecker.cfa.types.c.CNumericTypes;
import org.sosy_lab.cpachecker.cfa.types.c.CStorageClass;
import org.sosy_lab.cpachecker.core.ShutdownNotifier;
import org.sosy_lab.cpachecker.util.CFAUtils;
import org.sosy_lab.cpachecker.util.LoopStructure;
import org.sosy_lab.cpachecker.util.LoopStructure.Loop;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.BiMap;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.collect.TreeMultimap;

/**
 * Instances of this class are used to apply single loop transformation to
 * control flow automata.
 */
@Options(prefix = "cfa.transformIntoSingleLoop")
public class CFASingleLoopTransformation {

    public static final String ARTIFICIAL_PROGRAM_COUNTER_FUNCTION_NAME = " ";

    /**
     * The name of the program counter variable introduced by the transformation.
     */
    public static final String PROGRAM_COUNTER_VAR_NAME = "___pc";

    /**
     * The description of the dummy edges used.
     */
    private static final String DUMMY_EDGE = "DummyEdge";

    private static final Predicate<CFAEdge> DUMMY_EDGE_PREDICATE = new Predicate<CFAEdge>() {

        @Override
        public boolean apply(@Nullable CFAEdge pArg0) {
            return isDummyEdge(pArg0);
        }
    };

    /**
     * The log manager.
     */
    private final LogManager logger;

    /**
     * The shutdown notifier used.
     */
    private final ShutdownNotifier shutdownNotifier;

    @Option(secure = true, description = "Single loop transformation builds a decision tree based on"
            + " the program counter values. This option causes the last program"
            + " counter value not to be explicitly assumed in the decision tree,"
            + " so that it is only indirectly represented by the assumption of"
            + " falsehood for all other assumptions in the decision tree.")
    private boolean omitExplicitLastProgramCounterAssumption = false;

    @Option(secure = true, description = "This option controls the size of the subgraphs referred"
            + " to by program counter values. The larger the subgraphs, the"
            + " fewer program counter values are required. Possible values are "
            + " MULTIPLE_PATHS, SINGLE_PATH and SINGLE_EDGE, where"
            + " MULTIPLE_PATHS has the largest subgraphs (and fewest program"
            + " counter values) and SINGLE_EDGE has the smallest subgraphs (and"
            + " most program counter values). The larger the subgraphs, the"
            + " closer the resulting graph will look like the original CFA.")
    private SubGraphGrowthStrategy subgraphGrowthStrategy = SubGraphGrowthStrategy.MULTIPLE_PATHS;

    /**
     * Creates a new single loop transformer.
     *
     * @param pLogger the log manager to be used.
     * @param pConfig the configuration used.
     *
     * @throws InvalidConfigurationException if the configuration is invalid.
     */
    private CFASingleLoopTransformation(LogManager pLogger, Configuration pConfig,
            ShutdownNotifier pShutdownNotifier) throws InvalidConfigurationException {
        this.logger = pLogger;
        this.shutdownNotifier = pShutdownNotifier;
        pConfig.inject(this);
    }

    /**
     * Gets a single loop transformation strategy using the given log manager, configuration and optional shutdown notifier.
     *
     * @param pLogger the log manager used.
     * @param pConfig the configuration used.
     * @param pShutdownNotifier the optional shutdown notifier.
     *
     * @return a single loop transformation strategy.
     *
     * @throws InvalidConfigurationException if the configuration is invalid.
     */
    public static CFASingleLoopTransformation getSingleLoopTransformation(LogManager pLogger, Configuration pConfig,
            @Nullable ShutdownNotifier pShutdownNotifier) throws InvalidConfigurationException {
        return new CFASingleLoopTransformation(pLogger, pConfig, pShutdownNotifier);
    }

    /**
     * Applies the single loop transformation to the given CFA.
     *
     * @param pInputCFA the control flow automaton to be transformed.
     * @param pVarClassification the variable classification.
     * @param pLoopStructure the current loop structure.
     *
     * @return a new CFA with at most one loop.
     * @throws InvalidConfigurationException if the configuration this transformer was created with is invalid.
     * @throws InterruptedException if a shutdown has been requested by the registered shutdown notifier.
     */
    public MutableCFA apply(MutableCFA pInputCFA) throws InvalidConfigurationException, InterruptedException {

        // If the transformation is not necessary, return the original graph
        if (pInputCFA.getLoopStructure().isPresent()) {
            LoopStructure loopStructure = pInputCFA.getLoopStructure().get();
            if (loopStructure.getCount() == 0) {
                // no loops, nothing to do
                return pInputCFA;
            }

            if (loopStructure.getCount() == 1) {
                Loop singleLoop = Iterables.getOnlyElement(loopStructure.getAllLoops());
                boolean modificationRequired = singleLoop.getLoopHeads().size() > 1
                        || from(singleLoop.getIncomingEdges()).filter(not(instanceOf(CFunctionReturnEdge.class)))
                                .size() > 1;
                if (!modificationRequired) {
                    return pInputCFA;
                }
            }
        }

        CopyOnWriteSortedMap<CFANode, CFANode> globalNewToOld = CopyOnWriteSortedMap
                .copyOf(PathCopyingPersistentTreeMap.<CFANode, CFANode>of());

        // Create new main function entry and initialize the program counter there
        FunctionEntryNode oldMainFunctionEntryNode = pInputCFA.getMainFunction();
        FunctionEntryNode start = (FunctionEntryNode) getOrCreateNewFromOld(oldMainFunctionEntryNode,
                globalNewToOld);
        SingleLoopHead loopHead = new SingleLoopHead();

        Queue<CFANode> nodes = new ArrayDeque<>(getAllNodes(oldMainFunctionEntryNode));

        // Eliminate self loops
        eliminateSelfLoops(nodes);

        Multimap<Integer, CFANode> newPredecessorsToPC = LinkedHashMultimap.create();
        Map<Integer, CFANode> newSuccessorsToPC = new LinkedHashMap<>();
        globalNewToOld.put(oldMainFunctionEntryNode, start);

        // Create new nodes and assume edges based on program counter values leading to the new nodes
        int pcValueOfStart = buildProgramCounterValueMaps(oldMainFunctionEntryNode, nodes, newPredecessorsToPC,
                newSuccessorsToPC, globalNewToOld);

        // Declare program counter and initialize it
        String pcVarName = PROGRAM_COUNTER_VAR_NAME;
        CInitializerExpression pcInitializer = new CInitializerExpression(FileLocation.DUMMY,
                new CIntegerLiteralExpression(FileLocation.DUMMY, CNumericTypes.INT,
                        BigInteger.valueOf(pcValueOfStart)));
        CDeclaration pcDeclaration = new CVariableDeclaration(FileLocation.DUMMY, true, CStorageClass.AUTO,
                CNumericTypes.INT, pcVarName, pcVarName, pcVarName, pcInitializer);
        CIdExpression pcIdExpression = new CIdExpression(FileLocation.DUMMY, pcDeclaration);
        CFANode declarationDummy = newSuccessorsToPC.get(pcValueOfStart);
        CFAEdge pcDeclarationEdge = new CDeclarationEdge(String.format("int %s = %d;", pcVarName, pcValueOfStart),
                FileLocation.DUMMY, start, declarationDummy, pcDeclaration);

        ImmutableBiMap<Integer, CFANode> newSuccessorsToPCImmutable = ImmutableBiMap.copyOf(newSuccessorsToPC);

        addToNodes(pcDeclarationEdge);

        // Remove trivial dummy subgraphs and other dummy edges etc.
        simplify(start, newPredecessorsToPC, newSuccessorsToPC, newSuccessorsToPCImmutable, globalNewToOld);

        /*
         * Connect the subgraph tails to their successors via the loop head by
         * setting the corresponding program counter values.
         */
        connectSubgraphLeavingNodesToLoopHead(loopHead, newPredecessorsToPC, pcIdExpression);

        // Connect the subgraph entry nodes by assuming the program counter values
        connectLoopHeadToSubgraphEntryNodes(loopHead, newSuccessorsToPC, pcIdExpression,
                new CBinaryExpressionBuilder(pInputCFA.getMachineModel(), logger));

        /*
         * Fix the summary edges broken by the indirection introduced by the
         * artificial loop.
         */
        fixSummaryEdges(start, newSuccessorsToPCImmutable, globalNewToOld);

        // Build the CFA from the syntactically reachable nodes
        return buildCFA(start, loopHead, pInputCFA.getMachineModel(), pInputCFA.getLanguage());
    }

    /**
     * Simplify the new graph by removing empty subgraphs and dummy edges.
     *
     * @param pStartNode the start node of the new control flow automaton.
     * @param pNewPredecessorsToPC the mapping of program counter value assignment predecessors to program counter values. Must be mutable.
     * @param pNewSuccessorsToPC the mapping of program counter value assumption successors to program counter values. Must be mutable.
     * @param pGlobalNewToOld
     * @throws InterruptedException if a shutdown has been requested by the registered shutdown notifier.
     */
    private void simplify(CFANode pStartNode, Multimap<Integer, CFANode> pNewPredecessorsToPC,
            Map<Integer, CFANode> pNewSuccessorsToPC, BiMap<Integer, CFANode> pImmutableNewSuccessorsToPC,
            Map<CFANode, CFANode> pGlobalNewToOld) throws InterruptedException {
        Map<CFANode, Integer> pcToNewSuccessors = pImmutableNewSuccessorsToPC.inverse();

        for (int replaceablePCValue : new ArrayList<>(pNewPredecessorsToPC.keySet())) {
            this.shutdownNotifier.shutdownIfNecessary();
            CFANode newSuccessor = pNewSuccessorsToPC.get(replaceablePCValue);
            List<CFANode> tailsOfRedundantSubgraph = new ArrayList<>(pNewPredecessorsToPC.get(replaceablePCValue));
            for (CFANode tailOfRedundantSubgraph : tailsOfRedundantSubgraph) {
                Integer precedingPCValue;
                CFAEdge dummyEdge;
                // If a subgraph consists only of a dummy edge, eliminate it completely
                if (tailOfRedundantSubgraph.getNumEnteringEdges() == 1
                        && isDummyEdge(dummyEdge = tailOfRedundantSubgraph.getEnteringEdge(0))
                        && dummyEdge.getPredecessor().getNumEnteringEdges() == 0
                        && (precedingPCValue = pcToNewSuccessors.get(dummyEdge.getPredecessor())) != null) {
                    Integer predToRemove = pcToNewSuccessors.get(newSuccessor);
                    CFANode removed = pNewSuccessorsToPC.remove(predToRemove);
                    assert removed == newSuccessor;
                    for (CFANode removedPredecessor : pNewPredecessorsToPC.removeAll(predToRemove)) {
                        pNewPredecessorsToPC.put(precedingPCValue, removedPredecessor);
                    }
                    pNewPredecessorsToPC.remove(precedingPCValue, tailOfRedundantSubgraph);
                    pNewSuccessorsToPC.remove(precedingPCValue);
                    pNewSuccessorsToPC.put(precedingPCValue, newSuccessor);
                }
            }
        }
        for (CFAEdge oldDummyEdge : findEdges(DUMMY_EDGE_PREDICATE, pStartNode)) {
            this.shutdownNotifier.shutdownIfNecessary();
            CFANode successor = pGlobalNewToOld.get(oldDummyEdge.getSuccessor());
            for (CFAEdge edge : CFAUtils.enteringEdges(successor).toList()) {
                if (isDummyEdge(edge)) {
                    removeFromNodes(edge);
                    CFANode predecessor = edge.getPredecessor();
                    /*
                     * If the subgraph is entered by a dummy edge adjust the program
                     * counter successor.
                     */
                    Integer precedingPCValue;
                    if (predecessor.getNumEnteringEdges() == 0
                            && (precedingPCValue = pcToNewSuccessors.get(predecessor)) != null) {
                        pcToNewSuccessors.remove(predecessor);
                        pNewSuccessorsToPC.remove(precedingPCValue);
                        pNewSuccessorsToPC.put(precedingPCValue, edge.getSuccessor());
                    } else {
                        /*
                         * If the dummy edge is somewhere in between, replace its
                         * predecessor by its successor in the graph.
                         */
                        for (CFAEdge edgeEnteringPredecessor : CFAUtils.enteringEdges(predecessor).toList()) {
                            removeFromNodes(edgeEnteringPredecessor);
                            edgeEnteringPredecessor = copyCFAEdgeWithNewNodes(edgeEnteringPredecessor,
                                    edgeEnteringPredecessor.getPredecessor(), successor, pGlobalNewToOld);
                            addToNodes(edgeEnteringPredecessor);
                        }
                    }
                }
            }
        }
    }

    /**
     * All function call edges calling functions where the summary edge
     * successor, i.e. the node succeeding the function call predecessor in the
     * caller function, is now a successor of the artificial decision tree, need
     * to be fixed in that their summary edge must now point to a different
     * successor: The predecessor of the assignment edge assigning the program
     * counter value that leads to the old successor.
     *
     * @param pStartNode the start node of the new graph.
     * @param pNewSuccessorsToPC the successor nodes of program counter value
     * assume edges mapped to their respective program counter value.
     * @param pGlobalNewToOld the mapping of new control flow nodes to old control
     * flow nodes.
     *
     * @throws InterruptedException if a shutdown has been requested by the registered shutdown notifier.
     */
    private void fixSummaryEdges(FunctionEntryNode pStartNode, ImmutableBiMap<Integer, CFANode> pNewSuccessorsToPC,
            Map<CFANode, CFANode> pGlobalNewToOld) throws InterruptedException {
        for (FunctionCallEdge fce : findEdges(FunctionCallEdge.class, pStartNode)) {
            FunctionEntryNode entryNode = fce.getSuccessor();
            FunctionExitNode exitNode = entryNode.getExitNode();
            FunctionSummaryEdge oldSummaryEdge = fce.getSummaryEdge();
            CFANode oldSummarySuccessor = fce.getSummaryEdge().getSuccessor();
            Integer pcValue = pNewSuccessorsToPC.inverse().get(oldSummarySuccessor);
            if (pcValue != null) {
                // Find the correct new successor
                for (CFANode potentialNewSummarySuccessor : CFAUtils.successorsOf(exitNode)) {
                    if (potentialNewSummarySuccessor.getNumLeavingEdges() == 1) {
                        CFAEdge potentialPCValueAssignmentEdge = potentialNewSummarySuccessor.getLeavingEdge(0);
                        if (potentialPCValueAssignmentEdge instanceof ProgramCounterValueAssignmentEdge) {
                            ProgramCounterValueAssignmentEdge programCounterValueAssignmentEdge = (ProgramCounterValueAssignmentEdge) potentialPCValueAssignmentEdge;
                            if (programCounterValueAssignmentEdge.getProgramCounterValue() == pcValue) {
                                FunctionSummaryEdge newSummaryEdge = (FunctionSummaryEdge) copyCFAEdgeWithNewNodes(
                                        oldSummaryEdge, oldSummaryEdge.getPredecessor(),
                                        potentialNewSummarySuccessor, pGlobalNewToOld);
                                // Fix function summary statement edges
                                for (CFunctionSummaryStatementEdge edge : CFAUtils
                                        .leavingEdges(oldSummaryEdge.getPredecessor())
                                        .filter(CFunctionSummaryStatementEdge.class)) {
                                    if (edge.getPredecessor() != newSummaryEdge.getPredecessor()
                                            || edge.getSuccessor() != newSummaryEdge.getSuccessor()) {
                                        removeFromNodes(edge);
                                        if (exitNode.getNumEnteringEdges() > 0) {
                                            CFAEdge newEdge = copyCFAEdgeWithNewNodes(edge,
                                                    newSummaryEdge.getPredecessor(), newSummaryEdge.getSuccessor(),
                                                    pGlobalNewToOld);
                                            addToNodes(newEdge);
                                        }
                                    }
                                }
                                // Fix the function summary edge
                                removeSummaryEdgeFromNodes(oldSummaryEdge);
                                oldSummaryEdge = newSummaryEdge.getPredecessor().getLeavingSummaryEdge();
                                if (oldSummaryEdge != null) {
                                    removeSummaryEdgeFromNodes(oldSummaryEdge);
                                }
                                newSummaryEdge.getPredecessor().addLeavingSummaryEdge(newSummaryEdge);
                                oldSummaryEdge = newSummaryEdge.getSuccessor().getEnteringSummaryEdge();
                                if (oldSummaryEdge != null) {
                                    removeSummaryEdgeFromNodes(oldSummaryEdge);
                                }
                                newSummaryEdge.getSuccessor().addEnteringSummaryEdge(newSummaryEdge);
                                break;
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Collects all matching edges reachable from the given start node.
     *
     * @param pTypeToken a token specifying the type of the edges.
     * @param pStartNode the node to start searching from.
     * @return all found matching edges.
     */
    private <T extends CFAEdge> Iterable<T> findEdges(Class<T> pTypeToken, CFANode pStartNode) {
        return from(findEdges(instanceOf(pTypeToken), pStartNode)).filter(pTypeToken);
    }

    /**
     * Collects all matching edges reachable from the given start node.
     *
     * @param pPredicate a predicate specifying the edges.
     * @param pStartNode the node to start searching from.
     * @return all found matching edges.
     */
    private Iterable<CFAEdge> findEdges(Predicate<? super CFAEdge> pPredicate, CFANode pStartNode) {
        Collection<CFAEdge> result = new ArrayList<>();
        Set<CFANode> visited = new HashSet<>();
        Queue<CFANode> waitlist = new ArrayDeque<>();
        waitlist.add(pStartNode);
        while (!waitlist.isEmpty()) {
            CFANode current = waitlist.poll();
            if (visited.add(current)) {
                for (CFAEdge edge : CFAUtils.leavingEdges(current)) {
                    if (pPredicate.apply(edge)) {
                        result.add(edge);
                    }
                    waitlist.add(edge.getSuccessor());
                }
            }
        }
        return result;
    }

    /**
     * Builds the program counter value maps.
     *
     * @param pOldMainFunctionEntryNode the old main function entry node.
     * @param pNodes the nodes of the original graph.
     * @param pNewPredecessorsToPC the mapping of program counter value assignment predecessors to program counter values. Must be mutable.
     * @param pNewSuccessorsToPC the mapping of program counter value assumption successors to program counter values. Must be mutable.
     * @param pGlobalNewToOld the mapping of new control flow nodes to old control flow nodes.
     *
     * @return the initial program counter value.
     * @throws InterruptedException if a shutdown has been requested by the registered shutdown notifier.
     */
    private int buildProgramCounterValueMaps(FunctionEntryNode pOldMainFunctionEntryNode, Iterable<CFANode> pNodes,
            Multimap<Integer, CFANode> pNewPredecessorsToPC, Map<Integer, CFANode> pNewSuccessorsToPC,
            CopyOnWriteSortedMap<CFANode, CFANode> pGlobalNewToOld) throws InterruptedException {
        Set<CFANode> visited = new HashSet<>();
        Map<CFANode, CFANode> tmpMap = new LinkedHashMap<>();
        AcyclicGraph subgraph = null;
        int pcValueOfStart = -1;

        Set<CFANode> newWaitlistNodes = new LinkedHashSet<>();
        List<Pair<CFANode, CFANode>> predecessorsAndSuccessors = new ArrayList<>();
        for (CFANode subgraphRoot : pNodes) {
            // Mark an unvisited node as visited or discard a visited node
            if (!visited.add(subgraphRoot)) {
                continue;
            }
            this.shutdownNotifier.shutdownIfNecessary();

            /*
             * Handle the old main entry node: There is a new main entry node and
             * there must only be one main entry node, so while the old node must be
             * represented in the transformed graph, it must no longer be a main
             * entry node.
             */
            boolean isOldMainEntryNode = subgraphRoot.equals(pOldMainFunctionEntryNode);
            if (isOldMainEntryNode) {
                subgraphRoot = new CFANode(subgraphRoot.getFunctionName());
                replaceInStructure(pOldMainFunctionEntryNode, subgraphRoot);
                CFANode newSubgraphRoot = getOrCreateNewFromOld(subgraphRoot, pGlobalNewToOld);
                pcValueOfStart = getPCValueFor(newSubgraphRoot);
                pNewSuccessorsToPC.put(pcValueOfStart, newSubgraphRoot);
                visited.add(subgraphRoot);
            }

            // Get an acyclic sub graph
            subgraph = subgraph == null ? new AcyclicGraph(subgraphRoot) : subgraph.reset(subgraphRoot);

            Deque<CFANode> waitlist = new ArrayDeque<>();
            waitlist.add(subgraphRoot);
            CopyOnWriteSortedMap<CFANode, CFANode> newToOld = CopyOnWriteSortedMap.copyOf(pGlobalNewToOld);
            while (!waitlist.isEmpty()) {
                CFANode current = waitlist.poll();
                assert subgraph.containsNode(current) && visited.contains(current);

                newWaitlistNodes.clear();
                predecessorsAndSuccessors.clear();

                Set<CFAEdge> edgesToRemove = new HashSet<>();
                Set<CFAEdge> edgesToAdd = new HashSet<>();

                for (CFAEdge edge : CFAUtils.leavingEdges(current).toList()) {
                    CFANode next = edge.getSuccessor();

                    assert current != next : "Self-loops must be eliminated previously";

                    // Add the edge to the subgraph if no cycle is introduced by it
                    if ((!visited.contains(next) || subgraph.containsNode(next))
                            && (edge.getEdgeType() == CFAEdgeType.ReturnStatementEdge
                                    || next instanceof CFATerminationNode
                                    || subgraphGrowthStrategy.isFurtherGrowthDesired(subgraph))
                            && subgraph.offerEdge(edge, shutdownNotifier)) {
                        if (visited.add(next)) {
                            newWaitlistNodes.add(next);
                        }
                    } else {
                        assert tmpMap.isEmpty();

                        /*
                         * Function call edges should stay with their successor, thus a
                         * dummy predecessor is introduced between the original predecessor
                         * and the edge. Also, all other edges of the old predecessor must
                         * be moved to the new predecessor.
                         */
                        if (edge.getEdgeType() == CFAEdgeType.FunctionCallEdge) {
                            newWaitlistNodes.clear();
                            predecessorsAndSuccessors.clear();
                            subgraph.abort();
                            newToOld = CopyOnWriteSortedMap.copyOf(pGlobalNewToOld);
                            edgesToAdd.clear();
                            edgesToRemove.clear();

                            FunctionCallEdge fce = (FunctionCallEdge) edge;
                            tmpMap.put(next, next);
                            tmpMap.put(fce.getSummaryEdge().getSuccessor(), fce.getSummaryEdge().getSuccessor());

                            // Replace the edge in the old graph and create a copy in the new graph
                            CFAEdge newEdge = replaceAndCopy(edge, tmpMap, newToOld, edgesToRemove, edgesToAdd);

                            // Compute the program counter for the replaced edge and map the nodes to it
                            CFANode newPredecessor = getOrCreateNewFromOld(current, newToOld);
                            CFANode newSuccessor = newEdge.getPredecessor();
                            predecessorsAndSuccessors.add(Pair.of(newPredecessor, newSuccessor));

                            // Move the other edges over
                            for (CFAEdge otherEdge : CFAUtils.leavingEdges(current).toList()) {
                                if (otherEdge != edge) {
                                    /*
                                     * Replace the edge in the old graph, as there is a new
                                     * predecessor, and create its equivalent in the new graph.
                                     */
                                    replaceAndCopy(otherEdge, tmpMap.get(current), otherEdge.getSuccessor(), tmpMap,
                                            newToOld, edgesToRemove, edgesToAdd);
                                }
                            }
                            tmpMap.clear();
                            // Skip the other edges, as they have already been dealt with
                            break;
                        } else {
                            /*
                             * Other edges should stay with their original predecessor, thus a
                             * dummy successor is introduced between the edge and the original
                             * successor
                             */
                            edgesToRemove.add(edge);
                            tmpMap.put(current, current);
                            CFAEdge replacementEdge = copyCFAEdgeWithNewNodes(edge, tmpMap);
                            // The replacement edge is added in place of the old edge
                            edgesToAdd.add(replacementEdge);
                            subgraph.addEdge(replacementEdge, shutdownNotifier);

                            CFANode dummy = replacementEdge.getSuccessor();

                            // Compute the program counter for the replaced edge and map the nodes to it
                            CFANode newPredecessor = getOrCreateNewFromOld(dummy, newToOld);
                            CFANode newSuccessor = getOrCreateNewFromOld(next, newToOld);
                            predecessorsAndSuccessors.add(Pair.of(newPredecessor, newSuccessor));
                        }
                        tmpMap.clear();
                    }
                }
                pGlobalNewToOld = CopyOnWriteSortedMap.copyOf(newToOld);
                subgraph.commit();
                waitlist.addAll(newWaitlistNodes);
                for (Pair<CFANode, CFANode> predecessorAndSuccessor : predecessorsAndSuccessors) {
                    CFANode predecessor = predecessorAndSuccessor.getFirst();
                    CFANode successor = predecessorAndSuccessor.getSecond();
                    registerTransitionThroughLoopHead(predecessor, successor, pNewPredecessorsToPC,
                            pNewSuccessorsToPC);
                }
                for (CFAEdge edgeToRemove : edgesToRemove) {
                    removeFromNodes(edgeToRemove);
                }
                for (CFAEdge edgeToAdd : edgesToAdd) {
                    addToNodes(edgeToAdd);
                }
            }

            // Copy the subgraph
            for (CFAEdge oldEdge : subgraph.getEdges()) {
                addToNodes(copyCFAEdgeWithNewNodes(oldEdge, pGlobalNewToOld));
            }
        }
        return pcValueOfStart;
    }

    private void registerTransitionThroughLoopHead(CFANode pPredecessor, CFANode pSuccessor,
            Multimap<Integer, CFANode> pPredecessorsToPC, Map<Integer, CFANode> pSuccessorsToPC) {
        if (!(pPredecessor instanceof CFATerminationNode)) {
            int pcToSuccessor = getPCValueFor(pSuccessor);
            pPredecessorsToPC.put(pcToSuccessor, pPredecessor);
            pSuccessorsToPC.put(pcToSuccessor, pSuccessor);
        }
    }

    private CFAEdge replaceAndCopy(CFAEdge pOriginalEdge, @Nullable Map<CFANode, CFANode> pOldToOld,
            Map<CFANode, CFANode> pGlobalNewToOld, Set<CFAEdge> pEdgesToRemove, Set<CFAEdge> pEdgesToAdd) {
        CFANode replacementPredecessor = getOrCreateNewFromOld(pOriginalEdge.getPredecessor(), pOldToOld);
        CFANode replacementSuccessor = getOrCreateNewFromOld(pOriginalEdge.getSuccessor(), pOldToOld);
        return replaceAndCopy(pOriginalEdge, replacementPredecessor, replacementSuccessor, pOldToOld,
                pGlobalNewToOld, pEdgesToRemove, pEdgesToAdd);
    }

    private CFAEdge replaceAndCopy(CFAEdge pOriginalEdge, CFANode pReplacementPredecessor,
            CFANode pReplacementSuccessor, @Nullable Map<CFANode, CFANode> pOldToOld,
            Map<CFANode, CFANode> pGlobalNewToOld, Set<CFAEdge> pEdgesToRemove, Set<CFAEdge> pEdgesToAdd) {
        pEdgesToRemove.add(pOriginalEdge);

        // Replace the edge in the old graph, as there is a new predecessor
        CFAEdge replacementEdge = copyCFAEdgeWithNewNodes(pOriginalEdge, pReplacementPredecessor,
                pReplacementSuccessor, pOldToOld);

        // The replacement edge is added in place of the old edge
        pEdgesToAdd.add(replacementEdge);

        // Create the actual edge in the new graph
        CFAEdge newEdge = copyCFAEdgeWithNewNodes(replacementEdge, pGlobalNewToOld);
        pEdgesToAdd.add(newEdge);
        return newEdge;
    }

    /**
     * Eliminates all self loops of the given nodes by introducing dummy nodes
     * and edges, so that at least two nodes are involved in any loop afterwards.
     *
     * @param pNodes the nodes to check for self loops.
     * @throws InterruptedException if a shutdown has been requested by the registered shutdown notifier.
     */
    private void eliminateSelfLoops(Collection<CFANode> pNodes) throws InterruptedException {
        List<CFANode> toAdd = new ArrayList<>();
        for (CFANode node : pNodes) {
            this.shutdownNotifier.shutdownIfNecessary();
            for (CFAEdge edge : CFAUtils.leavingEdges(node)) {

                CFANode successor = edge.getSuccessor();
                // Eliminate a direct self edge by introducing a dummy node in between
                if (successor == node) {
                    removeFromNodes(edge);

                    Map<CFANode, CFANode> tmpMap = new LinkedHashMap<>();
                    CFANode dummy = getOrCreateNewFromOld(successor, tmpMap);

                    tmpMap.put(node, node);
                    CFAEdge replacementEdge = copyCFAEdgeWithNewNodes(edge, node, dummy, tmpMap);
                    addToNodes(replacementEdge);

                    BlankEdge dummyEdge = new BlankEdge("", edge.getFileLocation(), dummy, successor, DUMMY_EDGE);
                    addToNodes(dummyEdge);

                    toAdd.add(dummy);

                    edge = dummyEdge;
                }

                // Replace obvious endless loop "while (x) {}" by "if (x) { TERMINATION NODE }"
                if (edge.getEdgeType() == CFAEdgeType.AssumeEdge) {
                    CFANode assumePredecessor = edge.getPredecessor();
                    CFANode current = edge.getSuccessor();
                    FluentIterable<CFAEdge> leavingEdges;
                    CFAEdge leavingEdge = edge;
                    List<CFAEdge> edgesToRemove = new ArrayList<>();
                    edgesToRemove.add(leavingEdge);
                    while (!current.equals(assumePredecessor)
                            && (leavingEdges = CFAUtils.leavingEdges(current)).size() == 1
                            && (leavingEdge = Iterables.getOnlyElement(leavingEdges))
                                    .getEdgeType() == CFAEdgeType.BlankEdge) {
                        current = leavingEdge.getSuccessor();
                        edgesToRemove.add(leavingEdge);
                    }
                    if (current.equals(assumePredecessor)) {
                        for (CFAEdge toRemove : edgesToRemove) {
                            removeFromNodes(toRemove);
                        }
                        CFANode terminationNode = new CFATerminationNode(assumePredecessor.getFunctionName());
                        CFAEdge newEdge = copyCFAEdgeWithNewNodes(edge, assumePredecessor, terminationNode,
                                new LinkedHashMap<CFANode, CFANode>());
                        addToNodes(newEdge);
                        toAdd.add(terminationNode);
                    }
                }
            }
        }
        pNodes.addAll(toAdd);
    }

    /**
     * Builds a CFA by collecting the nodes syntactically reachable from the
     * start node. Any nodes belonging to functions with unreachable entry nodes
     * are also omitted.
     *
     * @param pStartNode the start node.
     * @param pLoopHead the single loop head.
     * @param pMachineModel the machine model.
     * @param pLanguage the programming language.
     *
     * @return the CFA represented by the nodes reachable from the start node.
     *
     * @throws InvalidConfigurationException if the configuration is invalid.
     * @throws InterruptedException if a shutdown has been requested by the registered shutdown notifier.
     */
    private MutableCFA buildCFA(FunctionEntryNode pStartNode, CFANode pLoopHead, MachineModel pMachineModel,
            Language pLanguage) throws InvalidConfigurationException, InterruptedException {

        SortedMap<String, FunctionEntryNode> functions = new TreeMap<>();

        SortedSetMultimap<String, CFANode> allNodes = mapNodesToFunctions(pStartNode, functions);

        // Give the loop head the lowest post order id to ensure that it is always
        // traversed last
        pLoopHead.setReversePostorderId(-1);

        // Instantiate the transformed graph in a preliminary form
        MutableCFA cfa = new MutableCFA(pMachineModel, functions, allNodes, pStartNode, pLanguage);

        // Get information about the loop structure
        LoopStructure loopStructure = LoopStructure.getLoopStructureForSingleLoop(pLoopHead);
        cfa.setLoopStructure(Optional.of(loopStructure));

        // Finalize the transformed CFA
        return cfa;
    }

    /**
     * Maps all nodes reachable from the given start node to their functions and
     * builds a mapping of function entry nodes to their functions.
     *
     * @param pStartNode the start node.
     * @param functions the found functions will be stored in this map.
     *
     * @return all nodes reachable from the given start node mapped to their
     * functions.
     *
     * @throws InterruptedException if a shutdown has been requested by the registered shutdown notifier.
     */
    private SortedSetMultimap<String, CFANode> mapNodesToFunctions(FunctionEntryNode pStartNode,
            Map<String, FunctionEntryNode> functions) throws InterruptedException {
        SortedSetMultimap<String, CFANode> allNodes = TreeMultimap.create();
        FunctionExitNode artificialFunctionExitNode = new FunctionExitNode(
                ARTIFICIAL_PROGRAM_COUNTER_FUNCTION_NAME);
        CFunctionDeclaration artificialFunctionDeclaration = new CFunctionDeclaration(FileLocation.DUMMY,
                CFunctionType.NO_ARGS_VOID_FUNCTION, ARTIFICIAL_PROGRAM_COUNTER_FUNCTION_NAME,
                ImmutableList.<CParameterDeclaration>of());
        FunctionEntryNode artificialFunctionEntryNode = new CFunctionEntryNode(FileLocation.DUMMY,
                artificialFunctionDeclaration, artificialFunctionExitNode, Collections.<String>emptyList(),
                Optional.<CVariableDeclaration>absent());
        Set<CFANode> nodes = getAllNodes(pStartNode);
        for (CFANode node : nodes) {
            for (CFAEdge leavingEdge : CFAUtils.allLeavingEdges(node).toList()) {
                if (!nodes.contains(leavingEdge.getSuccessor())) {
                    removeFromNodes(leavingEdge);
                }
            }
            allNodes.put(node.getFunctionName(), node);
            if (node instanceof FunctionEntryNode) {
                functions.put(node.getFunctionName(), (FunctionEntryNode) node);
            } else if (node.getFunctionName().equals(ARTIFICIAL_PROGRAM_COUNTER_FUNCTION_NAME)) {
                functions.put(node.getFunctionName(), artificialFunctionEntryNode);
            }
        }

        // Assign reverse post order ids to the control flow nodes
        Collection<CFANode> nodesWithNoIdAssigned = getAllNodes(pStartNode);
        for (CFANode n : nodesWithNoIdAssigned) {
            n.setReversePostorderId(-1);
        }
        while (!nodesWithNoIdAssigned.isEmpty()) {
            this.shutdownNotifier.shutdownIfNecessary();
            CFAReversePostorder sorter = new CFAReversePostorder();
            sorter.assignSorting(nodesWithNoIdAssigned.iterator().next());
            nodesWithNoIdAssigned = from(nodesWithNoIdAssigned).filter(new Predicate<CFANode>() {

                @Override
                public boolean apply(@Nullable CFANode pArg0) {
                    if (pArg0 == null) {
                        return false;
                    }
                    return pArg0.getReversePostorderId() < 0;
                }

            }).toList();
        }
        return allNodes;
    }

    /**
     * Removes the given node from its graph by removing all its entering edges
     * from its predecessors and all its leaving edges from its successors.
     *
     * All these edges are also removed from the node itself, of course.
     *
     * @param pToRemove the node to be removed.
     */
    private static void removeFromGraph(CFANode pToRemove) {
        while (pToRemove.getNumEnteringEdges() > 0) {
            removeFromNodes(pToRemove.getEnteringEdge(0));
        }
        if (pToRemove.getEnteringSummaryEdge() != null) {
            removeSummaryEdgeFromNodes(pToRemove.getEnteringSummaryEdge());
        }
        while (pToRemove.getNumLeavingEdges() > 0) {
            removeFromNodes(pToRemove.getLeavingEdge(0));
        }
        if (pToRemove.getLeavingSummaryEdge() != null) {
            removeSummaryEdgeFromNodes(pToRemove.getLeavingSummaryEdge());
        }
    }

    /**
     * Removes the given edge from its nodes.
     *
     * @param pEdge the edge to remove.
     */
    private static void removeFromNodes(CFAEdge pEdge) {
        if (pEdge instanceof FunctionSummaryEdge) {
            removeSummaryEdgeFromNodes((FunctionSummaryEdge) pEdge);
        } else {
            pEdge.getPredecessor().removeLeavingEdge(pEdge);
            pEdge.getSuccessor().removeEnteringEdge(pEdge);
            if (pEdge instanceof FunctionCallEdge) {
                FunctionCallEdge functionCallEdge = (FunctionCallEdge) pEdge;
                FunctionSummaryEdge summaryEdge = functionCallEdge.getSummaryEdge();
                if (summaryEdge != null) {
                    removeSummaryEdgeFromNodes(summaryEdge);
                }
            }
        }
    }

    /**
     * Removes the given summary edge from its nodes.
     *
     * @param pEdge the edge to remove.
     */
    private static void removeSummaryEdgeFromNodes(FunctionSummaryEdge pEdge) {
        CFANode predecessor = pEdge.getPredecessor();
        if (predecessor.getLeavingSummaryEdge() == pEdge) {
            predecessor.removeLeavingSummaryEdge(pEdge);
        }
        CFANode successor = pEdge.getSuccessor();
        if (successor.getEnteringSummaryEdge() == pEdge) {
            successor.removeEnteringSummaryEdge(pEdge);
        }
    }

    /**
     * Adds the given edge as a leaving edge to its predecessor and as an
     * entering edge to its successor.
     *
     * @param pEdge the edge to add.
     */
    private static void addToNodes(CFAEdge pEdge) {
        CFANode predecessor = pEdge.getPredecessor();
        if (pEdge instanceof FunctionSummaryEdge) {
            FunctionSummaryEdge summaryEdge = (FunctionSummaryEdge) pEdge;
            FunctionSummaryEdge oldSummaryEdge = pEdge.getPredecessor().getLeavingSummaryEdge();
            if (oldSummaryEdge != null) {
                removeSummaryEdgeFromNodes(oldSummaryEdge);
            }
            oldSummaryEdge = pEdge.getSuccessor().getEnteringSummaryEdge();
            if (oldSummaryEdge != null) {
                removeSummaryEdgeFromNodes(oldSummaryEdge);
            }
            predecessor.addLeavingSummaryEdge(summaryEdge);
            pEdge.getSuccessor().addEnteringSummaryEdge(summaryEdge);
        } else {
            assert predecessor.getNumLeavingEdges() == 0
                    || predecessor.getNumLeavingEdges() <= 1 && pEdge.getEdgeType() == CFAEdgeType.AssumeEdge
                    || predecessor instanceof FunctionExitNode
                            && pEdge.getEdgeType() == CFAEdgeType.FunctionReturnEdge
                    || predecessor.getLeavingEdge(0).getEdgeType() == CFAEdgeType.FunctionCallEdge
                            && pEdge.getEdgeType() == CFAEdgeType.StatementEdge
                    || predecessor.getLeavingEdge(0).getEdgeType() == CFAEdgeType.StatementEdge
                            && pEdge.getEdgeType() == CFAEdgeType.FunctionCallEdge;
            predecessor.addLeavingEdge(pEdge);
            pEdge.getSuccessor().addEnteringEdge(pEdge);
        }
    }

    /**
     * Connects the nodes leaving a subgraph to the loop head using assignment
     * edges setting the program counter to the value required for reaching the
     * correct successor in the next loop iteration.
     *
     * @param pLoopHead the loop head.
     * @param pNewPredecessorsToPC the nodes that represent gates for leaving
     * their subgraph mapped to the program counter values corresponding to the
     * correct successor states.
     * @param pPCIdExpression the CIdExpression used for the program counter variable.
     */
    private static void connectSubgraphLeavingNodesToLoopHead(CFANode pLoopHead,
            Multimap<Integer, CFANode> pNewPredecessorsToPC, CIdExpression pPCIdExpression) {
        Map<Integer, CFANode> connectionNodes = new HashMap<>();
        for (Map.Entry<Integer, CFANode> newPredecessorToPC : pNewPredecessorsToPC.entries()) {
            int pcToSet = newPredecessorToPC.getKey();
            CFANode connectionNode = connectionNodes.get(pcToSet);
            if (connectionNode == null) {
                connectionNode = new CFANode(ARTIFICIAL_PROGRAM_COUNTER_FUNCTION_NAME);
                connectionNodes.put(pcToSet, connectionNode);
                CFAEdge edgeToLoopHead = createProgramCounterAssignmentEdge(connectionNode, pLoopHead,
                        pPCIdExpression, pcToSet);
                addToNodes(edgeToLoopHead);
            }
            CFANode subgraphPredecessor = newPredecessorToPC.getValue();
            CFAEdge dummyEdge = new BlankEdge("", FileLocation.DUMMY, subgraphPredecessor, connectionNode, "");
            addToNodes(dummyEdge);
        }
    }

    /**
     * Connects subgraph entry nodes to the loop head via program counter value assume edges.
     *
     * @param pLoopHead the loop head.
     * @param newSuccessorToPCMapping the mapping of subgraph entry nodes to the
     * corresponding program counter values.
     * @param pPCIdExpression the CIdExpression used for the program counter variable.
     * @param pExpressionBuilder the CExpressionBuilder used to build the assume edges.
     */
    private void connectLoopHeadToSubgraphEntryNodes(SingleLoopHead pLoopHead,
            Map<Integer, CFANode> newSuccessorToPCMapping, CIdExpression pPCIdExpression,
            CBinaryExpressionBuilder pExpressionBuilder) {
        List<ProgramCounterValueAssumeEdge> toAdd = new ArrayList<>();
        CFANode decisionTreeNode = pLoopHead;
        Map<CFANode, CFANode> tmpMap = new LinkedHashMap<>();
        for (Entry<Integer, CFANode> pcToNewSuccessorMapping : newSuccessorToPCMapping.entrySet()) {
            CFANode newSuccessor = pcToNewSuccessorMapping.getValue();
            int pcToSet = pcToNewSuccessorMapping.getKey();

            /*
             * A subgraph root should only be entered via the loop head;
             * other entries must be redirected.
             */
            if (newSuccessor.getNumEnteringEdges() > 0) {
                assert tmpMap.isEmpty();
                ProgramCounterValueAssignmentEdge pcAssignmentEdge = pLoopHead.getEnteringAssignmentEdge(pcToSet);
                if (pcAssignmentEdge != null) {
                    CFANode dummySuccessor = pcAssignmentEdge.getPredecessor();
                    for (CFAEdge enteringEdge : CFAUtils.allEnteringEdges(newSuccessor).toList()) {
                        CFANode replacementSuccessor = tmpMap.get(enteringEdge.getSuccessor());
                        if (replacementSuccessor == null) {
                            replacementSuccessor = getOrCreateNewFromOld(enteringEdge.getSuccessor(), tmpMap);
                            CFAEdge connectionEdge = new BlankEdge("", FileLocation.DUMMY, replacementSuccessor,
                                    dummySuccessor, "");
                            addToNodes(connectionEdge);
                        }
                        CFAEdge replacementEdge = copyCFAEdgeWithNewNodes(enteringEdge,
                                enteringEdge.getPredecessor(), replacementSuccessor, tmpMap);
                        removeFromNodes(enteringEdge);
                        addToNodes(replacementEdge);
                    }
                    tmpMap.clear();
                }
            }

            // Connect the subgraph entry nodes to the loop header by assuming the program counter value
            CFANode newDecisionTreeNode = new CFANode(ARTIFICIAL_PROGRAM_COUNTER_FUNCTION_NAME);
            ProgramCounterValueAssumeEdge toSequence = createProgramCounterAssumeEdge(pExpressionBuilder,
                    decisionTreeNode, newSuccessor, pPCIdExpression, pcToSet, true);
            ProgramCounterValueAssumeEdge toNewDecisionTreeNode = createProgramCounterAssumeEdge(pExpressionBuilder,
                    decisionTreeNode, newDecisionTreeNode, pPCIdExpression, pcToSet, false);
            toAdd.add(toSequence);
            toAdd.add(toNewDecisionTreeNode);
            decisionTreeNode = newDecisionTreeNode;
        }
        /*
         * At the end of the decision tree, there is now an unreachable dangling
         * assume edge node. This does not hurt, but can be optimized out:
         */
        if (!toAdd.isEmpty()) {
            if (omitExplicitLastProgramCounterAssumption) {
                // The last edge is superfluous
                removeLast(toAdd);
                /*
                 * The last positive edge is thus the only relevant edge after the edge
                 * leading to its predecessor
                 */
                CFAEdge lastTrueEdge = removeLast(toAdd);
                /*
                 * The successor of the edge leading to the predecessor of the last
                 * positive edge can thus be set to the last relevant node
                 */
                if (!toAdd.isEmpty()) {
                    ProgramCounterValueAssumeEdge secondToLastFalseEdge = removeLast(toAdd);
                    ProgramCounterValueAssumeEdge newLastEdge = createProgramCounterAssumeEdge(pExpressionBuilder,
                            secondToLastFalseEdge.getPredecessor(), lastTrueEdge.getSuccessor(), pPCIdExpression,
                            secondToLastFalseEdge.getProgramCounterValue(), false);
                    toAdd.add(newLastEdge);
                } else {
                    BlankEdge edge = new BlankEdge("", FileLocation.DUMMY, pLoopHead, lastTrueEdge.getSuccessor(),
                            "");
                    addToNodes(edge);
                }
            } else {
                BlankEdge defaultBackEdge = new BlankEdge("", FileLocation.DUMMY, decisionTreeNode,
                        new CFATerminationNode(ARTIFICIAL_PROGRAM_COUNTER_FUNCTION_NAME),
                        "Illegal program counter value");
                addToNodes(defaultBackEdge);
            }
        }
        // Add the edges connecting the real nodes with the loop head
        for (CFAEdge edge : toAdd) {
            addToNodes(edge);
        }

    }

    /**
     * Removes the last element of a list.
     *
     * @param pList the list to get the element from.
     * @return the last element of the list.
     *
     * @throws NoSuchElementException if the list is empty.
     */
    private static <T> T removeLast(List<T> pList) {
        int index = pList.size() - 1;
        if (index < 0) {
            throw new NoSuchElementException();
        }
        return pList.remove(index);
    }

    /**
     * Gets all nodes syntactically reachable from the given start node.
     *
     * @param pStartNode the start node of the search.
     * @return all nodes syntactically reachable from the given start node.
     */
    private static Set<CFANode> getAllNodes(CFANode pStartNode) {
        Set<CFANode> nodes = new LinkedHashSet<>();
        Queue<CFANode> waitlist = new ArrayDeque<>();
        Queue<Deque<FunctionSummaryEdge>> callstacks = new ArrayDeque<>();
        waitlist.add(pStartNode);
        callstacks.offer(new ArrayDeque<FunctionSummaryEdge>());
        Set<CFANode> ignoredNodes = new HashSet<>();
        while (!waitlist.isEmpty()) {
            CFANode current = waitlist.poll();
            Deque<FunctionSummaryEdge> currentCallstack = callstacks.poll();
            if (nodes.add(current)) {
                for (CFAEdge leavingEdge : CFAUtils.allLeavingEdges(current)) {
                    final Deque<FunctionSummaryEdge> newCallstack;
                    CFANode successor = leavingEdge.getSuccessor();
                    if (leavingEdge instanceof FunctionCallEdge) {
                        newCallstack = new ArrayDeque<>(currentCallstack);
                        newCallstack.push(((FunctionCallEdge) leavingEdge).getSummaryEdge());
                    } else if (leavingEdge instanceof FunctionReturnEdge) {
                        if (currentCallstack.isEmpty()) {
                            ignoredNodes.add(successor);
                            continue;
                        }
                        newCallstack = new ArrayDeque<>(currentCallstack);
                        FunctionSummaryEdge summaryEdge = newCallstack.pop();
                        if (!summaryEdge.equals(((FunctionReturnEdge) leavingEdge).getSummaryEdge())) {
                            ignoredNodes.add(successor);
                            continue;
                        }
                    } else {
                        newCallstack = currentCallstack;
                    }
                    ignoredNodes.addAll(CFAUtils.predecessorsOf(current).toList());
                    waitlist.offer(successor);
                    callstacks.offer(currentCallstack);
                }
            }
        }

        ignoredNodes.removeAll(nodes);
        for (CFANode ignoredNode : ignoredNodes) {
            removeFromGraph(ignoredNode);
        }
        for (CFANode node : nodes) {
            for (CFANode predecessor : CFAUtils.predecessorsOf(node).toList()) {
                if (!nodes.contains(predecessor)) {
                    assert !CFAUtils.predecessorsOf(predecessor).anyMatch(in(nodes));
                    removeFromGraph(predecessor);
                }
            }
            for (CFANode successor : CFAUtils.successorsOf(node).toList()) {
                if (!nodes.contains(successor)) {
                    assert !CFAUtils.successorsOf(successor).anyMatch(in(nodes));
                    removeFromGraph(successor);
                }
            }
        }
        return nodes;
    }

    /**
     * Replaces the given old node by the given new node in its structure by
     * removing all edges leading to and from the old node and copying them so
     * that the leave or lead to the new node.
     *
     * @param pOldNode the node to remove the edges from.
     * @param pNewNode the node to add the edges to.
     */
    private void replaceInStructure(CFANode pOldNode, CFANode pNewNode) {
        Map<CFANode, CFANode> newToOld = new LinkedHashMap<>();
        newToOld.put(pOldNode, pNewNode);
        CFAEdge oldEdge;
        while ((oldEdge = removeNextEnteringEdge(pOldNode)) != null
                && constantTrue(newToOld.put(oldEdge.getPredecessor(), oldEdge.getPredecessor()))
                || (oldEdge = removeNextLeavingEdge(pOldNode)) != null
                        && constantTrue(newToOld.put(oldEdge.getSuccessor(), oldEdge.getSuccessor()))) {
            CFAEdge newEdge = copyCFAEdgeWithNewNodes(oldEdge, newToOld);
            addToNodes(newEdge);
        }
    }

    /**
     * Returns <code>true</code>, no matter what is passed.
     *
     * @param pParam this argument is ignored.
     * @return <code>true</code>
     */
    private static boolean constantTrue(Object pParam) {
        return true;
    }

    /**
     * Removes the next entering edge from the given node and the predecessor of
     * the edge.
     *
     * @param pCfaNode the node to remove an edge from.
     *
     * @return the removed edge.
     */
    @Nullable
    private static CFAEdge removeNextEnteringEdge(CFANode pCfaNode) {
        int numberOfEnteringEdges = pCfaNode.getNumEnteringEdges();
        CFAEdge result = null;
        if (numberOfEnteringEdges > 0) {
            result = pCfaNode.getEnteringEdge(numberOfEnteringEdges - 1);
        }
        if (result == null) {
            result = pCfaNode.getEnteringSummaryEdge();
        }
        if (result != null) {
            removeFromNodes(result);
        }
        return result;
    }

    /**
     * Removes the next leaving edge from the given node and the successor of
     * the edge.
     *
     * @param pCfaNode the node to remove an edge from.
     *
     * @return the removed edge.
     */
    @Nullable
    private static CFAEdge removeNextLeavingEdge(CFANode pCfaNode) {
        int numberOfLeavingEdges = pCfaNode.getNumLeavingEdges();
        CFAEdge result = null;
        if (numberOfLeavingEdges > 0) {
            result = pCfaNode.getLeavingEdge(numberOfLeavingEdges - 1);
        }
        if (result == null) {
            result = pCfaNode.getLeavingSummaryEdge();
        }
        if (result != null) {
            removeFromNodes(result);
        }
        return result;
    }

    /**
     * Given a node and a mapping of nodes of one graph to nodes of a different
     * graph, assumes that the given node belongs to the first graph and checks
     * if a node of the second graph is mapped to it. If so, the node of the
     * second graph mapped to the given node is returned. Otherwise, the given
     * node is copied <strong>without edges</strong> and recorded in the mapping
     * before it is returned.
     *
     * If the provided mapping is {@code null}, no copy but the given node itself
     * is returned.
     *
     * @param pNode the node to get or create a partner for in the second graph.
     * @param pNewToOldMapping the mapping between the first graph to the second
     * graph. If {@code null}, the identity is returned.
     *
     * @return a copy of the given node, possibly reused from the provided
     * mapping or the identity of the given node if the provided mapping is
     * {@code null}.
     */
    private CFANode getOrCreateNewFromOld(CFANode pNode, @Nullable Map<CFANode, CFANode> pNewToOldMapping) {
        if (pNewToOldMapping == null) {
            return pNode;
        }

        CFANode result = pNewToOldMapping.get(pNode);
        if (result != null) {
            return result;
        }

        String functionName = pNode.getFunctionName();

        if (pNode instanceof CLabelNode) {

            result = new CLabelNode(functionName, ((CLabelNode) pNode).getLabel());

        } else if (pNode instanceof CFunctionEntryNode) {

            CFunctionEntryNode functionEntryNode = (CFunctionEntryNode) pNode;
            FunctionExitNode functionExitNode = (FunctionExitNode) getOrCreateNewFromOld(
                    functionEntryNode.getExitNode(), pNewToOldMapping);
            result = functionExitNode.getEntryNode();

        } else if (pNode instanceof JMethodEntryNode) {

            JMethodEntryNode methodEntryNode = (JMethodEntryNode) pNode;
            FunctionExitNode functionExitNode = (FunctionExitNode) getOrCreateNewFromOld(
                    methodEntryNode.getExitNode(), pNewToOldMapping);
            result = functionExitNode.getEntryNode();

        } else if (pNode instanceof FunctionExitNode) {

            FunctionExitNode oldFunctionExitNode = (FunctionExitNode) pNode;
            FunctionEntryNode precomputedEntryNode = (FunctionEntryNode) pNewToOldMapping
                    .get(oldFunctionExitNode.getEntryNode());

            if (precomputedEntryNode != null) {
                return precomputedEntryNode.getExitNode();
            }

            FunctionExitNode functionExitNode = new FunctionExitNode(functionName);

            FunctionEntryNode oldEntryNode = oldFunctionExitNode.getEntryNode();
            FileLocation entryFileLocation = oldEntryNode.getFileLocation();
            final FunctionEntryNode functionEntryNode;
            if (oldEntryNode instanceof CFunctionEntryNode) {
                functionEntryNode = new CFunctionEntryNode(entryFileLocation,
                        ((CFunctionEntryNode) oldEntryNode).getFunctionDefinition(), functionExitNode,
                        oldEntryNode.getFunctionParameterNames(),
                        ((CFunctionEntryNode) oldEntryNode).getReturnVariable());
            } else if (oldEntryNode instanceof JMethodEntryNode) {
                functionEntryNode = new JMethodEntryNode(entryFileLocation,
                        ((JMethodEntryNode) oldEntryNode).getFunctionDefinition(), functionExitNode,
                        oldEntryNode.getFunctionParameterNames(),
                        ((JMethodEntryNode) oldEntryNode).getReturnVariable());
            } else {
                throw new AssertionError();
            }
            functionExitNode.setEntryNode(functionEntryNode);

            pNewToOldMapping.put(pNode, functionExitNode);
            pNewToOldMapping.put(oldFunctionExitNode, functionEntryNode);

            result = functionExitNode;

        } else if (pNode instanceof CFATerminationNode) {

            result = new CFATerminationNode(functionName);

        } else if (pNode.getClass() != CFANode.class) {

            Class<? extends CFANode> clazz = pNode.getClass();
            Class<?>[] requiredParameterTypes = new Class<?>[] { int.class, String.class };
            for (Constructor<?> cons : clazz.getConstructors()) {
                if (cons.isAccessible() && Arrays.equals(cons.getParameterTypes(), requiredParameterTypes)) {
                    try {
                        result = (CFANode) cons.newInstance(functionName);
                        break;
                    } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                            | InvocationTargetException e) {
                        result = null;
                    }
                }
            }

            if (result == null) {
                result = new CFANode(functionName);
                this.logger.log(Level.WARNING,
                        "Unknown node type " + clazz + "; created copy as instance of base type CFANode.");
            } else {
                this.logger.log(Level.WARNING, "Unknown node type " + clazz + "; created copy by reflection.");
            }

        } else {
            result = new CFANode(functionName);
        }
        pNewToOldMapping.put(pNode, result);
        return result;
    }

    /**
     * Copies the given control flow edge using the given new predecessor and
     * successor. Any additionally required nodes are taken from the given
     * mapping by using the corresponding node of the old edge as a key or, if
     * no node is mapped to this key, by copying the key and recording the result
     * in the mapping.
     *
     * @param pEdge the edge to copy.
     * @param pNewPredecessor the new predecessor.
     * @param pNewSuccessor the new successor.
     * @param pNewToOldMapping a mapping of old nodes to new nodes.
     *
     * @return a new edge with the given predecessor and successor.
     */
    private CFAEdge copyCFAEdgeWithNewNodes(CFAEdge pEdge, CFANode pNewPredecessor, CFANode pNewSuccessor,
            final Map<CFANode, CFANode> pNewToOldMapping) {
        String rawStatement = pEdge.getRawStatement();
        FileLocation fileLocation = pEdge.getFileLocation();
        switch (pEdge.getEdgeType()) {
        case AssumeEdge:
            CAssumeEdge assumeEdge = (CAssumeEdge) pEdge;
            return new CAssumeEdge(rawStatement, fileLocation, pNewPredecessor, pNewSuccessor,
                    assumeEdge.getExpression(), assumeEdge.getTruthAssumption());
        case BlankEdge:
            return new BlankEdge(rawStatement, fileLocation, pNewPredecessor, pNewSuccessor,
                    pEdge.getDescription());
        case DeclarationEdge:
            CDeclarationEdge declarationEdge = (CDeclarationEdge) pEdge;
            return new CDeclarationEdge(rawStatement, fileLocation, pNewPredecessor, pNewSuccessor,
                    declarationEdge.getDeclaration());
        case FunctionCallEdge: {
            if (!(pNewSuccessor instanceof FunctionEntryNode)) {
                throw new IllegalArgumentException(
                        "The successor of a function call edge must be a function entry node.");
            }
            CFunctionCallEdge functionCallEdge = (CFunctionCallEdge) pEdge;
            FunctionSummaryEdge oldSummaryEdge = functionCallEdge.getSummaryEdge();
            CFunctionSummaryEdge functionSummaryEdge = (CFunctionSummaryEdge) copyCFAEdgeWithNewNodes(
                    oldSummaryEdge, pNewPredecessor,
                    getOrCreateNewFromOld(oldSummaryEdge.getSuccessor(), pNewToOldMapping), pNewToOldMapping);
            addToNodes(functionSummaryEdge);
            Optional<CFunctionCall> cFunctionCall = functionCallEdge.getRawAST();
            return new CFunctionCallEdge(rawStatement, fileLocation, pNewPredecessor,
                    (CFunctionEntryNode) pNewSuccessor, cFunctionCall.orNull(), functionSummaryEdge);
        }
        case FunctionReturnEdge:
            if (!(pNewPredecessor instanceof FunctionExitNode)) {
                throw new IllegalArgumentException(
                        "The predecessor of a function return edge must be a function exit node.");
            }
            CFunctionReturnEdge functionReturnEdge = (CFunctionReturnEdge) pEdge;
            CFunctionSummaryEdge oldSummaryEdge = functionReturnEdge.getSummaryEdge();
            CFANode functionCallPred = oldSummaryEdge.getPredecessor();
            CFANode functionSummarySucc = oldSummaryEdge.getSuccessor();
            // If there is a conflicting summary edge, never use the one stored with the function return edge
            if (oldSummaryEdge != functionCallPred.getLeavingSummaryEdge()
                    && functionCallPred.getLeavingSummaryEdge() != null) {
                oldSummaryEdge = (CFunctionSummaryEdge) functionCallPred.getLeavingSummaryEdge();
            } else if (oldSummaryEdge != functionSummarySucc.getEnteringSummaryEdge()
                    && functionSummarySucc.getEnteringSummaryEdge() != null) {
                oldSummaryEdge = (CFunctionSummaryEdge) functionSummarySucc.getEnteringSummaryEdge();
            }
            CFunctionSummaryEdge functionSummaryEdge = (CFunctionSummaryEdge) copyCFAEdgeWithNewNodes(
                    oldSummaryEdge, pNewToOldMapping);
            addToNodes(functionSummaryEdge);
            return new CFunctionReturnEdge(fileLocation, (FunctionExitNode) pNewPredecessor, pNewSuccessor,
                    functionSummaryEdge);
        case MultiEdge:
            MultiEdge multiEdge = (MultiEdge) pEdge;
            return new MultiEdge(pNewPredecessor, pNewSuccessor,
                    from(multiEdge.getEdges()).transform(new Function<CFAEdge, CFAEdge>() {

                        @Override
                        @Nullable
                        public CFAEdge apply(@Nullable CFAEdge pOldEdge) {
                            if (pOldEdge == null) {
                                return null;
                            }
                            return copyCFAEdgeWithNewNodes(pOldEdge, pNewToOldMapping);
                        }

                    }).toList());
        case ReturnStatementEdge:
            if (!(pNewSuccessor instanceof FunctionExitNode)) {
                throw new IllegalArgumentException(
                        "The successor of a return statement edge must be a function exit node.");
            }
            CReturnStatementEdge returnStatementEdge = (CReturnStatementEdge) pEdge;
            Optional<CReturnStatement> cReturnStatement = returnStatementEdge.getRawAST();
            return new CReturnStatementEdge(rawStatement, cReturnStatement.orNull(), fileLocation, pNewPredecessor,
                    (FunctionExitNode) pNewSuccessor);
        case StatementEdge:
            CStatementEdge statementEdge = (CStatementEdge) pEdge;
            if (statementEdge instanceof CFunctionSummaryStatementEdge) {
                CFunctionSummaryStatementEdge functionStatementEdge = (CFunctionSummaryStatementEdge) pEdge;
                return new CFunctionSummaryStatementEdge(rawStatement, statementEdge.getStatement(), fileLocation,
                        pNewPredecessor, pNewSuccessor, functionStatementEdge.getFunctionCall(),
                        functionStatementEdge.getFunctionName());
            }
            return new CStatementEdge(rawStatement, statementEdge.getStatement(), fileLocation, pNewPredecessor,
                    pNewSuccessor);
        case CallToReturnEdge:
            CFunctionSummaryEdge cFunctionSummaryEdge = (CFunctionSummaryEdge) pEdge;
            return new CFunctionSummaryEdge(rawStatement, fileLocation, pNewPredecessor, pNewSuccessor,
                    cFunctionSummaryEdge.getExpression(),
                    (CFunctionEntryNode) getOrCreateNewFromOld(cFunctionSummaryEdge.getFunctionEntry(),
                            pNewToOldMapping));
        default:
            throw new IllegalArgumentException("Unsupported edge type: " + pEdge.getEdgeType());
        }
    }

    /**
     * Copies the given control flow edge predecessor, successor and any
     * additionally required nodes are taken from the given mapping by using the
     * corresponding node of the old edge as a key or, if no node is mapped to
     * this key, by copying the key and recording the result in the mapping.
     *
     * @param pEdge the edge to copy.
     * @param pNewToOldMapping a mapping of old nodes to new nodes.
     *
     * @return a new edge with the given predecessor and successor.
     */
    private CFAEdge copyCFAEdgeWithNewNodes(CFAEdge pEdge, final Map<CFANode, CFANode> pNewToOldMapping) {
        CFANode newPredecessor = getOrCreateNewFromOld(pEdge.getPredecessor(), pNewToOldMapping);
        CFANode newSuccessor = getOrCreateNewFromOld(pEdge.getSuccessor(), pNewToOldMapping);
        return copyCFAEdgeWithNewNodes(pEdge, newPredecessor, newSuccessor, pNewToOldMapping);
    }

    /**
     * Checks if the given edge is a dummy edge.
     *
     * @param pEdge the edge to check.
     * @return <code>true</code> if the edge is a dummy edge, <code>false</code> otherwise.
     */
    private static boolean isDummyEdge(CFAEdge pEdge) {
        return pEdge != null && pEdge.getEdgeType() == CFAEdgeType.BlankEdge
                && pEdge.getDescription().equals(DUMMY_EDGE);
    }

    /**
     * Gets the program counter value for the given target CFA node.
     *
     * @param pCFANode the target CFA node.
     *
     * @return the program counter value for the given target CFA node.
     */
    private static int getPCValueFor(CFANode pCFANode) {
        return pCFANode.getNodeNumber();
    }

    /**
     * Creates a new program counter value assignment edge between the given
     * predecessor and successor for the given program counter id expression and
     * the program counter value to be assigned.
     *
     * @param pPredecessor the predecessor of the new edge.
     * @param pSuccessor the successor of the new edge.
     * @param pPCIdExpression the program counter id expression to be used.
     * @param pPCValue the program counter value to be assigned.
     *
     * @return a new program counter value assignment edge.
     */
    private static ProgramCounterValueAssignmentEdge createProgramCounterAssignmentEdge(CFANode pPredecessor,
            CFANode pSuccessor, CIdExpression pPCIdExpression, int pPCValue) {
        return new CProgramCounterValueAssignmentEdge(pPredecessor, pSuccessor, pPCIdExpression, pPCValue);
    }

    /**
     * Creates a new program counter value assume edge between the given
     * predecessor and successor, using the given expression builder to build the
     * binary assumption expression of the equation between the given program
     * counter value and the given program counter value id expression.
     *
     * @param pExpressionBuilder the expression builder used to create the
     * assume expression.
     * @param pPredecessor the predecessor node of the edge.
     * @param pSuccessor the successor node of the edge.
     * @param pPCIdExpression the program counter id expression.
     * @param pPCValue the assumed program counter value.
     * @param pTruthAssumption if {@code true} the equation is assumed to be
     * true, if {@code false}, the equation is assumed to be false.
     */
    private static ProgramCounterValueAssumeEdge createProgramCounterAssumeEdge(
            CBinaryExpressionBuilder pExpressionBuilder, CFANode pPredecessor, CFANode pSuccessor,
            CIdExpression pPCIdExpression, int pPCValue, boolean pTruthAssumption) {
        return new CProgramCounterValueAssumeEdge(pExpressionBuilder, pPredecessor, pSuccessor, pPCIdExpression,
                pPCValue, pTruthAssumption);
    }

    /**
     * This enum contains different strategies
     * that decide how large the individual parts of the body of the new loop become.
     */
    private static enum SubGraphGrowthStrategy {

        /**
         * This growth strategy allows for infinite growth.
         */
        MULTIPLE_PATHS {

            @Override
            public boolean isFurtherGrowthDesired(AcyclicGraph pGraph) {
                return true;
            }

        },

        /**
         * This growth strategy allows for growth along an arbitrary single
         * finite path.
         */
        SINGLE_PATH {

            @Override
            public boolean isFurtherGrowthDesired(AcyclicGraph pGraph) {
                for (CFANode node : pGraph.getNodes()) {
                    if (node.getNumLeavingEdges() > 1) {
                        return false;
                    }
                }
                return true;
            }

        },

        /**
         * This growth strategy advises against any kind of growth.
         */
        SINGLE_EDGE {

            @Override
            public boolean isFurtherGrowthDesired(AcyclicGraph pGraph) {
                return false;
            }

        };

        /**
         * Decides whether or not further growth is desired for the given graph.
         *
         * @param pGraph the current graph.
         *
         * @return {@code true} if further growth of the graph is desired,
         * @{code false} otherwise.
         */
        abstract boolean isFurtherGrowthDesired(AcyclicGraph pGraph);
    }
}