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

Added MemberNodes to represent an object's tree in the PDG.

* JSysPDG: inserts IO and Member nodes before generating data dependencies.
* ObjectTree is now a real tree.
* ObjectTree has string and node iterables.
* VariableAction can be cloned with #createCopy(), the graphNode is set to null.
* VariableAction.Movable can't have object tree, getter obtains inner's.
parent 17c92bef
Loading
Loading
Loading
Loading
+68 −0
Original line number Diff line number Diff line
package es.upv.mist.slicing.graphs.jsysdg;

import es.upv.mist.slicing.graphs.exceptionsensitive.ESPDG;
import es.upv.mist.slicing.graphs.pdg.PDG;
import es.upv.mist.slicing.nodes.GraphNode;
import es.upv.mist.slicing.nodes.VariableAction;
import es.upv.mist.slicing.nodes.io.CallNode;
import es.upv.mist.slicing.nodes.io.IONode;
import es.upv.mist.slicing.nodes.oo.MemberNode;

import java.util.Deque;
import java.util.LinkedList;

public class JSysPDG extends ESPDG {
    public JSysPDG() {
@@ -10,4 +19,63 @@ public class JSysPDG extends ESPDG {
    public JSysPDG(JSysCFG cfg) {
        super(cfg);
    }

    @Override
    protected PDG.Builder createBuilder() {
        return new Builder();
    }

    protected class Builder extends ESPDG.Builder {

        /** Computes all the data dependencies between {@link VariableAction variable actions} of this graph. */
        @Override
        protected void buildDataDependency() {
            addSyntheticNodesToPDG();
            super.buildDataDependency();
        }

        protected void addSyntheticNodesToPDG() {
            for (GraphNode<?> node : cfg.vertexSet()) {
                Deque<CallNode> callNodeStack = new LinkedList<>();
                for (VariableAction va : node.getVariableActions()) {
                    if (va instanceof VariableAction.CallMarker) {
                        // Compute the call node, if entering the marker. Additionally, it places the node
                        // in the graph and makes it control-dependent on its container.
                        if (!((VariableAction.CallMarker) va).isEnter()) {
                            callNodeStack.pop();
                        } else {
                            CallNode callNode = CallNode.create(((VariableAction.CallMarker) va).getCall());
                            if (node.isImplicitInstruction())
                                callNode.markAsImplicit();
                            addVertex(callNode);
                            addControlDependencyArc(node, callNode);
                            callNodeStack.push(callNode);
                        }
                        continue;
                    }
                    GraphNode<?> parentNode; // node that represents the root of the object tree
                    if (va instanceof VariableAction.Movable) {
                        GraphNode<?> realNode = ((VariableAction.Movable) va).getRealNode();
                        addVertex(realNode);
                        connectRealNode(node, callNodeStack.peek(), realNode);
                        parentNode = realNode;
                    } else if (va.getObjectTree().isEmpty() || node instanceof IONode) {
                        parentNode = node;
                    } else {
                        parentNode = new MemberNode(va.toString(), null);
                        addVertex(parentNode);
                        addControlDependencyArc(node, parentNode);
                    }
                    // Extract the member nodes contained within the object tree
                    for (MemberNode memberNode : va.getObjectTree().nodeIterable()) {
                        MemberNode memberParent = memberNode.getParent();
                        assert memberParent == null || containsVertex(memberParent);
                        addVertex(memberNode);
                        addControlDependencyArc(memberParent == null ? parentNode : memberParent, memberNode);
                    }
                }
                assert callNodeStack.isEmpty();
            }
        }
    }
}
+18 −16
Original line number Diff line number Diff line
@@ -14,7 +14,6 @@ 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;

@@ -31,7 +30,7 @@ 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<?>, Map<A, ObjectTree>> {
public abstract class InterproceduralActionFinder<A extends VariableAction> extends BackwardDataFlowAnalysis<CallGraph.Vertex, CallGraph.Edge<?>, Set<A>> {
    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<>();
@@ -62,14 +61,14 @@ public abstract class InterproceduralActionFinder<A extends VariableAction> exte
        var actions = vertexDataMap.get(vertex);
        // Update stored action map
        actionStoredMap.computeIfAbsent(vertex, v -> new HashMap<>());
        for (A a : actions.keySet())
        for (A a : actions)
            actionStoredMap.get(vertex).computeIfAbsent(a, __ -> new StoredAction());
        // FORMAL: per declaration (1)
        for (A a : actions.keySet())
        for (A a : actions)
            getStored(vertex, a).storeFormal(() -> sandBoxedHandler(vertex, a, this::handleFormalAction));
        // ACTUAL: per call (n)
        for (CallGraph.Edge<?> edge : graph.incomingEdgesOf(vertex))
            actions.keySet().stream().sorted(new ParameterFieldSorter(edge)).forEach(a ->
            actions.stream().sorted(new ParameterFieldSorter(edge)).forEach(a ->
                    getStored(vertex, a).storeActual(edge, e -> sandBoxedHandler(e, a, this::handleActualAction)));
    }

@@ -157,18 +156,18 @@ public abstract class InterproceduralActionFinder<A extends VariableAction> exte
    // ===========================================================

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

    @Override
    protected Map<A, ObjectTree> initialValue(CallGraph.Vertex vertex) {
    protected Set<A> initialValue(CallGraph.Vertex vertex) {
        CFG cfg = cfgMap.get(vertex.getDeclaration());
        if (cfg == null)
            return Collections.emptyMap();
            return Collections.emptySet();
        Stream<VariableAction> actionStream =  cfg.vertexSet().stream()
                // Ignore root node, it is literally the entrypoint for interprocedural actions.
                .filter(n -> n != cfg.getRootNode())
@@ -178,15 +177,18 @@ public abstract class InterproceduralActionFinder<A extends VariableAction> exte
                // We skip over non-root variables (for each 'x.a' action we'll find 'x' later)
                .filter(VariableAction::isRootAction);
        Stream<A> filteredStream = mapAndFilterActionStream(actionStream, cfg);
        Map<A, ObjectTree> map = new HashMap<>();
        Set<A> set = new HashSet<>();
        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());
            if (set.contains(a)) {
                for (A aFromSet : set)
                    if (aFromSet.hashCode() == a.hashCode() && Objects.equals(aFromSet, a))
                        aFromSet.getObjectTree().addAll(a.getObjectTree());
            } else {
                set.add(a.createCopy());
            }
        }
        return map;
        return set;
    }

    /** Given a stream of VariableAction objects, map it to the finders' type and
+5 −8
Original line number Diff line number Diff line
@@ -11,7 +11,6 @@ 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;

@@ -28,14 +27,13 @@ public class InterproceduralDefinitionFinder extends InterproceduralActionFinder
    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(vertex.getDeclaration(), resolved);
            Movable movable = new Movable(def.toUsage(cfg.getExitNode(), objTree), formalOut);
            Movable movable = new Movable(def.toUsage(cfg.getExitNode()), formalOut);
            cfg.getExitNode().addMovableVariable(movable);
        }
        FormalIONode formalIn = FormalIONode.createFormalInDecl(vertex.getDeclaration(), resolved);
        cfg.getRootNode().addMovableVariable(new Movable(def.toDeclaration(cfg.getRootNode(), objTree), formalIn));
        cfg.getRootNode().addMovableVariable(new Movable(def.toDeclaration(cfg.getRootNode()), formalIn));
    }

    @Override
@@ -43,7 +41,6 @@ public class InterproceduralDefinitionFinder extends InterproceduralActionFinder
        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)
@@ -53,9 +50,9 @@ public class InterproceduralDefinitionFinder extends InterproceduralActionFinder
                Set<NameExpr> exprSet = new HashSet<>();
                arg.accept(new OutNodeVariableVisitor(), exprSet);
                for (NameExpr nameExpr : exprSet)
                    movables.add(new Movable(new Definition(nameExpr, nameExpr.toString(), graphNode, objTree), actualOut));
                    movables.add(new Movable(new Definition(nameExpr, nameExpr.toString(), graphNode, def.getObjectTree()), actualOut));
            } else {
                movables.add(new Movable(def.toDefinition(graphNode, objTree), actualOut));
                movables.add(new Movable(def.toDefinition(graphNode), actualOut));
            }
        } else if (resolved.isField()) {
            // Known limitation: static fields
@@ -65,7 +62,7 @@ public class InterproceduralDefinitionFinder extends InterproceduralActionFinder
                return;
            String aliasedName = obtainAliasedFieldName(def, edge);
            ActualIONode actualOut = ActualIONode.createActualOut(edge.getCall(), resolved, null);
            var movableDef  = new Definition(obtainScope(edge.getCall()), aliasedName, graphNode, objTree);
            var movableDef  = new Definition(obtainScope(edge.getCall()), aliasedName, graphNode, def.getObjectTree());
            movables.add(new Movable(movableDef, actualOut));
        } else {
            throw new IllegalStateException("Definition must be either from a parameter or a field!");
+7 −6
Original line number Diff line number Diff line
@@ -8,7 +8,10 @@ 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.VariableAction.Declaration;
import es.upv.mist.slicing.nodes.VariableAction.Definition;
import es.upv.mist.slicing.nodes.VariableAction.Movable;
import es.upv.mist.slicing.nodes.VariableAction.Usage;
import es.upv.mist.slicing.nodes.VariableVisitor;
import es.upv.mist.slicing.nodes.io.ActualIONode;
import es.upv.mist.slicing.nodes.io.FormalIONode;
@@ -30,8 +33,7 @@ public class InterproceduralUsageFinder extends InterproceduralActionFinder<Usag
        CFG cfg = cfgMap.get(vertex.getDeclaration());
        ResolvedValueDeclaration resolved = use.getResolvedValueDeclaration();
        FormalIONode formalIn = FormalIONode.createFormalIn(vertex.getDeclaration(), resolved);
        ObjectTree objTree = vertexDataMap.get(vertex).get(use);
        Movable movable = new Movable(use.toDefinition(cfg.getRootNode(), objTree), formalIn);
        Movable movable = new Movable(use.toDefinition(cfg.getRootNode()), formalIn);
        cfg.getRootNode().addMovableVariable(movable);
    }

@@ -40,7 +42,6 @@ public class InterproceduralUsageFinder extends InterproceduralActionFinder<Usag
        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);
@@ -57,7 +58,7 @@ public class InterproceduralUsageFinder extends InterproceduralActionFinder<Usag
            // 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);
                movables.get(0).getObjectTree().addAll(use.getObjectTree());
        } else if (resolved.isField()) {
            // Known limitation: static fields
            // An object creation expression input an existing object via actual-in because it creates it.
@@ -65,7 +66,7 @@ public class InterproceduralUsageFinder extends InterproceduralActionFinder<Usag
                return;
            String aliasedName = obtainAliasedFieldName(use, edge);
            ActualIONode actualIn = ActualIONode.createActualIn(edge.getCall(), resolved, null);
            var movableUse = new Usage(obtainScope(edge.getCall()), aliasedName, graphNode, objTree);
            var movableUse = new Usage(obtainScope(edge.getCall()), aliasedName, graphNode, use.getObjectTree());
            movables.add(new Movable(movableUse, actualIn));
        } else {
            throw new IllegalStateException("Definition must be either from a parameter or a field!");
+191 −22
Original line number Diff line number Diff line
@@ -10,12 +10,11 @@ import es.upv.mist.slicing.arcs.Arc;
import es.upv.mist.slicing.arcs.pdg.DataDependencyArc;
import es.upv.mist.slicing.graphs.Graph;
import es.upv.mist.slicing.graphs.pdg.PDG;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.SimpleDirectedGraph;
import es.upv.mist.slicing.nodes.oo.MemberNode;
import es.upv.mist.slicing.utils.Utils;

import java.lang.reflect.InvocationTargetException;
import java.util.Objects;
import java.util.Set;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -208,18 +207,33 @@ public abstract class VariableAction {
    }

    /** Creates a new usage action with the same variable and the given node. */
    public final Usage toUsage(GraphNode<?> graphNode, ObjectTree objectTree) {
        return new Usage(variable, realName, graphNode, objectTree);
    public final Usage toUsage(GraphNode<?> graphNode) {
        return new Usage(variable, realName, graphNode, (ObjectTree) getObjectTree().clone());
    }

    /** Creates a new definition action with the same variable and the given node. */
    public final Definition toDefinition(GraphNode<?> graphNode, ObjectTree objectTree) {
        return new Definition(variable, realName, graphNode, objectTree);
    public final Definition toDefinition(GraphNode<?> graphNode) {
        return new Definition(variable, realName, graphNode, (ObjectTree) getObjectTree().clone());
    }

    /** Creates a new declaration action with the same variable and the given node. */
    public final Declaration toDeclaration(GraphNode<?> graphNode, ObjectTree objectTree) {
        return new Declaration(variable, realName, graphNode, objectTree);
    public final Declaration toDeclaration(GraphNode<?> graphNode) {
        return new Declaration(variable, realName, graphNode, (ObjectTree) getObjectTree().clone());
    }

    @SuppressWarnings("unchecked")
    public final <A extends VariableAction> A createCopy() {
        if (this instanceof Usage)
            return (A) toUsage(null);
        if (this instanceof Definition)
            return (A) toDefinition(null);
        if (this instanceof Declaration)
            return (A) toDeclaration(null);
        if (this instanceof Movable) {
            Movable m = (Movable) this;
            return (A) new Movable(m.inner.createCopy(), null);
        }
        throw new IllegalStateException("This kind of variable action can't be copied");
    }

    @Override
@@ -359,6 +373,10 @@ public abstract class VariableAction {
            this.inner = inner;
        }

        public ObjectTree getObjectTree() {
            return inner.getObjectTree();
        }

        @Override
        public void addObjectField(String fieldName) {
            throw new UnsupportedOperationException("Movable actions don't support the object tree");
@@ -458,27 +476,178 @@ public abstract class VariableAction {
        }
    }

    public static class ObjectTree extends SimpleDirectedGraph<String, DefaultEdge> {
        private static final String ROOT = "-root-";
    public static class ObjectTree implements Cloneable {
        private static final Pattern FIELD_SPLIT = Pattern.compile("^(?<root>(([_0-9A-Za-z]+\\.)*this)|([_0-9A-Za-z]+))(\\.(?<fields>.+))?$");

        private final String memberName;
        private final MemberNode memberNode;
        private final Map<String, ObjectTree> childrenMap = new HashMap<>();

        public ObjectTree() {
            super(null, DefaultEdge::new, false);
            addVertex(ROOT);
            memberName = null;
            memberNode = null;
        }

        private ObjectTree(String memberName, ObjectTree parent) {
            this.memberName = memberName;
            this.memberNode = new MemberNode(memberName, parent.memberNode);
        }

        public void addField(String fieldName) {
            String parent = ROOT;
            String[] splitField = fieldName.split("\\.");
            for (int i = 1; i < splitField.length; i++) {
                addVertex(splitField[i]);
                addEdge(parent, splitField[i]);
                parent = splitField[i];
            String members = removeRoot(fieldName);
            addNonRootField(members);
        }

        private void addNonRootField(String members) {
            if (members.contains(".")) {
                int firstDot = members.indexOf('.');
                String first = members.substring(0, firstDot);
                String rest = members.substring(firstDot + 1);
                childrenMap.computeIfAbsent(first, f -> new ObjectTree(f, this));
                childrenMap.get(first).addNonRootField(rest);
            } else {
                childrenMap.computeIfAbsent(members, f -> new ObjectTree(f, this));
            }
        }

        public void addAll(ObjectTree tree) {
            tree.vertexSet().forEach(this::addVertex);
            tree.edgeSet().forEach(e -> addEdge(tree.getEdgeSource(e), tree.getEdgeTarget(e)));
            for (Map.Entry<String, ObjectTree> entry : tree.childrenMap.entrySet())
                if (childrenMap.containsKey(entry.getKey()))
                    childrenMap.get(entry.getKey()).addAll(entry.getValue());
                else
                    childrenMap.put(entry.getKey(), entry.getValue().clone(this));
        }

        public boolean hasMember(String member) {
            String field = removeRoot(member);
            return hasNonRootMember(field);
        }

        private boolean hasNonRootMember(String members) {
            if (members.contains(".")) {
                int firstDot = members.indexOf('.');
                String first = members.substring(0, firstDot);
                String rest = members.substring(firstDot + 1);
                return childrenMap.containsKey(first) && childrenMap.get(first).hasNonRootMember(rest);
            } else {
                return childrenMap.containsKey(members);
            }
        }

        public MemberNode getNodeFor(String member) {
            String field = removeRoot(member);
            return getNodeForNonRoot(field);
        }

        private MemberNode getNodeForNonRoot(String members) {
            if (members.contains(".")) {
                int firstDot = members.indexOf('.');
                String first = members.substring(0, firstDot);
                String rest = members.substring(firstDot + 1);
                assert childrenMap.containsKey(first);
                return childrenMap.get(first).getNodeForNonRoot(rest);
            } else {
                assert childrenMap.containsKey(members);
                return childrenMap.get(members).memberNode;
            }
        }

        public boolean isEmpty() {
            return childrenMap.isEmpty();
        }

        public Iterable<String> nameIterable() {
            return () -> new Iterator<>() {
                final Iterator<ObjectTree> it = treeIterator();

                @Override
                public boolean hasNext() {
                    return it.hasNext();
                }

                @Override
                public String next() {
                    return it.next().memberName;
                }
            };
        }

        public Iterable<MemberNode> nodeIterable() {
            return () -> new Iterator<>() {
                final Iterator<ObjectTree> it = treeIterator();

                @Override
                public boolean hasNext() {
                    return it.hasNext();
                }

                @Override
                public MemberNode next() {
                    return it.next().memberNode;
                }
            };
        }

        private Iterator<ObjectTree> treeIterator() {
            return new Iterator<>() {
                final Set<ObjectTree> remaining = new HashSet<>(childrenMap.values());
                Iterator<ObjectTree> childIterator = null;

                @Override
                public boolean hasNext() {
                    if (childIterator == null || !childIterator.hasNext())
                        return !remaining.isEmpty();
                    else
                        return true;
                }

                @Override
                public ObjectTree next() {
                    if (childIterator == null || !childIterator.hasNext()) {
                        ObjectTree tree = Utils.setPop(remaining);
                        childIterator = tree.treeIterator();
                        return tree;
                    } else {
                        return childIterator.next();
                    }
                }
            };
        }

        @Override
        public Object clone() {
            ObjectTree clone = new ObjectTree();
            for (Map.Entry<String, ObjectTree> entry : childrenMap.entrySet())
                clone.childrenMap.put(entry.getKey(), entry.getValue().clone(clone));
            return clone;
        }

        private ObjectTree clone(ObjectTree parent) {
            ObjectTree clone = new ObjectTree(memberName, parent);
            for (Map.Entry<String, ObjectTree> entry : childrenMap.entrySet())
                clone.childrenMap.put(entry.getKey(), entry.getValue().clone(clone));
            return clone;
        }

        protected String removeRoot(String fieldWithRoot) {
            Matcher matcher = FIELD_SPLIT.matcher(fieldWithRoot);
            if (matcher.matches() && matcher.group("fields") != null)
                return matcher.group("fields");
            throw new IllegalArgumentException("Field should be of the form <obj>.<field>, <Type>.this.<field>, where <obj> may not contain dots.");
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            ObjectTree tree = (ObjectTree) o;
            return Objects.equals(memberName, tree.memberName) &&
                    childrenMap.values().equals(tree.childrenMap.values());
        }

        @Override
        public int hashCode() {
            return Objects.hash(memberName, childrenMap);
        }
    }
}
Loading