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

Merge branch 'dynamic-type-search' into 'develop'

Interprocedural DynamicTypeResolver

Closes #45

See merge request !53
parents a09ff14e de1aa0b6
Loading
Loading
Loading
Loading
Loading
+38 −0
Original line number Diff line number Diff line
package es.upv.mist.slicing.graphs;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.expr.Expression;
@@ -59,6 +60,43 @@ public class CallGraph extends DirectedPseudograph<CallGraph.Vertex, CallGraph.E
                .map(decl -> (CallableDeclaration<?>) decl);
    }

    /** Locates the calls to a given declaration. The result is any node that represents a call. */
    public Stream<Node> callsTo(CallableDeclaration<?> callee) {
        return incomingEdgesOf(findVertexByDeclaration(callee)).stream()
                .map(Edge::getCall)
                .map(Node.class::cast);
    }

    /** Locates the calls contained in a given method or constructor.
     *  See {@link #callsTo(CallableDeclaration)} for the return value. */
    public Stream<Node> callsFrom(CallableDeclaration<?> caller) {
        return outgoingEdgesOf(findVertexByDeclaration(caller)).stream()
                .map(Edge::getCall)
                .map(Node.class::cast);
    }

    /** Locates the methods that may call the given declaration. */
    public Stream<CallableDeclaration<?>> callersOf(CallableDeclaration<?> callee) {
        return incomingEdgesOf(findVertexByDeclaration(callee)).stream()
                .map(this::getEdgeSource)
                .map(Vertex::getDeclaration);
    }

    /** Locates the methods that may be called when executing the given declaration. */
    public Stream<CallableDeclaration<?>> calleesOf(CallableDeclaration<?> caller) {
        return outgoingEdgesOf(findVertexByDeclaration(caller)).stream()
                .map(this::getEdgeTarget)
                .map(Vertex::getDeclaration);
    }

    /** Locate the vertex that represents in this graph the given declaration. */
    protected Vertex findVertexByDeclaration(CallableDeclaration<?> declaration) {
        return vertexSet().stream()
                .filter(v -> v.declaration == declaration ||
                        ASTUtils.equalsWithRange(v.declaration, declaration))
                .findFirst().orElseThrow();
    }

    @Override
    public void build(NodeList<CompilationUnit> arg) {
        if (isBuilt())
+133 −41
Original line number Diff line number Diff line
package es.upv.mist.slicing.graphs.oo;

import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.CallableDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.expr.CastExpr;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.stmt.ReturnStmt;
import com.github.javaparser.resolution.declarations.ResolvedClassDeclaration;
import com.github.javaparser.resolution.types.ResolvedType;
import es.upv.mist.slicing.graphs.CallGraph;
import es.upv.mist.slicing.graphs.ClassGraph;
import es.upv.mist.slicing.graphs.cfg.CFG;
import es.upv.mist.slicing.nodes.GraphNode;
import es.upv.mist.slicing.nodes.VariableAction;
import es.upv.mist.slicing.nodes.io.ActualIONode;
import es.upv.mist.slicing.nodes.io.FormalIONode;
import es.upv.mist.slicing.utils.ASTUtils;

import java.util.HashSet;
import java.util.Objects;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/** A dynamic type solver that complements Javaparser's {@code resolve()} method. */
/** A dynamic type solver that complements JavaParser's {@code resolve()} method. */
public class DynamicTypeResolver {
    protected final CFG cfg;
    protected final Map<CallableDeclaration<?>, CFG> cfgMap;
    protected final ClassGraph classGraph;
    protected final CallGraph callGraph;

    public DynamicTypeResolver(CFG cfg, ClassGraph classGraph) {
        this.cfg = cfg;
    public DynamicTypeResolver(Map<CallableDeclaration<?>, CFG> cfgMap, ClassGraph classGraph, CallGraph callGraph) {
        this.cfgMap = cfgMap;
        this.classGraph = classGraph;
        this.callGraph = callGraph;
    }

    /** Obtains the set of dynamic types that the variable passed as argument
     *  may take. <strong>The current implementation is intra-procedural!</strong> */
    public Set<ResolvedType> resolve(VariableAction action) {
        return resolveIntraProcedural(action);
    /** Obtains the possible dynamic types of the given expression, which is contained within a GraphNode.
     *  Only expressions of a reference type are allowed (e.g. objects, arrays, but not primitives). */
    public Set<ResolvedType> resolve(Expression expression, GraphNode<?> container) {
        assert expression.calculateResolvedType().isReference(): "The expression must be of reference type (no primitives).";
        return resolveStreamed(expression, container).collect(Collectors.toSet());
    }

    /** Obtains a list of possible dynamic types for the given action, by
     *  searching for definitions intraprocedurally. */
    public Set<ResolvedType> resolveIntraProcedural(VariableAction action) {
        return cfg.findLastDefinitionsFrom(action).stream()
                .map(VariableAction::asDefinition)
                .filter(Objects::nonNull)
                .map(def -> {
                    Expression expr = unwrapExpr(def.getExpression());
                    if (expr.isObjectCreationExpr() || expr.isArrayCreationExpr()
                            || expr.isArrayInitializerExpr())
                        return Set.of(expr.calculateResolvedType());
                    else
                        return findAllSubtypes(def.getExpression().calculateResolvedType());
                })
                .reduce(new HashSet<>(), (a, b) -> { a.addAll(b); return a; });
    /** Directs each kind of expression to the appropriate resolve method. */
    protected Stream<ResolvedType> resolveStreamed(Expression expression, GraphNode<?> container) {
        if (expression.isMethodCallExpr())
            return resolveMethodCallExpr(expression.asMethodCallExpr());
        if (expression.isNameExpr() || expression.isFieldAccessExpr()) // May be field, local variable or parameter
            return resolveVariable(expression, container);
        if (expression.isArrayAccessExpr() ||
                expression.isThisExpr())
            return anyTypeOf(expression);
        if (expression.isCastExpr())
            return resolveCast(expression.asCastExpr(), container);
        if (expression.isEnclosedExpr())
            return resolveStreamed(expression.asEnclosedExpr().getInner(), container);
        if (expression.isObjectCreationExpr() ||
                expression.isArrayCreationExpr())
            return Stream.of(expression.calculateResolvedType());
        throw new IllegalArgumentException("The given expression is not an object-compatible one.");
    }

    /** Unwraps an enclosed expression, and other constructs that are type-transparent. */
    protected Expression unwrapExpr(Expression expr) {
        if (expr.isCastExpr())
            if (ASTUtils.isDownCast(expr.asCastExpr()))
                return expr;
            else
                return unwrapExpr(expr.asCastExpr().getExpression());
        if (expr.isEnclosedExpr())
            return unwrapExpr(expr.asEnclosedExpr().getInner());
        return expr;
    /** Checks the possible values of all ReturnStmt of this call's target methods. */
    protected Stream<ResolvedType> resolveMethodCallExpr(MethodCallExpr methodCallExpr) {
        assert !methodCallExpr.calculateResolvedType().isVoid();
        return callGraph.getCallTargets(methodCallExpr)
                .map(cfgMap::get)
                .flatMap(cfg -> cfg.vertexSet().stream())
                .filter(node -> node.getAstNode() instanceof ReturnStmt)
                .flatMap(node -> resolveStreamed(((ReturnStmt) node.getAstNode()).getExpression().orElseThrow(), node));
    }

    /** Extracts from the call graph all the subtypes of the given argument.
     *  The input is included as part of the output. */
    protected Set<ResolvedType> findAllSubtypes(ResolvedType t) {
        return classGraph.subclassesOf(t.asReferenceType().getTypeDeclaration().orElseThrow().asClass()).stream()
    /** Searches for the corresponding VariableAction object, then calls {@link #resolveVariableAction(VariableAction)}. */
    protected Stream<ResolvedType> resolveVariable(Expression expression, GraphNode<?> graphNode) {
        Optional<VariableAction> va = graphNode.getVariableActions().stream()
                .filter(action -> ASTUtils.equalsWithRange(action.getVariableExpression(), expression))
                .findFirst();
        if (va.isEmpty())
            return anyTypeOf(expression);
        return resolveVariableAction(va.get());
    }

    /**
     * Looks up the last definitions according to the CFG, then resolves the last assignment to it.
     * If the last definition was not in this method (in the case of parameters or fields), it
     * uses auxiliary methods to locate the last interprocedural definition.
     * Otherwise, the last expression(s) assigned to it is found and recursively resolved.
     */
    protected Stream<ResolvedType> resolveVariableAction(VariableAction va) {
        CFG cfg = cfgMap.get(findCallableDeclarationFromGraphNode(va.getGraphNode()));
        return cfg.findLastDefinitionsFrom(va).stream()
                .flatMap(def -> {
                    if (def.asDefinition().getExpression() == null) {
                        if (def instanceof VariableAction.Movable && ((VariableAction.Movable) def).getRealNode() instanceof FormalIONode)
                            return resolveFormalIn((FormalIONode) ((VariableAction.Movable) def).getRealNode());
                        throw new IllegalArgumentException("Definition was not movable and hadn't an expression.");
                    }
                    return resolveStreamed(def.asDefinition().getExpression(), def.getGraphNode());
                });
    }

    /** Locate the declaration (method or constructor) where the given node is located. */
    protected CallableDeclaration<?> findCallableDeclarationFromGraphNode(GraphNode<?> node) {
        return cfgMap.values().stream()
                .filter(cfg -> cfg.containsVertex(node))
                .map(CFG::getDeclaration)
                .findFirst().orElseThrow();
    }

    /** Looks up the expression assigned to all corresponding actual-in nodes and resolves it. */
    protected Stream<ResolvedType> resolveFormalIn(FormalIONode formalIn) {
        assert formalIn.isInput();
        return callGraph.callsTo(findCallableDeclarationFromGraphNode(formalIn))
                .map(this::findNodeInMapByAST)
                .map(GraphNode::getVariableActions)
                .flatMap(List::stream)
                .filter(VariableAction.Movable.class::isInstance)
                .map(VariableAction.Movable.class::cast)
                .flatMap(movable -> {
                    GraphNode<?> realNode = movable.getRealNode();
                    if (!(realNode instanceof ActualIONode))
                        return Stream.empty();
                    ActualIONode actualIn = (ActualIONode) realNode;
                    if (!actualIn.matchesFormalIO(formalIn))
                        return Stream.empty();
                    return resolveStreamed(actualIn.getArgument(), movable.getGraphNode());
                });
    }

    /** Locate the CFG graph node in which the argument is contained.
     *  Its time requirement is linear with the number of total nodes in */
    protected GraphNode<?> findNodeInMapByAST(Node astNode) {
        return cfgMap.values().stream()
                .map(cfg -> cfg.findNodeByASTNode(astNode))
                .filter(Optional::isPresent)
                .map(Optional::get)
                .findFirst().orElseThrow();
    }

    /** Checks if the cast marks a more specific type and returns that one.
     *  Otherwise, it unwraps the cast expression and recursively resolves the type
     *  of the inner expression. */
    protected Stream<ResolvedType> resolveCast(CastExpr cast, GraphNode<?> container) {
        if (ASTUtils.isDownCast(cast))
            return Stream.of(cast.getType().resolve());
        return resolveStreamed(cast.getExpression(), container);
    }

    /** Returns all possible types that the given expression can be, by obtaining its static type
     *  and locating all subtypes in the class graph. */
    protected Stream<ResolvedType> anyTypeOf(Expression expression) {
        ResolvedClassDeclaration type = expression.calculateResolvedType().asReferenceType()
                .getTypeDeclaration().orElseThrow().asClass();
        return classGraph.subclassesOf(type).stream()
                .map(ClassOrInterfaceDeclaration::resolve)
                .map(ASTUtils::resolvedTypeDeclarationToResolvedType)
                .collect(Collectors.toSet());
                .map(ASTUtils::resolvedTypeDeclarationToResolvedType);
    }
}
+4 −4
Original line number Diff line number Diff line
@@ -74,7 +74,7 @@ public abstract class InterproceduralActionFinder<A extends VariableAction> exte
    /** Obtains the expression passed as argument for the given action at the given call. If {@code input}
     * is false, primitive parameters will be skipped, as their value cannot be redefined.*/
    protected Expression extractArgument(VariableAction action, CallGraph.Edge<?> edge, boolean input) {
        ResolvedValueDeclaration resolved = action.getNameExpr().resolve();
        ResolvedValueDeclaration resolved = action.getResolvedValueDeclaration();
        CallableDeclaration<?> callTarget = graph.getEdgeTarget(edge).getDeclaration();
        if (resolved.isParameter()) {
            ResolvedParameterDeclaration p = resolved.asParameter();
@@ -83,7 +83,7 @@ public abstract class InterproceduralActionFinder<A extends VariableAction> exte
            int paramIndex = ASTUtils.getMatchingParameterIndex(callTarget, p);
            return ASTUtils.getResolvableArgs(edge.getCall()).get(paramIndex);
        } else if (resolved.isField()) {
            return action.getNameExpr();
            return action.getVariableExpression();
        } else {
            throw new IllegalArgumentException("Variable should be either param or field!");
        }
@@ -116,8 +116,8 @@ public abstract class InterproceduralActionFinder<A extends VariableAction> exte
            ResolvedValueDeclaration r1 = null;
            ResolvedValueDeclaration r2 = null;
            try {
                r1 = o1.getAction().getNameExpr().resolve();
                r2 = o2.getAction().getNameExpr().resolve();
                r1 = o1.getAction().getResolvedValueDeclaration();
                r2 = o2.getAction().getResolvedValueDeclaration();
                if (r1.isParameter() && r2.isParameter())
                    return -Integer.compare(ASTUtils.getMatchingParameterIndex(graph.getEdgeTarget(edge).getDeclaration(), r1.asParameter()),
                            ASTUtils.getMatchingParameterIndex(graph.getEdgeTarget(edge).getDeclaration(), r2.asParameter()));
+2 −2
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@ public class InterproceduralDefinitionFinder extends InterproceduralActionFinder
    @Override
    protected void handleFormalAction(CallableDeclaration<?> declaration, VariableAction.Definition def) {
        CFG cfg = cfgMap.get(declaration);
        ResolvedValueDeclaration resolved = def.getNameExpr().resolve();
        ResolvedValueDeclaration resolved = def.getResolvedValueDeclaration();
        if (!resolved.isParameter() || !resolved.getType().isPrimitive()) {
            FormalIONode formalOut = FormalIONode.createFormalOut(declaration, resolved);
            cfg.getExitNode().addMovableVariable(new VariableAction.Movable(def.toUsage(cfg.getExitNode()), formalOut));
@@ -40,7 +40,7 @@ public class InterproceduralDefinitionFinder extends InterproceduralActionFinder
    protected void handleActualAction(CallGraph.Edge<?> edge, VariableAction.Definition def) {
        Set<VariableAction.Movable> movables = new HashSet<>();
        GraphNode<?> graphNode = edge.getGraphNode();
        ResolvedValueDeclaration resolved = def.getNameExpr().resolve();
        ResolvedValueDeclaration resolved = def.getResolvedValueDeclaration();
        Expression arg = extractArgument(def, edge, false);
        if (arg == null)
            return;
+2 −2
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@ public class InterproceduralUsageFinder extends InterproceduralActionFinder<Vari
    @Override
    protected void handleFormalAction(CallableDeclaration<?> declaration, VariableAction.Usage use) {
        CFG cfg = cfgMap.get(declaration);
        ResolvedValueDeclaration resolved = use.getNameExpr().resolve();
        ResolvedValueDeclaration resolved = use.getResolvedValueDeclaration();
        FormalIONode formalIn = FormalIONode.createFormalIn(declaration, resolved);
        cfg.getRootNode().addMovableVariable(new VariableAction.Movable(use.toDefinition(cfg.getRootNode()), formalIn));
    }
@@ -36,7 +36,7 @@ public class InterproceduralUsageFinder extends InterproceduralActionFinder<Vari
    protected void handleActualAction(CallGraph.Edge<?> edge, VariableAction.Usage use) {
        Set<VariableAction.Movable> movables = new HashSet<>();
        GraphNode<?> graphNode = edge.getGraphNode();
        ResolvedValueDeclaration resolved = use.getNameExpr().resolve();
        ResolvedValueDeclaration resolved = use.getResolvedValueDeclaration();
        Expression argument = extractArgument(use, edge, true);
        ActualIONode actualIn = ActualIONode.createActualIn(edge.getCall(), resolved, argument);
        argument.accept(new VariableVisitor(
Loading