Commit 17c92bef authored by Carlos Galindo's avatar Carlos Galindo
Browse files

InterproceduralActionFinders structure altered, can merge trees

* IAF now use a map of VariableAction instead of a set of StoredAction.
* They contain a map with stored actions.
* In their state, they accumulate the tree of repeated actions.
parent fccdabb3
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -275,7 +275,7 @@ public class CallGraph extends DirectedPseudograph<CallGraph.Vertex, CallGraph.E

        @Override
        public String toString() {
            return super.toString();
            return declaration.toString();
        }
    }

+45 −45
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ import es.upv.mist.slicing.graphs.BackwardDataFlowAnalysis;
import es.upv.mist.slicing.graphs.CallGraph;
import es.upv.mist.slicing.graphs.cfg.CFG;
import es.upv.mist.slicing.nodes.VariableAction;
import es.upv.mist.slicing.nodes.VariableAction.ObjectTree;
import es.upv.mist.slicing.utils.ASTUtils;
import es.upv.mist.slicing.utils.Logger;

@@ -21,7 +22,6 @@ 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
@@ -31,8 +31,10 @@ import java.util.stream.Stream;
 * declarations define, use or declare which variables, interprocedurally.
 * @param <A> The action to be searched for
 */
public abstract class InterproceduralActionFinder<A extends VariableAction> extends BackwardDataFlowAnalysis<CallGraph.Vertex, CallGraph.Edge<?>, Set<InterproceduralActionFinder.StoredAction<A>>> {
public abstract class InterproceduralActionFinder<A extends VariableAction> extends BackwardDataFlowAnalysis<CallGraph.Vertex, CallGraph.Edge<?>, Map<A, ObjectTree>> {
    protected final Map<CallableDeclaration<?>, CFG> cfgMap;
    /** A map from vertex and action to its corresponding stored action, to avoid generating duplicate nodes. */
    protected final Map<CallGraph.Vertex, Map<A, StoredAction>> actionStoredMap = new HashMap<>();

    public InterproceduralActionFinder(CallGraph callGraph, Map<CallableDeclaration<?>, CFG> cfgMap) {
        super(callGraph);
@@ -49,18 +51,26 @@ public abstract class InterproceduralActionFinder<A extends VariableAction> exte
        graph.vertexSet().forEach(this::saveDeclaration);
    }

    /** Obtains the StoredAction object with information on which actions have been stored. */
    protected StoredAction getStored(CallGraph.Vertex vertex, A action) {
        return actionStoredMap.get(vertex).get(action);
    }

    /** Save the current set of actions associated to the given declaration. It will avoid saving
     *  duplicates by default, so this method may be called multiple times safely. */
    protected void saveDeclaration(CallGraph.Vertex vertex) {
        Set<StoredAction<A>> storedActions = vertexDataMap.get(vertex);

        var actions = vertexDataMap.get(vertex);
        // Update stored action map
        actionStoredMap.computeIfAbsent(vertex, v -> new HashMap<>());
        for (A a : actions.keySet())
            actionStoredMap.get(vertex).computeIfAbsent(a, __ -> new StoredAction());
        // FORMAL: per declaration (1)
        for (StoredAction<A> sa : storedActions)
            sa.storeFormal(a -> sandBoxedHandler(vertex.getDeclaration(), a, this::handleFormalAction));
        for (A a : actions.keySet())
            getStored(vertex, a).storeFormal(() -> sandBoxedHandler(vertex, a, this::handleFormalAction));
        // ACTUAL: per call (n)
        for (CallGraph.Edge<?> edge : graph.incomingEdgesOf(vertex))
            storedActions.stream().sorted(new ParameterFieldSorter(edge))
                    .forEach(sa -> sa.storeActual(edge, (e, a) -> sandBoxedHandler(e, a, this::handleActualAction)));
            actions.keySet().stream().sorted(new ParameterFieldSorter(edge)).forEach(a ->
                    getStored(vertex, a).storeActual(edge, e -> sandBoxedHandler(e, a, this::handleActualAction)));
    }

    /** A sandbox to avoid resolution errors when a variable is included that is a class name
@@ -74,7 +84,7 @@ public abstract class InterproceduralActionFinder<A extends VariableAction> exte
    }

    /** Generate the formal node(s) related to this action and declaration. */
    protected abstract void handleFormalAction(CallableDeclaration<?> declaration, A action);
    protected abstract void handleFormalAction(CallGraph.Vertex vertex, A action);

    /** Generate the actual node(s) related to this action and call. */
    protected abstract void handleActualAction(CallGraph.Edge<?> edge, A action);
@@ -147,19 +157,19 @@ public abstract class InterproceduralActionFinder<A extends VariableAction> exte
    // ===========================================================

    @Override
    protected Set<StoredAction<A>> compute(CallGraph.Vertex vertex, Set<CallGraph.Vertex> predecessors) {
    protected Map<A, ObjectTree> compute(CallGraph.Vertex vertex, Set<CallGraph.Vertex> predecessors) {
        saveDeclaration(vertex);
        Set<StoredAction<A>> newValue = new HashSet<>(vertexDataMap.get(vertex));
        newValue.addAll(initialValue(vertex));
        Map<A, ObjectTree> newValue = new HashMap<>(vertexDataMap.get(vertex));
        newValue.putAll(initialValue(vertex));
        return newValue;
    }

    @Override
    protected Set<StoredAction<A>> initialValue(CallGraph.Vertex vertex) {
    protected Map<A, ObjectTree> initialValue(CallGraph.Vertex vertex) {
        CFG cfg = cfgMap.get(vertex.getDeclaration());
        if (cfg == null)
            return Collections.emptySet();
        Stream<VariableAction> stream =  cfg.vertexSet().stream()
            return Collections.emptyMap();
        Stream<VariableAction> actionStream =  cfg.vertexSet().stream()
                // Ignore root node, it is literally the entrypoint for interprocedural actions.
                .filter(n -> n != cfg.getRootNode())
                .flatMap(n -> n.getVariableActions().stream())
@@ -167,9 +177,16 @@ public abstract class InterproceduralActionFinder<A extends VariableAction> exte
                .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());
        Stream<A> filteredStream = mapAndFilterActionStream(actionStream, cfg);
        Map<A, ObjectTree> map = new HashMap<>();
        for (Iterator<A> it = filteredStream.iterator(); it.hasNext(); ) {
            A a = it.next();
            if (map.containsKey(a))
                map.get(a).addAll(a.getObjectTree());
            else
                map.put(a, (ObjectTree) a.getObjectTree().clone());
        }
        return map;
    }

    /** Given a stream of VariableAction objects, map it to the finders' type and
@@ -183,19 +200,19 @@ public abstract class InterproceduralActionFinder<A extends VariableAction> exte
    /** 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. */
    private class ParameterFieldSorter implements Comparator<StoredAction<A>> {
    private class ParameterFieldSorter implements Comparator<A> {
        protected final CallGraph.Edge<?> edge;
        public ParameterFieldSorter(CallGraph.Edge<?> edge) {
            this.edge = edge;
        }

        @Override
        public int compare(StoredAction<A> o1, StoredAction<A> o2) {
        public int compare(A o1, A o2) {
            ResolvedValueDeclaration r1 = null;
            ResolvedValueDeclaration r2 = null;
            try {
                r1 = o1.getAction().getResolvedValueDeclaration();
                r2 = o2.getAction().getResolvedValueDeclaration();
                r1 = o1.getResolvedValueDeclaration();
                r2 = o2.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()));
@@ -219,47 +236,30 @@ public abstract class InterproceduralActionFinder<A extends VariableAction> exte

    /** A wrapper around a variable action, which keeps track of whether formal and actual nodes
     *  have been saved to the graph or not. */
    protected static class StoredAction<A extends VariableAction> {
        protected final A action;
    protected static class StoredAction {
        /** Whether the action has been saved as actual node for each call. */
        private final Map<CallGraph.Edge<?>, Boolean> actualStoredMap = new HashMap<>();

        /** Whether the action has been saved as formal node. */
        protected boolean formalStored = false;

        private StoredAction(A action) {
            this.action = action;
        }

        public A getAction() {
            return action;
        }
        private StoredAction() {}

        /** If this action has not yet been saved as formal node, use the argument to do so, then mark it as stored. */
        private void storeFormal(Consumer<A> save) {
        private void storeFormal(Runnable save) {
            if (!formalStored) {
                save.accept(action);
                save.run();
                formalStored = true;
            }
        }

        /** If this action has not yet been saved as actual node for the given edge,
         * use the consumer to do so, then mark it as stored. */
        private void storeActual(CallGraph.Edge<?> edge, BiConsumer<CallGraph.Edge<?>, A> save) {
        private void storeActual(CallGraph.Edge<?> edge, Consumer<CallGraph.Edge<?>> save) {
            if (!actualStoredMap.getOrDefault(edge, false)) {
                save.accept(edge, action);
                save.accept(edge);
                actualStoredMap.put(edge, true);
            }
        }

        @Override
        public int hashCode() {
            return Objects.hash(action);
        }

        @Override
        public boolean equals(Object obj) {
            return obj instanceof StoredAction && Objects.equals(action, ((StoredAction<?>) obj).action);
        }
    }
}
+20 −14
Original line number Diff line number Diff line
@@ -9,6 +9,9 @@ import es.upv.mist.slicing.graphs.CallGraph;
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.VariableAction.Definition;
import es.upv.mist.slicing.nodes.VariableAction.Movable;
import es.upv.mist.slicing.nodes.VariableAction.ObjectTree;
import es.upv.mist.slicing.nodes.io.ActualIONode;
import es.upv.mist.slicing.nodes.io.FormalIONode;

@@ -16,28 +19,31 @@ import java.util.*;
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> {
public class InterproceduralDefinitionFinder extends InterproceduralActionFinder<Definition> {
    public InterproceduralDefinitionFinder(CallGraph callGraph, Map<CallableDeclaration<?>, CFG> cfgMap) {
        super(callGraph, cfgMap);
    }

    @Override
    protected void handleFormalAction(CallableDeclaration<?> declaration, VariableAction.Definition def) {
        CFG cfg = cfgMap.get(declaration);
    protected void handleFormalAction(CallGraph.Vertex vertex, Definition def) {
        CFG cfg = cfgMap.get(vertex.getDeclaration());
        ResolvedValueDeclaration resolved = def.getResolvedValueDeclaration();
        ObjectTree objTree = vertexDataMap.get(vertex).get(def);
        if (!resolved.isParameter() || !resolved.getType().isPrimitive()) {
            FormalIONode formalOut = FormalIONode.createFormalOut(declaration, resolved);
            cfg.getExitNode().addMovableVariable(new VariableAction.Movable(def.toUsage(cfg.getExitNode()), formalOut));
            FormalIONode formalOut = FormalIONode.createFormalOut(vertex.getDeclaration(), resolved);
            Movable movable = new Movable(def.toUsage(cfg.getExitNode(), objTree), formalOut);
            cfg.getExitNode().addMovableVariable(movable);
        }
        FormalIONode formalIn = FormalIONode.createFormalInDecl(declaration, resolved);
        cfg.getRootNode().addMovableVariable(new VariableAction.Movable(def.toDeclaration(cfg.getRootNode()), formalIn));
        FormalIONode formalIn = FormalIONode.createFormalInDecl(vertex.getDeclaration(), resolved);
        cfg.getRootNode().addMovableVariable(new Movable(def.toDeclaration(cfg.getRootNode(), objTree), formalIn));
    }

    @Override
    protected void handleActualAction(CallGraph.Edge<?> edge, VariableAction.Definition def) {
        List<VariableAction.Movable> movables = new LinkedList<>();
    protected void handleActualAction(CallGraph.Edge<?> edge, Definition def) {
        List<Movable> movables = new LinkedList<>();
        GraphNode<?> graphNode = edge.getGraphNode();
        ResolvedValueDeclaration resolved = def.getResolvedValueDeclaration();
        ObjectTree objTree = vertexDataMap.get(graph.getEdgeTarget(edge)).get(def);
        if (resolved.isParameter()) {
            Expression arg = extractArgument(resolved.asParameter(), edge, false);
            if (arg == null)
@@ -47,9 +53,9 @@ public class InterproceduralDefinitionFinder extends InterproceduralActionFinder
                Set<NameExpr> exprSet = new HashSet<>();
                arg.accept(new OutNodeVariableVisitor(), exprSet);
                for (NameExpr nameExpr : exprSet)
                    movables.add(new VariableAction.Movable(new VariableAction.Definition(nameExpr, nameExpr.toString(), graphNode), actualOut));
                    movables.add(new Movable(new Definition(nameExpr, nameExpr.toString(), graphNode, objTree), actualOut));
            } else {
                movables.add(new VariableAction.Movable(def.toDefinition(graphNode), actualOut));
                movables.add(new Movable(def.toDefinition(graphNode, objTree), actualOut));
            }
        } else if (resolved.isField()) {
            // Known limitation: static fields
@@ -59,8 +65,8 @@ public class InterproceduralDefinitionFinder extends InterproceduralActionFinder
                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));
            var movableDef  = new Definition(obtainScope(edge.getCall()), aliasedName, graphNode, objTree);
            movables.add(new Movable(movableDef, actualOut));
        } else {
            throw new IllegalStateException("Definition must be either from a parameter or a field!");
        }
@@ -68,7 +74,7 @@ public class InterproceduralDefinitionFinder extends InterproceduralActionFinder
    }

    @Override
    protected Stream<VariableAction.Definition> mapAndFilterActionStream(Stream<VariableAction> stream, CFG cfg) {
    protected Stream<Definition> mapAndFilterActionStream(Stream<VariableAction> stream, CFG cfg) {
        return stream.filter(VariableAction::isDefinition)
                .map(VariableAction::asDefinition)
                .filter(def -> cfg.findDeclarationFor(def).isEmpty());
+26 −13
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ import es.upv.mist.slicing.graphs.CallGraph;
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.VariableAction.*;
import es.upv.mist.slicing.nodes.VariableVisitor;
import es.upv.mist.slicing.nodes.io.ActualIONode;
import es.upv.mist.slicing.nodes.io.FormalIONode;
@@ -19,32 +20,44 @@ import java.util.function.Predicate;
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> {
public class InterproceduralUsageFinder extends InterproceduralActionFinder<Usage> {
    public InterproceduralUsageFinder(CallGraph callGraph, Map<CallableDeclaration<?>, CFG> cfgMap) {
        super(callGraph, cfgMap);
    }

    @Override
    protected void handleFormalAction(CallableDeclaration<?> declaration, VariableAction.Usage use) {
        CFG cfg = cfgMap.get(declaration);
    protected void handleFormalAction(CallGraph.Vertex vertex, Usage use) {
        CFG cfg = cfgMap.get(vertex.getDeclaration());
        ResolvedValueDeclaration resolved = use.getResolvedValueDeclaration();
        FormalIONode formalIn = FormalIONode.createFormalIn(declaration, resolved);
        cfg.getRootNode().addMovableVariable(new VariableAction.Movable(use.toDefinition(cfg.getRootNode()), formalIn));
        FormalIONode formalIn = FormalIONode.createFormalIn(vertex.getDeclaration(), resolved);
        ObjectTree objTree = vertexDataMap.get(vertex).get(use);
        Movable movable = new Movable(use.toDefinition(cfg.getRootNode(), objTree), formalIn);
        cfg.getRootNode().addMovableVariable(movable);
    }

    @Override
    protected void handleActualAction(CallGraph.Edge<?> edge, VariableAction.Usage use) {
        List<VariableAction.Movable> movables = new LinkedList<>();
    protected void handleActualAction(CallGraph.Edge<?> edge, Usage use) {
        List<Movable> movables = new LinkedList<>();
        GraphNode<?> graphNode = edge.getGraphNode();
        ResolvedValueDeclaration resolved = use.getResolvedValueDeclaration();
        ObjectTree objTree = vertexDataMap.get(graph.getEdgeTarget(edge)).get(use);
        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))
                    (n, exp, name) -> movables.add(new Movable(new Declaration(exp, name, graphNode), actualIn)),
                    (n, exp, name, expression) -> movables.add(new Movable(new Definition(exp, name, graphNode, expression), actualIn)),
                    (n, exp, name) -> movables.add(new Movable(new Usage(exp, name, graphNode), actualIn))
            ), VariableVisitor.Action.USE);
            // a) es un objeto: movables==1 ('a', 'this')
            // b) es una combinacion otras cosas ('a[1]', 'a.call()', construccion de string)
            // void f(A a) { log(a.x); } <-- f(theA); // se copia el arbol de log(a) a f(theA) :D
            // void f(A a) { log(a.x); } <-- f(as[1]); // no se debe copiar el arbol a as
            // void f(A a) { log(a.x); } <-- f(call()); // el arbol va de log a call (por su retorno)
            // TODO: this check is not specific enough
            // Only copy the tree to the movables if there is only 1 movable: it is an object.
            if (movables.size() == 1)
                movables.get(0).getObjectTree().addAll(objTree);
        } else if (resolved.isField()) {
            // Known limitation: static fields
            // An object creation expression input an existing object via actual-in because it creates it.
@@ -52,8 +65,8 @@ public class InterproceduralUsageFinder extends InterproceduralActionFinder<Vari
                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));
            var movableUse = new Usage(obtainScope(edge.getCall()), aliasedName, graphNode, objTree);
            movables.add(new Movable(movableUse, actualIn));
        } else {
            throw new IllegalStateException("Definition must be either from a parameter or a field!");
        }
@@ -61,7 +74,7 @@ public class InterproceduralUsageFinder extends InterproceduralActionFinder<Vari
    }

    @Override
    protected Stream<VariableAction.Usage> mapAndFilterActionStream(Stream<VariableAction> stream, CFG cfg) {
    protected Stream<Usage> mapAndFilterActionStream(Stream<VariableAction> stream, CFG cfg) {
        return stream.filter(VariableAction::isUsage)
                .map(VariableAction::asUsage)
                .filter(Predicate.not(cfg::isCompletelyDefined));
+65 −16

File changed.

Preview size limit exceeded, changes collapsed.