Commit a09ff14e authored by Carlos Galindo's avatar Carlos Galindo
Browse files

Merge branch 'polymorphic-call-graph' into 'develop'

Polymorphic call graph

See merge request !51
parents 52f3a6cd 3c32b63e
Loading
Loading
Loading
Loading
Loading
+68 −17
Original line number Diff line number Diff line
@@ -2,10 +2,8 @@ package es.upv.mist.slicing.graphs;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.CallableDeclaration;
import com.github.javaparser.ast.body.ConstructorDeclaration;
import com.github.javaparser.ast.body.InitializerDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.expr.ObjectCreationExpr;
import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt;
@@ -15,16 +13,17 @@ import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclarati
import es.upv.mist.slicing.graphs.cfg.CFG;
import es.upv.mist.slicing.nodes.GraphNode;
import es.upv.mist.slicing.utils.ASTUtils;
import es.upv.mist.slicing.utils.NodeHashSet;
import es.upv.mist.slicing.utils.NodeNotFoundException;
import es.upv.mist.slicing.utils.Utils;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.DirectedPseudograph;
import org.jgrapht.nio.dot.DOTExporter;

import java.util.Deque;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * A directed graph which displays the available method declarations as nodes and their
@@ -41,23 +40,23 @@ import java.util.Objects;
public class CallGraph extends DirectedPseudograph<CallGraph.Vertex, CallGraph.Edge<?>> implements Buildable<NodeList<CompilationUnit>> {
    private final Map<CallableDeclaration<?>, CFG> cfgMap;
    private final Map<CallableDeclaration<?>, Vertex> vertexDeclarationMap = ASTUtils.newIdentityHashMap();
    private final ClassGraph classGraph;

    private boolean built = false;

    public CallGraph(Map<CallableDeclaration<?>, CFG> cfgMap) {
    public CallGraph(Map<CallableDeclaration<?>, CFG> cfgMap, ClassGraph classGraph) {
        super(null, null, false);
        this.cfgMap = cfgMap;
        this.classGraph = classGraph;
    }

    /** Resolve a call to its declaration, by using the call AST nodes stored on the edges. */
    public CallableDeclaration<?> getCallTarget(Resolvable<? extends ResolvedMethodLikeDeclaration> call) {
    /** Resolve a call to all its possible declarations, by using the call AST nodes stored on the edges. */
    public Stream<CallableDeclaration<?>> getCallTargets(Resolvable<? extends ResolvedMethodLikeDeclaration> call) {
        return edgeSet().stream()
                .filter(e -> e.getCall() == call)
                .map(this::getEdgeTarget)
                .map(Vertex::getDeclaration)
                .map(CallableDeclaration.class::cast)
                .findFirst()
                .orElse(null);
                .map(decl -> (CallableDeclaration<?>) decl);
    }

    @Override
@@ -105,8 +104,16 @@ public class CallGraph extends DirectedPseudograph<CallGraph.Vertex, CallGraph.E
    /** Find the calls to methods and constructors (edges) in the given list of compilation units. */
    protected void buildEdges(NodeList<CompilationUnit> arg) {
        arg.accept(new VoidVisitorAdapter<Void>() {
            private final Deque<ClassOrInterfaceDeclaration> classStack = new LinkedList<>();
            private final Deque<CallableDeclaration<?>> declStack = new LinkedList<>();

            @Override
            public void visit(ClassOrInterfaceDeclaration n, Void arg) {
                classStack.push(n);
                super.visit(n, arg);
                classStack.pop();
            }

            // ============ Method declarations ===========
            // There are some locations not considered, which may lead to an error in the stack.
            // 1. Method calls in non-static field initializations are assigned to all constructors of that class
@@ -129,24 +136,68 @@ public class CallGraph extends DirectedPseudograph<CallGraph.Vertex, CallGraph.E
            // =============== Method calls ===============
            @Override
            public void visit(MethodCallExpr n, Void arg) {
                n.resolve().toAst().ifPresent(decl -> addEdge(declStack.peek(), decl, n));
                n.resolve().toAst().ifPresent(decl -> createPolyEdges(decl, n));
                if (ASTUtils.shouldVisitArgumentsForMethodCalls(n))
                    super.visit(n, arg);
            }

            @Override
            public void visit(ObjectCreationExpr n, Void arg) {
                n.resolve().toAst().ifPresent(decl -> addEdge(declStack.peek(), decl, n));
                n.resolve().toAst().ifPresent(decl -> createNormalEdge(decl, n));
                if (ASTUtils.shouldVisitArgumentsForMethodCalls(n))
                    super.visit(n, arg);
            }

            @Override
            public void visit(ExplicitConstructorInvocationStmt n, Void arg) {
                n.resolve().toAst().ifPresent(decl -> addEdge(declStack.peek(), decl, n));
                n.resolve().toAst().ifPresent(decl -> createNormalEdge(decl, n));
                if (ASTUtils.shouldVisitArgumentsForMethodCalls(n))
                    super.visit(n, arg);
            }

            protected void createPolyEdges(MethodDeclaration decl, MethodCallExpr call) {
                // Static calls have no polymorphism, ignore
                if (decl.isStatic()) {
                    createNormalEdge(decl, call);
                    return;
                }
                Optional<Expression> scope = call.getScope();
                // Determine the type of the call's scope
                Set<ClassOrInterfaceDeclaration> dynamicTypes;
                if (scope.isEmpty()) {
                    // a) No scope: any class the method is in, or any outer class if the class is not static.
                    // Early exit: it is easier to find the methods that override the
                    // detected call than to account for all cases (implicit inner or outer class)
                    classGraph.overriddenSetOf(decl)
                            .forEach(methodDecl -> createNormalEdge(methodDecl, call));
                    return;
                } else if (scope.get().isThisExpr() && scope.get().asThisExpr().getTypeName().isEmpty()) {
                    // b) just 'this', the current class and any subclass
                    dynamicTypes = classGraph.subclassesOf(classStack.peek());
                } else if (scope.get().isThisExpr()) {
                    // c) 'ClassName.this', the given class and any subclass
                    dynamicTypes = classGraph.subclassesOf(scope.get().asThisExpr().resolve().asClass());
                } else {
                    // d) others: compute possible dynamic types of the expression (TODO)
                    dynamicTypes = classGraph.subclassesOf(scope.get().calculateResolvedType().asReferenceType());
                }
                // Locate the corresponding methods for each possible dynamic type, they must be available to all
                // To locate them, use the method signature and search for it in the class graph
                // Connect to each declaration
                AtomicInteger edgesCreated = new AtomicInteger();
                dynamicTypes.stream()
                        .map(t -> classGraph.findMethodByTypeAndSignature(t, decl.getSignature()))
                        .collect(Collectors.toCollection(NodeHashSet::new))
                        .forEach(methodDecl -> {
                            edgesCreated.getAndIncrement();
                            createNormalEdge(methodDecl, call);
                        });
                assert edgesCreated.get() > 0;
            }

            protected void createNormalEdge(CallableDeclaration<?> decl, Resolvable<? extends ResolvedMethodLikeDeclaration> call) {
                addEdge(declStack.peek(), decl, call);
            }
        }, null);
    }

+97 −6
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import com.github.javaparser.resolution.declarations.ResolvedClassDeclaration;
import com.github.javaparser.resolution.types.ResolvedReferenceType;
import es.upv.mist.slicing.arcs.Arc;
import es.upv.mist.slicing.utils.ASTUtils;
import es.upv.mist.slicing.utils.Utils;
@@ -30,16 +31,75 @@ public class ClassGraph extends DirectedPseudograph<ClassGraph.Vertex, ClassGrap

    /** Locates the vertex that represents a given class or interface declaration.
     *  The vertex must exist, or an exception will be thrown. */
    protected Vertex findVertex(ResolvedClassDeclaration declaration) {
        Optional<Vertex> vertex = vertexSet().stream()
                .filter(v -> v.declaration instanceof ClassOrInterfaceDeclaration)
    protected Vertex findClassVertex(ClassOrInterfaceDeclaration declaration) {
        return vertexSet().stream()
                .filter(v -> v.declaration.isClassOrInterfaceDeclaration())
                .filter(v -> ASTUtils.equalsWithRangeInCU(v.declaration, declaration))
                .findFirst().orElseThrow();
    }

    /** Locates the vertex that represents a given class or interface declaration.
     *  The vertex must exist, or an exception will be thrown. */
    protected Vertex findClassVertex(ResolvedClassDeclaration declaration) {
        return vertexSet().stream()
                .filter(v -> v.declaration.isClassOrInterfaceDeclaration())
                .filter(v -> v.declaration.asClassOrInterfaceDeclaration().resolve().asClass().equals(declaration))
                .findFirst();
        return vertex.orElseThrow();
                .findFirst().orElseThrow();
    }

    protected Vertex findClassVertex(ResolvedReferenceType type) {
        return vertexSet().stream()
                .filter(v -> v.declaration.isClassOrInterfaceDeclaration())
                .filter(v -> ASTUtils.resolvedTypeDeclarationToResolvedType(v.declaration.asClassOrInterfaceDeclaration().resolve()).equals(type))
                .findFirst().orElseThrow();
    }

    protected Vertex findMethodVertex(CallableDeclaration<?> declaration) {
        return vertexSet().stream()
                .filter(v -> v.declaration.isCallableDeclaration())
                .filter(v -> ASTUtils.equalsWithRangeInCU(v.declaration, declaration))
                .findFirst().orElseThrow();
    }

    public Set<MethodDeclaration> overriddenSetOf(MethodDeclaration method) {
        return subclassesStreamOf(classVertexOf(findMethodVertex(method)))
                .flatMap(vertex -> outgoingEdgesOf(vertex).stream()
                        .filter(ClassArc.Member.class::isInstance)
                        .map(ClassGraph.this::getEdgeTarget)
                        .filter(v -> v.declaration.isMethodDeclaration())
                        .filter(v -> v.declaration.asMethodDeclaration().getSignature().equals(method.getSignature()))
                        .map(v -> v.declaration.asMethodDeclaration()))
                .collect(Collectors.toSet());
    }

    protected Vertex classVertexOf(Vertex member) {
        assert member.declaration.isFieldDeclaration() ||
                member.declaration.isCallableDeclaration() ||
                member.declaration.isInitializerDeclaration();
        return incomingEdgesOf(member).stream()
                .filter(ClassArc.Member.class::isInstance)
                .map(this::getEdgeSource)
                .findFirst().orElseThrow();
    }

    /** Returns all child classes of the given class, including itself. */
    public Set<ClassOrInterfaceDeclaration> subclassesOf(ClassOrInterfaceDeclaration clazz) {
        return subclassesOf(findClassVertex(clazz));
    }

    /** Returns all child classes of the given class, including itself. */
    public Set<ClassOrInterfaceDeclaration> subclassesOf(ResolvedClassDeclaration clazz) {
        return subclassesStreamOf(findVertex(clazz))
        return subclassesOf(findClassVertex(clazz));
    }

    public Set<ClassOrInterfaceDeclaration> subclassesOf(ResolvedReferenceType type) {
        return subclassesOf(findClassVertex(type));
    }

    /** @see #subclassesOf(ClassOrInterfaceDeclaration) */
    protected Set<ClassOrInterfaceDeclaration> subclassesOf(Vertex v) {
        return subclassesStreamOf(v)
                .map(Vertex::getDeclaration)
                .map(ClassOrInterfaceDeclaration.class::cast)
                .collect(Collectors.toSet());
    }
@@ -51,6 +111,37 @@ public class ClassGraph extends DirectedPseudograph<ClassGraph.Vertex, ClassGrap
                .flatMap(this::subclassesStreamOf));
    }

    // TODO: this method ignores default method implementations in interfaces, as can be overridden.
    /** Looks up a method in the graph, going up the class inheritance tree to locate a
     *  matching method. If no match can be found, throws an {@link IllegalArgumentException}. */
    public MethodDeclaration findMethodByTypeAndSignature(ClassOrInterfaceDeclaration type, CallableDeclaration.Signature signature) {
        Optional<MethodDeclaration> result = outgoingEdgesOf(findClassVertex(type)).stream()
                .filter(ClassArc.Member.class::isInstance)
                .map(this::getEdgeTarget)
                .map(Vertex::getDeclaration)
                .filter(BodyDeclaration::isMethodDeclaration)
                .map(BodyDeclaration::asMethodDeclaration)
                .filter(decl -> signature.equals(decl.getSignature()))
                .findFirst();
        if (result.isPresent())
            return result.get();
        Optional<ClassOrInterfaceDeclaration> parentType = parentOf(type);
        if (parentType.isEmpty())
            throw new IllegalArgumentException("Cannot find the given signature: " + signature);
        return findMethodByTypeAndSignature(parentType.get(), signature);
    }

    /** Find the parent class or interface of a given class. */
    public Optional<ClassOrInterfaceDeclaration> parentOf(ClassOrInterfaceDeclaration declaration) {
        return incomingEdgesOf(findClassVertex(declaration)).stream()
                .filter(ClassArc.Extends.class::isInstance)
                .map(this::getEdgeSource)
                .map(Vertex::getDeclaration)
                .filter(BodyDeclaration::isClassOrInterfaceDeclaration)
                .map(BodyDeclaration::asClassOrInterfaceDeclaration)
                .findFirst();
    }

    @Override
    public void build(NodeList<CompilationUnit> arg) {
        if (isBuilt())
+6 −7
Original line number Diff line number Diff line
@@ -27,7 +27,8 @@ public class ExceptionSensitiveCallConnector extends CallConnector {
    @Override
    protected void connectCall(CallNode callNode, CallGraph callGraph) {
        var callExpr = callNode.getCallASTNode();
        if (callGraph.getCallTarget(callExpr).getThrownExceptions().size() > 0)
        // We can pick any call, because the signatures must match
        if (callGraph.getCallTargets(callExpr).findFirst().orElseThrow().getThrownExceptions().size() > 0)
            handleExceptionReturnArcs(callExpr, callGraph);
        super.connectCall(callNode, callGraph);
    }
@@ -44,12 +45,10 @@ public class ExceptionSensitiveCallConnector extends CallConnector {
                .filter(SyntheticNode.class::isInstance)
                .map(n -> (SyntheticNode<?>) n)
                .collect(Collectors.toSet());
        CallableDeclaration<?> decl = callGraph.getCallTarget(call);
        if (decl == null)
            throw new IllegalArgumentException("Unknown call!");

        callGraph.getCallTargets(call).forEach(decl -> {
            connectNormalNodes(synthNodes, call, decl);
            connectExceptionNodes(synthNodes, call, decl);
        });
    }

    /** Connects normal exit nodes to their corresponding return node. */
+14 −13
Original line number Diff line number Diff line
@@ -14,8 +14,6 @@ import es.upv.mist.slicing.nodes.io.FormalIONode;
import es.upv.mist.slicing.nodes.io.OutputNode;
import es.upv.mist.slicing.utils.Logger;

import java.util.Optional;

/** Adds interprocedural arcs between the 'PDG components' of an SDG.
 * Arcs generated include {@link ParameterInOutArc parameter input/output} and
 * {@link CallArc call} arcs. */
@@ -34,20 +32,23 @@ public class CallConnector {
                .forEach(node -> connectCall(node, callGraph));
    }

    /** Connects a given call to its declaration, via call and in/out arcs. */
    protected void connectCall(CallNode callNode, CallGraph callGraph) {
    /** Connects a given call to all possible matching declarations. */
    @SuppressWarnings("unchecked")
    protected void connectCall(CallNode callNode, CallGraph callGraph) {
        var callExpr = (Resolvable<? extends ResolvedMethodLikeDeclaration>) callNode.getAstNode();
        GraphNode<? extends CallableDeclaration<?>> declarationNode;
        try {
            declarationNode = Optional.ofNullable(callGraph.getCallTarget(callExpr))
                    .flatMap(sdg::findNodeByASTNode)
                    .orElseThrow(IllegalArgumentException::new);
        } catch (IllegalArgumentException e) {
        callGraph.getCallTargets(callExpr)
                .map(sdg::findNodeByASTNode)
                .filter(opt -> {
                    if (opt.isEmpty())
                        Logger.format("Method declaration not found: '%s'. Discarding", callExpr);
            return;
                    return opt.isPresent();
                })
                .map(opt -> opt.orElseThrow(IllegalArgumentException::new))
                .forEach(node -> connectCall(callNode, node));
    }

    /** Connects a given call to its declaration, via call and in/out arcs. */
    protected void connectCall(CallNode callNode, GraphNode<? extends CallableDeclaration<?>> declarationNode) {
        // Connect the call and declaration nodes
        sdg.addCallArc(callNode, declarationNode);

+3 −3
Original line number Diff line number Diff line
@@ -110,8 +110,8 @@ public class SDG extends Graph implements Sliceable, Buildable<NodeList<Compilat
        public void build(NodeList<CompilationUnit> nodeList) {
            // See creation strategy at http://kaz2.dsic.upv.es:3000/Fzg46cQvT1GzHQG9hFnP1g#Using-data-flow-in-the-SDG
            buildCFGs(nodeList);                             // 1
            CallGraph callGraph = createCallGraph(nodeList); // 2
            ClassGraph classGraph = createClassGraph(nodeList); // TODO: Update order and creation strategy
            CallGraph callGraph = createCallGraph(nodeList, classGraph); // 2
            dataFlowAnalysis(callGraph);                     // 3
            buildAndCopyPDGs();                              // 4
            connectCalls(callGraph);                         // 5
@@ -138,8 +138,8 @@ public class SDG extends Graph implements Sliceable, Buildable<NodeList<Compilat
        }

        /** Create call graph from the list of compilation units. */
        protected CallGraph createCallGraph(NodeList<CompilationUnit> nodeList) {
            CallGraph callGraph = new CallGraph(cfgMap);
        protected CallGraph createCallGraph(NodeList<CompilationUnit> nodeList, ClassGraph classGraph) {
            CallGraph callGraph = new CallGraph(cfgMap, classGraph);
            callGraph.build(nodeList);
            return callGraph;
        }
Loading