org.sosy_lab.cpachecker.cfa.postprocessing.global.FunctionCallUnwinder.java Source code

Java tutorial

Introduction

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

import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.sosy_lab.common.Pair;
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.CFACreationUtils;
import org.sosy_lab.cpachecker.cfa.FunctionCallCollector;
import org.sosy_lab.cpachecker.cfa.Language;
import org.sosy_lab.cpachecker.cfa.MutableCFA;
import org.sosy_lab.cpachecker.cfa.ast.AFunctionCall;
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.CStatement;
import org.sosy_lab.cpachecker.cfa.model.AStatementEdge;
import org.sosy_lab.cpachecker.cfa.model.CFANode;
import org.sosy_lab.cpachecker.cfa.model.FunctionEntryNode;
import org.sosy_lab.cpachecker.cfa.model.c.CStatementEdge;
import org.sosy_lab.cpachecker.util.CFATraversal;

import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.collect.TreeMultimap;

@Options(prefix = "cfa.functionCalls")
public class FunctionCallUnwinder {

    @Option(secure = true, description = "how often can a function appear in the callstack as a clone of the original function?")
    private int recursionDepth = 5;

    // TODO find better name, it is not always recursive
    private final static String RECURSION_SEPARATOR = "__recursive_call__";

    private final MutableCFA cfa;

    public FunctionCallUnwinder(final MutableCFA pCfa, final Configuration config, final LogManager pLogger)
            throws InvalidConfigurationException {
        config.inject(this);
        this.cfa = pCfa;

        if (cfa.getLanguage() != Language.C) {
            throw new InvalidConfigurationException("Function-call unwinding is only supported for C code.");
        }
    }

    public MutableCFA unwindRecursion() {
        assert cfa.getLanguage() == Language.C;

        // copy content of old CFAs
        final SortedMap<String, FunctionEntryNode> functions = new TreeMap<>(cfa.getAllFunctions());
        final SortedSetMultimap<String, CFANode> nodes = TreeMultimap.create();
        for (final String function : cfa.getAllFunctionNames()) {
            nodes.putAll(function, cfa.getFunctionNodes(function));
        }

        final Multimap<String, String> reverseCallGraph = HashMultimap.create();
        final Set<String> finished = new HashSet<>();
        final Deque<String> waitlist = new ArrayDeque<>();
        waitlist.add(cfa.getMainFunction().getFunctionName());

        while (!waitlist.isEmpty()) {
            final String functionname = waitlist.pop();

            if (!finished.add(functionname)) {
                continue;
            }

            // get CFA for functionname
            Preconditions.checkArgument(functions.containsKey(functionname), "function %s not available",
                    functionname);
            FunctionEntryNode entryNode = functions.get(functionname);

            // get functioncalls from the CFA
            final FunctionCallCollector visitor = new FunctionCallCollector();
            CFATraversal.dfs().traverseOnce(entryNode, visitor);
            final Collection<AStatementEdge> functionCalls = visitor.getFunctionCalls();

            // unwind recursion
            for (AStatementEdge statementEdge : functionCalls) {

                if (!isFunctionCall(statementEdge, functions.keySet())) {
                    continue;
                }

                final String calledFunction = getNameOfFunction(statementEdge);
                String newFunctionname = calledFunction;
                if (isCallStackSizeReached(calledFunction, reverseCallGraph)) {
                    // ignore, we have "bounded" recursion unwinding

                } else { // further unwinding allowed
                    while (isFatherOf(functionname, newFunctionname, reverseCallGraph)) {
                        newFunctionname = incrementFunctionname(newFunctionname);
                    }

                    if (!calledFunction.equals(newFunctionname)) {
                        // if we have found recursion and need a functioncall-replacement
                        if (!functions.containsKey(newFunctionname)) {
                            cloneFunction(calledFunction, newFunctionname, functions, nodes);
                        }

                        // redirect from caller to new (cloned) called function
                        replaceFunctionCall(statementEdge, newFunctionname);
                    }
                }

                waitlist.add(newFunctionname);
                reverseCallGraph.put(newFunctionname, functionname);
            }
        }

        return new MutableCFA(cfa.getMachineModel(), functions, nodes, cfa.getMainFunction(), cfa.getLanguage());
    }

    private void replaceFunctionCall(final AStatementEdge functionCallEdge, final String newFunctionName) {
        final CFANode pred = functionCallEdge.getPredecessor();
        final CFANode succ = functionCallEdge.getSuccessor();
        final AFunctionCall call = (AFunctionCall) functionCallEdge.getStatement();

        final AStatementEdge newEdge;
        if (call instanceof CFunctionCall) {

            // get old values
            final CDeclaration declaration = ((CFunctionCall) call).getFunctionCallExpression().getDeclaration();
            Preconditions.checkNotNull(declaration);
            final String oldFunctionName = declaration.getQualifiedName();

            // build new edge
            final FunctionCloner fc = new FunctionCloner(oldFunctionName, newFunctionName, true);
            newEdge = fc.cloneEdge(functionCallEdge, pred, succ);
        } else {
            // TODO support JAVA
            throw new AssertionError("unsupported edge: " + functionCallEdge);
        }

        // replace edge
        CFACreationUtils.removeEdgeFromNodes(functionCallEdge);
        CFACreationUtils.addEdgeUnconditionallyToCFA(newEdge);
    }

    /** clones a function and adds it to the maps. */
    private void cloneFunction(final String oldFunctionname, final String newFunctionname,
            final Map<String, FunctionEntryNode> functions, final SortedSetMultimap<String, CFANode> nodes) {
        Preconditions.checkArgument(!functions.containsKey(newFunctionname),
                "function exists, cloning is not allowed.");

        // clone
        final FunctionEntryNode entryNode = functions.get(oldFunctionname);
        final Pair<FunctionEntryNode, Collection<CFANode>> newFunction = FunctionCloner.cloneCFA(entryNode,
                newFunctionname);

        // add new function to CFA
        functions.put(newFunctionname, newFunction.getFirst());
        nodes.putAll(newFunctionname, newFunction.getSecond());
    }

    private static String getNameOfFunction(final AStatementEdge edge) {
        if (!(edge instanceof CStatementEdge)) {
            return null;
        }
        final CStatement statement = ((CStatementEdge) edge).getStatement();
        if (!(statement instanceof CFunctionCall)) {
            return null;
        }
        final CDeclaration declaration = ((CFunctionCall) statement).getFunctionCallExpression().getDeclaration();
        if (declaration == null) {
            return null;
        }
        return declaration.getQualifiedName();
    }

    /** returns, iff the edge contains a functioncall to another CFA. */
    private static boolean isFunctionCall(final AStatementEdge edge, final Collection<String> cfaFunctions) {
        // declaration == null -> functionPointer
        // functionName exists in CFA -> functioncall with CFA for called function
        //          -> we assume an original CFA, not a clone
        // otherwise: call of non-existent function, example: nondet_int()
        final String functionname = getNameOfFunction(edge);
        return functionname != null && (cfaFunctions.contains(functionname));
    }

    /** checks, iff there is an call-stack from father to child.
     * In the graph this would be a way from father to child.
     * It should be more efficient to search backwards,
     * because children have only one father in most cases. */
    private static boolean isFatherOf(final String child, final String possibleFather,
            final Multimap<String, String> reverseGraph) {
        final Set<String> finished = new HashSet<>();
        final Deque<String> waitlist = new ArrayDeque<>();
        waitlist.add(child);
        while (!waitlist.isEmpty()) {
            String current = waitlist.pop();
            if (!finished.add(current)) {
                continue;
            }
            if (current.equals(possibleFather)) {
                return true;
            }
            waitlist.addAll(reverseGraph.get(current));
        }
        return false;
    }

    /** checks maximum size of callstack.
     * TODO implement better user-defined limits */
    private boolean isCallStackSizeReached(final String calledFunction,
            final Multimap<String, String> reverseGraph) {
        final Collection<String> functions = reverseGraph.keySet();
        int maxDepth = 0;
        for (String function : functions) {
            if (function.startsWith(calledFunction)) {
                int index = function.indexOf(RECURSION_SEPARATOR);
                if (index != -1) {
                    int depth = Integer.parseInt(function.substring(index + RECURSION_SEPARATOR.length()));
                    maxDepth = Math.max(maxDepth, depth);
                }
            }
        }
        return maxDepth >= recursionDepth;
    }

    private String incrementFunctionname(String function) {
        int i = function.indexOf(RECURSION_SEPARATOR);
        int index = 1;
        if (i != -1) {
            index = Integer.parseInt(function.substring(i + RECURSION_SEPARATOR.length(), function.length())) + 1;
            function = function.substring(0, i);
        }
        return function + RECURSION_SEPARATOR + index;
    }
}