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

Tree handling of objects, multiple fixes and improvements

* Update tests with better slices.
* GraphNode#addActionsAfterCall uses varargs instead of a set.
* Improved handling of super() and this() calls.
* ImplicitNode is now generic
* Improved interface in JSysCFG to insert new instructions ad-hoc.
* Constructors now have an implicit "return this".
* Removed JSysCFG#getDeclarationClass, ASTUtils has a similar method.
* VariableActions can now store (but don't propagate) a tree of fields acted
  upon.
* VariableActions can now take null as variable, or any possible expression.
* Automatic generation of the actions for fields (USE(a.x) generates USE(a)).
* Conversely, fields now either traverse their scope or declare an action.
* Handling of ThisExpr.
* Better initialization naming for fields.
* Implemented default initialization of fields if none is present.
* InterproceduralActionFinders:
  * Unified the behaviour of #initialValue()
  * Improved generation of actual-in/out for fields.

Caveats:
* InterproceduralActionFinders cannot process VariableActions that don't resolve
  into a ResolvedValueDeclaration. Thus, 'this' must be declared when entering a
  method or constructor declaration.
* Removed type from IO nodes, as it required resolving expressions and was
  unnecessary for the slicer.
parent a2dc36c5
Loading
Loading
Loading
Loading
Loading
+7 −3
Original line number Diff line number Diff line
@@ -3,9 +3,13 @@ package es.upv.mist.slicing.graphs.jsysdg;
import com.github.javaparser.ast.Node;
import es.upv.mist.slicing.nodes.SyntheticNode;

// TODO: Concretar más el tipo T del nodo (Node es muy general). Por ahora seria solo ExplicitConstructorInvocationStmt
public class ImplicitNode extends SyntheticNode<Node> {
    protected ImplicitNode(String instruction, Node astNode) {
/**
 * A graph node that does not exist in the original program, but represents
 * implicit code constructs such as 'super()' at the start of a constructor.
 * @param <T> The type of the AST node contained in this graph node.
 */
public class ImplicitNode<T extends Node> extends SyntheticNode<T> {
    protected ImplicitNode(String instruction, T astNode) {
        super(instruction, astNode);
    }
}
+35 −34
Original line number Diff line number Diff line
@@ -2,48 +2,42 @@ package es.upv.mist.slicing.graphs.jsysdg;

import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.BodyDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.ConstructorDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.expr.ThisExpr;
import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt;
import com.github.javaparser.ast.stmt.ReturnStmt;
import es.upv.mist.slicing.graphs.ClassGraph;
import es.upv.mist.slicing.graphs.cfg.CFGBuilder;
import es.upv.mist.slicing.graphs.exceptionsensitive.ESCFG;
import es.upv.mist.slicing.nodes.GraphNode;
import es.upv.mist.slicing.utils.ASTUtils;

import java.util.LinkedList;
import java.util.List;

/**
 * An SDG that is tailored for Java, including a class graph, inheritance,
 * polymorphism and other features.
 */
public class JSysCFG extends ESCFG {

    /** ClassGraph associated to the Method represented by the CFG */
    protected ClassGraph clg;
    protected ClassGraph classGraph;

    public JSysCFG(ClassGraph clg){
    public JSysCFG(ClassGraph classGraph){
        super();
        this.clg = clg;
    }

    public ClassGraph getClassGraph(){
        return this.clg;
        this.classGraph = classGraph;
    }

    @Override
    protected CFGBuilder newCFGBuilder() {
        return new Builder(this);
    }

    /** Obtains the Javaparser Node corresponding to the class where the CFG is contained */
    public ClassOrInterfaceDeclaration getDeclarationClass() {
        assert rootNode != null;
        if (!(rootNode.getAstNode().getParentNode().get() instanceof ClassOrInterfaceDeclaration))
            throw new IllegalStateException("The Method declaration is not directly inside a Class Declaration");
        return (ClassOrInterfaceDeclaration) rootNode.getAstNode().getParentNode().get();
    }

    public class Builder extends ESCFG.Builder {

        /** List of inserted super calls in Javaparser AST to process them as Implicit Nodes (@ImplicitNode)*/
        /** List of implicit instructions inserted explicitly in this CFG.
         *  They should be included in the graph as ImplicitNodes. */
        protected List<Node> methodInsertedInstructions = new LinkedList<>();

        protected Builder(JSysCFG jSysCFG) {
@@ -51,24 +45,28 @@ public class JSysCFG extends ESCFG {
            assert jSysCFG == JSysCFG.this;
        }

        /** Esto se llama porque lo hemos insertado fantasma o porque existe. A continuacion se inserta el codigo dynInit */
        @Override
        public void visit(ExplicitConstructorInvocationStmt n, Void arg) {

            // 1. Create new super call if not present
        protected <T extends Node> GraphNode<T> connectTo(T n, String text) {
            GraphNode<T> dest;
            if (methodInsertedInstructions.contains(n)) {
                ImplicitNode node = new ImplicitNode(n.toString(), n);
                graph.addVertex(node);
                connectTo(node);
                dest = new ImplicitNode<>(n.toString(), n);
            } else {
                dest = new GraphNode<>(text, n);
            }
            else {
                connectTo(n);
            addVertex(dest);
            connectTo(dest);
            return dest;
        }
            // 2. Insert dynamic class code
            ClassOrInterfaceDeclaration containerClass = ((JSysCFG) graph).getDeclarationClass();
            List<BodyDeclaration<?>> dynInitList = ((JSysCFG) graph).getClassGraph().getDynInit(containerClass.getNameAsString());
            dynInitList.forEach(node -> node.accept(this, arg));

        @Override
        public void visit(ExplicitConstructorInvocationStmt n, Void arg) {
            // 1. Connect to the following statements
            connectTo(n);
            // 2. Insert dynamic class code (only for super())
            if (!n.isThis()) {
                ClassOrInterfaceDeclaration containerClass = ASTUtils.getClassNode(rootNode.getAstNode());
                classGraph.getDynInit(containerClass.getNameAsString()).forEach(node -> node.accept(this, arg));
            }
            // 3. Handle exceptions
            super.visitCallForExceptions(n);
        }
@@ -84,8 +82,11 @@ public class JSysCFG extends ESCFG {
            // Insert call to super() if it is implicit.
            if (!ASTUtils.constructorHasExplicitConstructorInvocation(n)){
                var superCall = new ExplicitConstructorInvocationStmt(null, null, false, null, new NodeList<>());
                var returnThis = new ReturnStmt(new ThisExpr());
                methodInsertedInstructions.add(superCall);
                methodInsertedInstructions.add(returnThis);
                n.getBody().addStatement(0, superCall);
                n.getBody().addStatement(returnThis);
            }
            // Perform the same task as previous graphs.
            super.visit(n, arg);
+92 −14
Original line number Diff line number Diff line
@@ -2,7 +2,12 @@ package es.upv.mist.slicing.graphs.sdg;

import com.github.javaparser.ast.body.CallableDeclaration;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.expr.ThisExpr;
import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt;
import com.github.javaparser.resolution.Resolvable;
import com.github.javaparser.resolution.UnsolvedSymbolException;
import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration;
import com.github.javaparser.resolution.declarations.ResolvedParameterDeclaration;
import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration;
import es.upv.mist.slicing.graphs.BackwardDataFlowAnalysis;
@@ -15,6 +20,9 @@ import es.upv.mist.slicing.utils.Logger;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

// TODO: this approach of generating actual nodes may skip an argument; this is only a problem if there is a definition
// TODO: update placement of actual and formal outputs for ESSDG (see if the definition/usage reaches all/any exits).
@@ -71,24 +79,73 @@ public abstract class InterproceduralActionFinder<A extends VariableAction> exte
    /** Generate the actual node(s) related to this action and call. */
    protected abstract void handleActualAction(CallGraph.Edge<?> edge, A action);

    // ===========================================================
    // ============== AUXILIARY METHODS FOR CHILDREN =============
    // ===========================================================

    /** Given a call, obtains the scope. If none is present it may return null.
     *  ExpressionConstructorInvocations result in a this expression, as they
     *  may be seen as dynamic method calls that can modify 'this'. */
    protected static Expression obtainScope(Resolvable<? extends ResolvedMethodLikeDeclaration> call) {
        if (call instanceof MethodCallExpr) {
            var methodCall = (MethodCallExpr) call;
            return methodCall.getScope().orElse(null);
        } else if (call instanceof ExplicitConstructorInvocationStmt) {
            return new ThisExpr();
        } else {
            throw new IllegalArgumentException("The given call is not of a valid type");
        }
    }

    /** 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.getResolvedValueDeclaration();
    protected Expression extractArgument(ResolvedParameterDeclaration p, CallGraph.Edge<?> edge, boolean input) {
        CallableDeclaration<?> callTarget = graph.getEdgeTarget(edge).getDeclaration();
        if (resolved.isParameter()) {
            ResolvedParameterDeclaration p = resolved.asParameter();
        if (!input && p.getType().isPrimitive())
            return null; // primitives do not have actual-out!
        int paramIndex = ASTUtils.getMatchingParameterIndex(callTarget, p);
        return ASTUtils.getResolvableArgs(edge.getCall()).get(paramIndex);
        } else if (resolved.isField()) {
            return action.getVariableExpression();
    }

    /** Generate the name that should be given to an object in a caller method, given an action
     *  in the callee method. This is used to transform a reference to 'this' into the scope
     *  of a method. */
    protected static String obtainAliasedFieldName(VariableAction action, CallGraph.Edge<?> edge) {
        if (edge.getCall() instanceof MethodCallExpr) {
            Optional<Expression> optScope = ((MethodCallExpr) edge.getCall()).getScope();
            return obtainAliasedFieldName(action, edge, optScope.isPresent() ? optScope.get().toString() : "");
        } else if (edge.getCall() instanceof ExplicitConstructorInvocationStmt) {
            // The only possibility is 'this' or its fields, so we return empty scope and 'type.this.' is generated
            return obtainAliasedFieldName(action, edge, "");
        } else {
            throw new IllegalArgumentException("The given call is not of a valid type");
        }
    }

    /** To be used by {@link #obtainAliasedFieldName(VariableAction, CallGraph.Edge)} exclusively. <br/>
     *  Given a scope, name inside a method and call, translates the name of a variable, such that 'this' becomes
     *  the scope of the method. */
    protected static String obtainAliasedFieldName(VariableAction action, CallGraph.Edge<?> edge, String scope) {
        if (scope.isEmpty()) {
            return action.getVariable();
        } else {
            throw new IllegalArgumentException("Variable should be either param or field!");
            String newPrefix = scope;
            newPrefix = newPrefix.replaceAll("((\\.)super|^super)(\\.)?", "$2this$3");
            if (newPrefix.equals("this")) {
                String fqName = ASTUtils.getClassNode(edge.getGraphNode().getAstNode()).getFullyQualifiedName().orElseThrow();
                newPrefix = fqName + ".this";
            }
            String withPrefix = action.getVariable();
            String withoutPrefix = withPrefix.replaceFirst("^((.*\\.)?this\\.?)", "");
            String result = newPrefix + withoutPrefix;
            return result.replaceFirst("this(\\.this)+", "this");
        }
    }

    // ===========================================================
    // =============== COMPUTE DATA FOR FIXED POINT ==============
    // ===========================================================

    @Override
    protected Set<StoredAction<A>> compute(CallGraph.Vertex vertex, Set<CallGraph.Vertex> predecessors) {
        saveDeclaration(vertex);
@@ -97,11 +154,32 @@ public abstract class InterproceduralActionFinder<A extends VariableAction> exte
        return newValue;
    }

    /** Wrap a variable action in a {@link StoredAction}, to track whether it has been applied to the graph or not. */
    protected StoredAction<A> wrapAction(A action) {
        return new StoredAction<>(action);
    @Override
    protected Set<StoredAction<A>> initialValue(CallGraph.Vertex vertex) {
        CFG cfg = cfgMap.get(vertex.getDeclaration());
        if (cfg == null)
            return Collections.emptySet();
        Stream<VariableAction> stream =  cfg.vertexSet().stream()
                // Ignore root node, it is literally the entrypoint for interprocedural actions.
                .filter(n -> n != cfg.getRootNode())
                .flatMap(n -> n.getVariableActions().stream())
                // We never analyze synthetic variables (all intraprocedural)
                .filter(Predicate.not(VariableAction::isSynthetic))
                // We skip over non-root variables (for each 'x.a' action we'll find 'x' later)
                .filter(VariableAction::isRootAction);
        return mapAndFilterActionStream(stream, cfg)
                .map(StoredAction::new)
                .collect(Collectors.toSet());
    }

    /** Given a stream of VariableAction objects, map it to the finders' type and
     *  filter unwanted items (only if the filter is specific to that type). */
    protected abstract Stream<A> mapAndFilterActionStream(Stream<VariableAction> stream, CFG cfg);

    // ===========================================================
    // ========================= SUBCLASSES ======================
    // ===========================================================

    /** A comparator to sort parameters and fields in the generation of actual nodes. It will sort
     *  {@link StoredAction}s in the following order: fields, then parameters by descending index number.
     *  The actual nodes will be generated in that order and inserted in reverse order in the graph node. */
+28 −24
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ package es.upv.mist.slicing.graphs.sdg;
import com.github.javaparser.ast.body.CallableDeclaration;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.expr.ObjectCreationExpr;
import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration;
import es.upv.mist.slicing.graphs.CallGraph;
import es.upv.mist.slicing.graphs.cfg.CFG;
@@ -11,12 +12,10 @@ import es.upv.mist.slicing.nodes.VariableAction;
import es.upv.mist.slicing.nodes.io.ActualIONode;
import es.upv.mist.slicing.nodes.io.FormalIONode;

import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/** An interprocedural definition finder, which adds the associated actions to formal and actual nodes in the CFGs. */
public class InterproceduralDefinitionFinder extends InterproceduralActionFinder<VariableAction.Definition> {
@@ -41,7 +40,8 @@ public class InterproceduralDefinitionFinder extends InterproceduralActionFinder
        Set<VariableAction.Movable> movables = new HashSet<>();
        GraphNode<?> graphNode = edge.getGraphNode();
        ResolvedValueDeclaration resolved = def.getResolvedValueDeclaration();
        Expression arg = extractArgument(def, edge, false);
        if (resolved.isParameter()) {
            Expression arg = extractArgument(resolved.asParameter(), edge, false);
            if (arg == null)
                return;
            ActualIONode actualOut = ActualIONode.createActualOut(edge.getCall(), resolved, arg);
@@ -53,22 +53,26 @@ public class InterproceduralDefinitionFinder extends InterproceduralActionFinder
            } else {
                movables.add(new VariableAction.Movable(def.toDefinition(graphNode), actualOut));
            }
        } else if (resolved.isField()) {
            // Known limitation: static fields
            // An object creation expression doesn't alter an existing object via actual-out
            // it is returned and assigned via -output-.
            if (edge.getCall() instanceof ObjectCreationExpr)
                return;
            String aliasedName = obtainAliasedFieldName(def, edge);
            ActualIONode actualOut = ActualIONode.createActualOut(edge.getCall(), resolved, null);
            var movableDef  = new VariableAction.Definition(obtainScope(edge.getCall()), aliasedName, graphNode, null);
            movables.add(new VariableAction.Movable(movableDef, actualOut));
        } else {
            throw new IllegalStateException("Definition must be either from a parameter or a field!");
        }
        graphNode.addActionsForCall(movables, edge.getCall(), false);
    }

    @Override
    protected Set<StoredAction<VariableAction.Definition>> initialValue(CallGraph.Vertex vertex) {
        CFG cfg = cfgMap.get(vertex.getDeclaration());
        if (cfg == null)
            return Collections.emptySet();
        return cfg.vertexSet().stream()
                .filter(n -> n != cfg.getRootNode())
                .flatMap(n -> n.getVariableActions().stream())
                .filter(VariableAction::isDefinition)
                .filter(Predicate.not(VariableAction::isSynthetic))
    protected Stream<VariableAction.Definition> mapAndFilterActionStream(Stream<VariableAction> stream, CFG cfg) {
        return stream.filter(VariableAction::isDefinition)
                .map(VariableAction::asDefinition)
                .filter(def -> cfg.findDeclarationFor(def).isEmpty())
                .map(this::wrapAction)
                .collect(Collectors.toSet());
                .filter(def -> cfg.findDeclarationFor(def).isEmpty());
    }
}
+25 −21
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ package es.upv.mist.slicing.graphs.sdg;

import com.github.javaparser.ast.body.CallableDeclaration;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.ObjectCreationExpr;
import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration;
import es.upv.mist.slicing.graphs.CallGraph;
import es.upv.mist.slicing.graphs.cfg.CFG;
@@ -11,12 +12,11 @@ import es.upv.mist.slicing.nodes.VariableVisitor;
import es.upv.mist.slicing.nodes.io.ActualIONode;
import es.upv.mist.slicing.nodes.io.FormalIONode;

import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/** An interprocedural usage finder, which adds the associated actions to formal and actual nodes in the CFGs. */
public class InterproceduralUsageFinder extends InterproceduralActionFinder<VariableAction.Usage> {
@@ -37,29 +37,33 @@ public class InterproceduralUsageFinder extends InterproceduralActionFinder<Vari
        Set<VariableAction.Movable> movables = new HashSet<>();
        GraphNode<?> graphNode = edge.getGraphNode();
        ResolvedValueDeclaration resolved = use.getResolvedValueDeclaration();
        Expression argument = extractArgument(use, edge, true);
        if (resolved.isParameter()) {
            Expression argument = extractArgument(resolved.asParameter(), edge, true);
            ActualIONode actualIn = ActualIONode.createActualIn(edge.getCall(), resolved, argument);
            argument.accept(new VariableVisitor(
                    (n, exp, name) -> movables.add(new VariableAction.Movable(new VariableAction.Declaration(exp, name, graphNode), actualIn)),
                    (n, exp, name, expression) -> movables.add(new VariableAction.Movable(new VariableAction.Definition(exp, name, graphNode, expression), actualIn)),
                    (n, exp, name) -> movables.add(new VariableAction.Movable(new VariableAction.Usage(exp, name, graphNode), actualIn))
            ), VariableVisitor.Action.USE);
        } else if (resolved.isField()) {
            // Known limitation: static fields
            // An object creation expression input an existing object via actual-in because it creates it.
            if (edge.getCall() instanceof ObjectCreationExpr)
                return;
            String aliasedName = obtainAliasedFieldName(use, edge);
            ActualIONode actualIn = ActualIONode.createActualIn(edge.getCall(), resolved, null);
            var movableUse = new VariableAction.Usage(obtainScope(edge.getCall()), aliasedName, graphNode);
            movables.add(new VariableAction.Movable(movableUse, actualIn));
        } else {
            throw new IllegalStateException("Definition must be either from a parameter or a field!");
        }
        graphNode.addActionsForCall(movables, edge.getCall(), true);
    }

    @Override
    protected Set<StoredAction<VariableAction.Usage>> initialValue(CallGraph.Vertex vertex) {
        CFG cfg = cfgMap.get(vertex.getDeclaration());
        if (cfg == null)
            return Collections.emptySet();
        return cfg.vertexSet().stream()
                .filter(n -> n != cfg.getRootNode())
                .flatMap(n -> n.getVariableActions().stream())
                .filter(VariableAction::isUsage)
                .filter(Predicate.not(VariableAction::isSynthetic))
    protected Stream<VariableAction.Usage> mapAndFilterActionStream(Stream<VariableAction> stream, CFG cfg) {
        return stream.filter(VariableAction::isUsage)
                .map(VariableAction::asUsage)
                .filter(Predicate.not(cfg::isCompletelyDefined))
                .map(this::wrapAction)
                .collect(Collectors.toSet());
                .filter(Predicate.not(cfg::isCompletelyDefined));
    }
}
Loading