Commit 51fc74bb authored by jacosro's avatar jacosro
Browse files

WIP method call nodes

parent b70f8c10
Loading
Loading
Loading
Loading
+4 −14
Original line number Diff line number Diff line
@@ -16,19 +16,9 @@ class MethodCallReplacer {
        this.sdg = sdg;
    }

    public void replace() {
        this.sdg.getContexts().stream()
            .filter(context -> context.getCurrentMethod().isPresent())
            .forEach(context -> {
                Logger.log("MethodCallReplacer", context);

                Optional<GraphNode<MethodDeclaration>> optionalRootNode = this.sdg.getRootNode(context);

                if (!optionalRootNode.isPresent()) {
                    return; // We don't have visited the code (e.g. the MethodDeclaration for a method call)
    public void replace(Context context) {
        for (MethodDeclaration methodDeclaration : this.sdg.getMethodDeclarations()) {
            methodDeclaration.accept(new MethodCallReplacerVisitor(sdg), context);
        }

                optionalRootNode.get().getAstNode().accept(new MethodCallReplacerVisitor(sdg), context);
            });
    }
}
+2 −190
Original line number Diff line number Diff line
@@ -145,6 +145,8 @@ class MethodCallReplacerVisitor extends VoidVisitorAdapter<Context> {

            GraphNode<ExpressionStmt> argumentInNode = sdg.addNode(inExprStmt.toString(), inExprStmt, TypeNodeFactory.fromType(NodeType.VARIABLE_IN));

            sdg.addControlDependencyArc(methodCallNode, argumentInNode);

            // Out expression
            VariableDeclarationExpr outVariableDeclarationExpr = new VariableDeclarationExpr(
                    new VariableDeclarator(
@@ -179,194 +181,4 @@ class MethodCallReplacerVisitor extends VoidVisitorAdapter<Context> {
            return Optional.empty();
        }
    }

    /**
     * Handles method calls with scope. Examples:
     *  - System.out.println() -> println() is a method call with scope System.out
     *  - new A().getB() -> getB() is a method call with scope new A()
     */
    private Optional<MethodDeclaration> shouldMakeCallWithScope(MethodCallExpr methodCallExpr, Context context) {
        assert methodCallExpr.getScope().isPresent();

        String scopeName = methodCallExpr.getScope().get().toString();

        if (!context.getCurrentClass().isPresent()) {
            return Optional.empty();
        }

        ClassOrInterfaceDeclaration currentClass = context.getCurrentClass().get();

        // Check if it's a static method call of current class
        if (!Objects.equals(scopeName, currentClass.getNameAsString())) {

            // Check if 'scopeName' is a variable
            List<GraphNode<?>> declarations = sdg.findDeclarationsOfVariable(scopeName, methodCallNode);

            if (declarations.isEmpty()) {
                // It is a static method call of another class. We do nothing
                return Optional.empty();
            }

            /*
                It's a variable since it has declarations. We now have to check if the class name
                is the same as the current class (the object is an instance of our class)
            */
            GraphNode<?> declarationNode = declarations.get(declarations.size() - 1);

            ExpressionStmt declarationExpr = (ExpressionStmt) declarationNode.getAstNode();
            VariableDeclarationExpr variableDeclarationExpr = declarationExpr.getExpression().asVariableDeclarationExpr();

            Optional<VariableDeclarator> optionalVariableDeclarator = variableDeclarationExpr.getVariables().stream()
                    .filter(variableDeclarator -> Objects.equals(variableDeclarator.getNameAsString(), scopeName))
                    .findFirst();

            if (!optionalVariableDeclarator.isPresent()) {
                // should not happen
                return Optional.empty();
            }

            Type variableType = optionalVariableDeclarator.get().getType();

            if (!variableType.isClassOrInterfaceType()) {
                // Not class type
                return Optional.empty();
            }

            if (!Objects.equals(variableType.asClassOrInterfaceType().getNameAsString(), currentClass.getNameAsString())) {
                // object is not instance of our class
                return Optional.empty();
            }

            // if we got here, the object is instance of our class
        }

        // It's a static method call to a method of the current class

        return findMethodInClass(methodCallExpr, currentClass);
    }

    private Optional<MethodDeclaration> shouldMakeCallWithNoScope(MethodCallExpr methodCallExpr, Context context) {
        assert !methodCallExpr.getScope().isPresent();

        /*
            May be a call to a method of the current class or a call to an imported static method.
            In the first case, we make the call. Otherwise, not.
        */

        if (!context.getCurrentClass().isPresent()) {
            return Optional.empty();
        }

        // We get the current class and search along their methods to find the one we're looking for...

        ClassOrInterfaceDeclaration currentClass = context.getCurrentClass().get();

        return findMethodInClass(methodCallExpr, currentClass);
    }

    private Optional<MethodDeclaration> findMethodInClass(MethodCallExpr methodCallExpr, ClassOrInterfaceDeclaration klass) {
        int argumentsCount = methodCallExpr.getArguments().size();

        // Get methods with equal name and parameter count
        List<MethodDeclaration> classMethods = klass.getMethodsByName(methodCallExpr.getNameAsString()).stream()
                // Filter methods with equal or less (varargs) number of parameters
                .filter(methodDeclaration -> {
                    NodeList<Parameter> parameters = methodDeclaration.getParameters();

                    if (parameters.size() == argumentsCount) {
                        return true;
                    }

                    if (parameters.isEmpty()) {
                        return false;
                    }

                    // There are more arguments than parameters. May be OK if last parameter is varargs
                    if (parameters.size() < argumentsCount) {
                        return parameters.get(parameters.size() - 1).isVarArgs();
                    }

                    // There are less arguments than parameters. May be OK if the last parameter is varargs
                    // and it's omited
                    return parameters.size() - 1 == argumentsCount
                            && parameters.get(parameters.size() - 1).isVarArgs();
                })
                .collect(Collectors.toList());

        if (classMethods.isEmpty()) {
            // No methods in class with that name and parameter count
            return Optional.empty();
        }

        if (classMethods.size() == 1) {
            // We found the method!
            return Optional.of(classMethods.get(0));
        }

        /*
         * Tricky one! We have to match argument and parameter types, so we have to:
         *   - Differentiate arguments expressions:
         *       - Easy: In case of CastExpr, get the type
         *       - Easy: In case of ObjectCreationExpr, get the type
         *       - Medium: In case of NameExpr, find the declaration and its type
         *       - Medium: In case of LiteralExpr, get the type
         *       - Hard: In case of MethodCallExpr, find MethodDeclaration and its type
         *   - If there is a varargs parameter, check every argument corresponding to it has the same type
         *
         * Example:
         *   At this point these three methods are considered as called:
         *    private void foo(int a, int b) {}
         *    private void foo(String a, String b) {}
         *    private void foo(String a, int... bs) {}
         *
         * We have to match types to get the correct one
         *
         * */

        return classMethods.stream().filter(methodDeclaration -> {
            boolean match = true;

            for (int i = 0; i < methodDeclaration.getParameters().size(); i++) {
                if (!match) {
                    break;
                }

                if (argumentsCount < i) {
                    return argumentsCount == i - 1
                            && methodDeclaration.getParameter(i).isVarArgs();
                }

                // TODO - Convert into a visitor

                Expression argumentExpression = methodCallExpr.getArgument(i);
                Parameter parameter = methodDeclaration.getParameter(i);

                if (argumentExpression.isCastExpr()) {
                    match = Objects.equals(argumentExpression.asCastExpr().getType(), parameter.getType());
                } else if (argumentExpression.isObjectCreationExpr()) {
                    match = Objects.equals(argumentExpression.asObjectCreationExpr().getType(), parameter.getType());
                } else if (argumentExpression.isNameExpr()) {
                    String variableName = argumentExpression.asNameExpr().getNameAsString();

                    List<GraphNode<?>> declarationsOfVariable = sdg.findDeclarationsOfVariable(argumentExpression.asNameExpr().getNameAsString(), methodCallNode);

                    assert !declarationsOfVariable.isEmpty();

                    GraphNode<?> declarationNode = declarationsOfVariable.get(declarationsOfVariable.size() - 1);

                    ExpressionStmt expressionStmt = (ExpressionStmt) declarationNode.getAstNode();

                    assert expressionStmt.getExpression().isVariableDeclarationExpr();

                    match = expressionStmt.getExpression().asVariableDeclarationExpr().getVariables().stream()
                            .filter(variableDeclarator -> Objects.equals(variableDeclarator.getName().asString(), variableName))
                            .findFirst()
                            .map(variableDeclarator -> Objects.equals(variableDeclarator.getType(), parameter.getType()))
                            .orElse(false);
                } // TODO: More checks
            }

            return match;
        }).findFirst();
    }
}
+10 −35
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@ import tfm.arcs.sdg.CallArc;
import tfm.arcs.sdg.ParameterInOutArc;
import tfm.graphs.Buildable;
import tfm.graphs.Graph;
import tfm.graphs.cfg.CFG;
import tfm.graphs.pdg.PDG;
import tfm.nodes.*;
import tfm.slicing.Slice;
@@ -25,10 +26,10 @@ import java.util.stream.Collectors;
public class SDG extends Graph implements Sliceable, Buildable<NodeList<CompilationUnit>> {
    private boolean built = false;

    private Map<Context, Long> contextToMethodRoot;
    private Map<MethodDeclaration, CFG> methodCFGMap;

    public SDG() {
        this.contextToMethodRoot = new HashMap<>();
        this.methodCFGMap = new HashMap<>();
    }

    @Override
@@ -46,46 +47,20 @@ public class SDG extends Graph implements Sliceable, Buildable<NodeList<Compilat
        return built;
    }

    public Set<Context> getContexts() {
        return contextToMethodRoot.keySet();
    public Set<MethodDeclaration> getMethodDeclarations() {
        return this.methodCFGMap.keySet();
    }

    @SuppressWarnings("unchecked")
    public List<GraphNode<MethodDeclaration>> getMethodRoots() {
        return contextToMethodRoot.values().stream()
                .map(id -> findNodeById(id))
                .filter(Optional::isPresent)
                .map(optional -> (GraphNode<MethodDeclaration>) optional.get())
                .collect(Collectors.toList());
    public void setMethodCFG(MethodDeclaration methodDeclaration, CFG cfg) {
        this.methodCFGMap.put(methodDeclaration, cfg);
    }

    @SuppressWarnings("unchecked")
    public Optional<GraphNode<MethodDeclaration>> getRootNode(Context context) {
        Long id = this.contextToMethodRoot.get(context);

        if (id == null) {
    public Optional<CFG> getMethodCFG(MethodDeclaration methodDeclaration) {
        if (!this.methodCFGMap.containsKey(methodDeclaration)) {
            return Optional.empty();
        }

        return findNodeById(id).map(node -> (GraphNode<MethodDeclaration>) node);
    }

    public void addRootNode(Context context, long id) {
        if (!findNodeById(id).isPresent())
            throw new IllegalArgumentException("Root node with id " + id + " is not contained in graph!");

        this.contextToMethodRoot.put(new Context(context), id);
    }

    public void addRootNode(Context context, GraphNode<MethodDeclaration> node) {
        addRootNode(context, node.getId());
    }

    public Optional<Context> getContext(long id) {
        return contextToMethodRoot.entrySet().stream()
                .filter(entry -> Objects.equals(entry.getValue(), id))
                .findFirst()
                .map(Map.Entry::getKey);
        return Optional.of(this.methodCFGMap.get(methodDeclaration));
    }

    public void addControlDependencyArc(GraphNode<?> from, GraphNode<?> to) {
+3 −3
Original line number Diff line number Diff line
@@ -51,8 +51,8 @@ class SDGBuilder extends VoidVisitorAdapter<Context> {

        GraphNode<MethodDeclaration> methodDeclarationNode = pdg.getRootNode().get();

        // Add root node from PDG
        sdg.addRootNode(context, methodDeclarationNode.getId());
        // Add CFG
        sdg.setMethodCFG(methodDeclaration, pdg.getCfg());
    }

    @Override
@@ -72,7 +72,7 @@ class SDGBuilder extends VoidVisitorAdapter<Context> {
        // Once every PDG is built, expand method call nodes of each one
        // and link them to the corresponding method declaration node
        MethodCallReplacer methodCallReplacer = new MethodCallReplacer(sdg);
        methodCallReplacer.replace();
        methodCallReplacer.replace(context);