diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ClassGraph.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ClassGraph.java index 726e0b47b93152cba5a54644791053b85af52ff6..58589b6a6eee5d75bb327d327c909a8c09a6c325 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ClassGraph.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ClassGraph.java @@ -19,7 +19,7 @@ import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; -public class ClassGraph extends DirectedPseudograph implements Buildable> { +public class ClassGraph extends DirectedPseudograph, ClassGraph.ClassArc> implements Buildable> { private static ClassGraph instance = null; /** Generates and returns a new class graph. This destroys the reference to the previous instance. */ @@ -34,10 +34,12 @@ public class ClassGraph extends DirectedPseudograph vertexDeclarationMap = new HashMap<>(); + /** A map from the FQ class name to its corresponding vertex. Use {@code mapKey(...)} to locate the key. */ + private final Map> classDeclarationMap = new HashMap<>(); + /** A map from the field name to its corresponding vertex. Use {@code mapKey(...)} to locate the key. */ + private final Map> fieldDeclarationMap = new HashMap<>(); + /** A map from the method's signature to its corresponding vertex. Use {@code mapKey(...)} to locate the key. */ + private final Map>> methodDeclarationMap = new HashMap<>(); private boolean built = false; @@ -46,17 +48,19 @@ public class ClassGraph extends DirectedPseudograph findClassVertex(ClassOrInterfaceDeclaration declaration) { + return classDeclarationMap.get(mapKey(declaration)); } - protected Vertex findMethodVertex(CallableDeclaration declaration) { - return vertexDeclarationMap.get(mapKey(declaration, ASTUtils.getClassNode(declaration))); + /** Whether this graph contains the given type as a vertex. */ + public boolean containsType(ResolvedType type) { + return type.isReferenceType() && classDeclarationMap.containsKey(mapKey(type.asReferenceType())); } + /** Set of method declarations that override the given argument. */ public Set overriddenSetOf(MethodDeclaration method) { - return subclassesStreamOf(classVertexOf(findMethodVertex(method))) + return subclassesStreamOf(findClassVertex(method.findAncestor(ClassOrInterfaceDeclaration.class).orElseThrow())) .flatMap(vertex -> outgoingEdgesOf(vertex).stream() .filter(ClassArc.Member.class::isInstance) .map(ClassGraph.this::getEdgeTarget) @@ -66,14 +70,24 @@ public class ClassGraph extends DirectedPseudograph findClassField(ResolvedType resolvedType, String fieldName) { + return Optional.ofNullable(classDeclarationMap.get(mapKey(resolvedType.asReferenceType()))) + .flatMap(v -> findClassField(v, fieldName)); + } + + /** @see #findClassField(ResolvedType,String) */ + @SuppressWarnings("unchecked") + public Optional findClassField(Vertex vertex, String fieldName) { + var field = vertex.getDeclaration().getFieldByName(fieldName); + if (field.isPresent()) + return field; + return incomingEdgesOf(vertex).stream() + .filter(ClassArc.Extends.class::isInstance) .map(this::getEdgeSource) - .findFirst().orElseThrow(); + .map(v -> (Vertex) v) + .findAny() + .flatMap(parent -> findClassField(parent, fieldName)); } /** Returns all child classes of the given class, including itself. */ @@ -83,25 +97,27 @@ public class ClassGraph extends DirectedPseudograph subclassesOf(ResolvedClassDeclaration clazz) { - return subclassesOf(vertexDeclarationMap.get(mapKey(clazz))); + return subclassesOf(classDeclarationMap.get(mapKey(clazz))); } public Set subclassesOf(ResolvedReferenceType type) { - return subclassesOf(vertexDeclarationMap.get(mapKey(type))); + return subclassesOf(classDeclarationMap.get(mapKey(type))); } /** @see #subclassesOf(ClassOrInterfaceDeclaration) */ - protected Set subclassesOf(Vertex v) { + protected Set subclassesOf(Vertex v) { return subclassesStreamOf(v) .map(Vertex::getDeclaration) .map(ClassOrInterfaceDeclaration.class::cast) .collect(Collectors.toSet()); } - protected Stream subclassesStreamOf(Vertex classVertex) { + @SuppressWarnings("unchecked") + protected Stream> subclassesStreamOf(Vertex classVertex) { return Stream.concat(Stream.of(classVertex), outgoingEdgesOf(classVertex).stream() .filter(ClassArc.Extends.class::isInstance) .map(this::getEdgeTarget) + .map(v -> (Vertex) v) .flatMap(this::subclassesStreamOf)); } @@ -109,7 +125,7 @@ public class ClassGraph extends DirectedPseudograph declaration) { - Vertex v = vertexDeclarationMap.get(mapKey(declaration, type)); + Vertex> v = methodDeclarationMap.get(mapKey(declaration, type)); if (v != null && v.declaration.isMethodDeclaration()) return v.declaration.asMethodDeclaration(); Optional parentType = parentOf(type); @@ -149,7 +165,7 @@ public class ClassGraph extends DirectedPseudograph generateObjectTreeForType(ResolvedType type) { if (type.isReferenceType()) { - Vertex v = vertexDeclarationMap.get(mapKey(type.asReferenceType())); + Vertex v = classDeclarationMap.get(mapKey(type.asReferenceType())); if (v != null) return Optional.of(generateObjectTreeFor(v)); } @@ -157,43 +173,57 @@ public class ClassGraph extends DirectedPseudograph classVertex) { if (classVertex == null) return new ObjectTree(); - return generateObjectTreeFor(classVertex, new ObjectTree(), ObjectTree.ROOT_NAME); + return generatePolyObjectTreeFor(classVertex, new ObjectTree(), ObjectTree.ROOT_NAME); } - protected ObjectTree generateObjectTreeFor(Vertex classVertex, ObjectTree tree, String level) { - Map classFields = findAllFieldsOf(classVertex); - for (Map.Entry entry : classFields.entrySet()) { + protected ObjectTree generatePolyObjectTreeFor(Vertex classVertex, ObjectTree tree, String level) { + Set types = subclassesOf(classVertex); + if (types.isEmpty()) { + generateObjectTreeFor(classVertex, tree, level); + } else { + for (ClassOrInterfaceDeclaration type : types) { + Vertex subclassVertex = classDeclarationMap.get(mapKey(type)); + if (!findAllFieldsOf(subclassVertex).isEmpty()) { + ObjectTree newType = tree.addType(ASTUtils.resolvedTypeDeclarationToResolvedType(type.resolve())); + generateObjectTreeFor(subclassVertex, tree, level + '.' + newType.getMemberNode().getLabel()); + } + } + } + return tree; + } + + protected void generateObjectTreeFor(Vertex classVertex, ObjectTree tree, String level) { + Map> classFields = findAllFieldsOf(classVertex); + for (var entry : classFields.entrySet()) { tree.addField(level + '.' + entry.getKey()); if (entry.getValue() != null) - generateObjectTreeFor(entry.getValue(), tree, level + '.' + entry.getKey()); + generatePolyObjectTreeFor(entry.getValue(), tree, level + '.' + entry.getKey()); } - return tree; } - protected Map findAllFieldsOf(Vertex classVertex) { - assert classVertex.declaration instanceof ClassOrInterfaceDeclaration; + protected Map> findAllFieldsOf(Vertex classVertex) { assert !classVertex.declaration.asClassOrInterfaceDeclaration().isInterface(); ClassOrInterfaceDeclaration clazz = classVertex.getDeclaration().asClassOrInterfaceDeclaration(); - Map fieldMap = new HashMap<>(); + Map> fieldMap = new HashMap<>(); while (clazz != null) { for (FieldDeclaration field : clazz.getFields()) { for (VariableDeclarator var : field.getVariables()) { if (fieldMap.containsKey(var.getNameAsString())) continue; - Vertex v = null; + Vertex v = null; if (var.getType().isClassOrInterfaceType()) { try { - v = vertexDeclarationMap.get(mapKey(var.getType().asClassOrInterfaceType().resolve())); + v = classDeclarationMap.get(mapKey(var.getType().asClassOrInterfaceType().resolve())); } catch (UnsolvedSymbolException ignored) { } } @@ -282,24 +312,24 @@ public class ClassGraph extends DirectedPseudograph v = new ClassGraph.Vertex<>(n); // Required string to match ClassOrInterfaceType and ClassOrInterfaceDeclaration. QualifiedName Not Valid - vertexDeclarationMap.put(mapKey(n), v); + classDeclarationMap.put(mapKey(n), v); addVertex(v); } /** Add a field declaration vertex to the class graph */ protected void addFieldDeclaration(FieldDeclaration n, ClassOrInterfaceDeclaration c){ - ClassGraph.Vertex v = new ClassGraph.Vertex(n); - vertexDeclarationMap.put(mapKey(n, c), v); + ClassGraph.Vertex v = new ClassGraph.Vertex<>(n); + fieldDeclarationMap.put(mapKey(n, c), v); addVertex(v); } /** Add a method/constructor declaration vertex to the class graph */ protected void addCallableDeclaration(CallableDeclaration n, ClassOrInterfaceDeclaration c){ assert n instanceof ConstructorDeclaration || n instanceof MethodDeclaration; - ClassGraph.Vertex v = new ClassGraph.Vertex(n); - vertexDeclarationMap.put(mapKey(n, c), v); + ClassGraph.Vertex> v = new ClassGraph.Vertex<>(n); + methodDeclarationMap.put(mapKey(n, c), v); addVertex(v); } @@ -312,7 +342,7 @@ public class ClassGraph extends DirectedPseudograph v = classDeclarationMap.get(mapKey(n)); addClassEdges(v); super.visit(n, arg); classStack.pop(); @@ -322,8 +352,8 @@ public class ClassGraph extends DirectedPseudograph c = classDeclarationMap.get(mapKey(clazz)); + Vertex v = fieldDeclarationMap.get(mapKey(n, clazz)); addEdge(c, v, new ClassArc.Member()); } @@ -331,8 +361,8 @@ public class ClassGraph extends DirectedPseudograph c = classDeclarationMap.get(mapKey(clazz)); + Vertex> v = methodDeclarationMap.get(mapKey(n, clazz)); addEdge(c, v, new ClassArc.Member()); } @@ -340,31 +370,29 @@ public class ClassGraph extends DirectedPseudograph c = classDeclarationMap.get(mapKey(clazz)); + Vertex> v = methodDeclarationMap.get(mapKey(n, clazz)); addEdge(c, v, new ClassArc.Member()); } }, null); } - protected void addClassEdges(Vertex v){ - assert v.declaration instanceof ClassOrInterfaceDeclaration; - ClassOrInterfaceDeclaration dv = (ClassOrInterfaceDeclaration) v.declaration; - dv.getExtendedTypes().forEach(p -> { - Vertex source = vertexDeclarationMap.get(mapKey(p.resolve())); + protected void addClassEdges(Vertex v) { + v.declaration.getExtendedTypes().forEach(p -> { + Vertex source = classDeclarationMap.get(mapKey(p.resolve())); if (source != null && containsVertex(v)) addEdge(source, v, new ClassArc.Extends()); }); - dv.getImplementedTypes().forEach(p -> { - Vertex source = vertexDeclarationMap.get(mapKey(p.resolve())); + v.declaration.getImplementedTypes().forEach(p -> { + Vertex source = classDeclarationMap.get(mapKey(p.resolve())); if (source != null && containsVertex(v)) addEdge(source, v, new ClassArc.Implements()); }); } /** Creates a graph-appropriate DOT exporter. */ - public DOTExporter getDOTExporter() { - DOTExporter dot = new DOTExporter<>(); + public DOTExporter, ClassGraph.ClassArc> getDOTExporter() { + DOTExporter, ClassGraph.ClassArc> dot = new DOTExporter<>(); dot.setVertexAttributeProvider(vertex -> Utils.dotLabel(vertex.declaration.toString().replaceAll("\\{.*}", ""))); dot.setEdgeAttributeProvider(edge -> Utils.dotLabel(edge.getClass().getSimpleName())); return dot; @@ -372,17 +400,17 @@ public class ClassGraph extends DirectedPseudograph> { // First ancestor common class in the JavaParser hierarchy for // ClassOrInterfaceDeclaration, FieldDeclaration and CallableDeclaration - protected final BodyDeclaration declaration; + protected final T declaration; - public Vertex(BodyDeclaration declaration) { + public Vertex(T declaration) { this.declaration = declaration; } /** The declaration represented by this node. */ - public BodyDeclaration getDeclaration() { + public T getDeclaration() { return declaration; } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ExpressionObjectTreeFinder.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ExpressionObjectTreeFinder.java index 2d4fb0fe9ba0bcfc9bda0767ffeb4816365a8b10..456f2b407d3120a774c2f8df71a85fb8f122518e 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ExpressionObjectTreeFinder.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ExpressionObjectTreeFinder.java @@ -81,7 +81,7 @@ public class ExpressionObjectTreeFinder { else if (foundDecl) return lastDef; } else if (a.isDefinition() && a.getName().equals(root)) { - if (root.equals(realName) || a.hasTreeMember(realName)) + if (root.equals(realName) || a.hasPolyTreeMember(realName)) lastDef = a; } } @@ -196,14 +196,14 @@ public class ExpressionObjectTreeFinder { visitCall(n, arg); } - protected void visitCall(Expression call, String arg) { - if (ASTUtils.shouldVisitArgumentsForMethodCalls((Resolvable) call)) + protected void visitCall(Resolvable call, String arg) { + if (ASTUtils.shouldVisitArgumentsForMethodCalls(call)) return; VariableAction lastUseOut = null; for (VariableAction variableAction : graphNode.getVariableActions()) { if (variableAction instanceof VariableAction.CallMarker) { VariableAction.CallMarker marker = (VariableAction.CallMarker) variableAction; - if (ASTUtils.equalsWithRange((Node) marker.getCall(), call) && !marker.isEnter()) { + if (ASTUtils.equalsWithRange((Node) marker.getCall(), (Node) call) && !marker.isEnter()) { assert lastUseOut != null; list.add(new Pair<>(lastUseOut, arg)); return; @@ -252,7 +252,10 @@ public class ExpressionObjectTreeFinder { VariableAction sourceAction = sourcePair.a; String sourceMember = sourcePair.b; if (targetAction.hasObjectTree()) { - ObjectTree.copyTargetTreeToSource(sourceAction.getObjectTree(), targetAction.getObjectTree(), sourceMember, targetMember); + boolean sourceTypesInClassGraph = sourceAction.getDynamicTypes().stream() + .anyMatch(ClassGraph.getInstance()::containsType); + if (sourceTypesInClassGraph && !sourceAction.hasObjectTree()) + ObjectTree.copyTargetTreeToSource(sourceAction.getObjectTree(), targetAction.getObjectTree(), sourceMember, targetMember); sourceAction.setPDGTreeConnectionTo(targetAction, sourceMember, targetMember); } else { sourceAction.setPDGValueConnection(sourceMember); diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCFG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCFG.java index c7f4a3695db29a3819217681be99ea2a6e13f2a9..fad38999077254ef2b87380a1ff34578d1e54f26 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCFG.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCFG.java @@ -77,7 +77,7 @@ public class JSysCFG extends ESCFG { /** Given a field declaration, locate all definitions that affect the given member. */ public List findAllFutureObjectDefinitionsFor(VariableAction action) { List list = new LinkedList<>(); - Predicate filter = a -> a.isDefinition() && a.getName().equals("this") && a.hasTreeMember(action.getName()); + Predicate filter = a -> a.isDefinition() && a.getName().equals("this") && a.hasPolyTreeMember(action.getName()); findAllFutureVarActionsFor(new HashSet<>(), list, action.getGraphNode(), action, filter); return list; } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysPDG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysPDG.java index 51a5f1d316e914132860f07a9d3c0dadccfc4da3..bb75c9a06e3b8c25480aa7c06d552cc209ed9d94 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysPDG.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysPDG.java @@ -54,8 +54,8 @@ public class JSysPDG extends ESPDG { } protected void addDeclarationFlowDependencyArc(VariableAction declaration, VariableAction definition) { - MemberNode defMember = definition.getObjectTree().getNodeFor(declaration.getName()); - addEdge(graphNodeOf(declaration), defMember, new FlowDependencyArc()); + for (MemberNode target : definition.getObjectTree().getNodesForPoly(declaration.getName())) + addEdge(graphNodeOf(declaration), target, new FlowDependencyArc()); } // definicion de miembro --flow--> uso de miembro diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/SummaryArcAnalyzer.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/SummaryArcAnalyzer.java index faa86740b7b527efc4df40df170507805b28a691..55f06bd94191edd3c3a35b1ec6e2ee69491eaab5 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/SummaryArcAnalyzer.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/SummaryArcAnalyzer.java @@ -31,11 +31,8 @@ public class SummaryArcAnalyzer extends AbstractSummaryArcAnalyzer(([_0-9A-Za-z]+\\.)*this)|([_0-9A-Za-z]+)|(-root-))(\\.(?.+))?$"); + private static final Pattern FIELD_SPLIT = Pattern.compile("^(?(([_0-9A-Za-z]+\\.)*this)|([_0-9A-Za-z]+)|(" + ROOT_NAME + ")|(" + VARIABLE_NAME_OUTPUT + ")|(" + ACTIVE_EXCEPTION_VARIABLE + "))(\\.(?.+))?$"); /** Direct children of this tree node, mapped by field name. */ private final Map childrenMap = new HashMap<>(); @@ -36,12 +43,22 @@ public class ObjectTree implements Cloneable { /** Create a root of a new object tree with the given name. */ public ObjectTree(String memberName) { - memberNode = new MemberNode(memberName, null); + this(new MemberNode(memberName, null)); } /** Create a child tree node for the given field, whose node is linked to the given parent. */ private ObjectTree(String memberName, ObjectTree parent) { - this.memberNode = new MemberNode(memberName, parent.memberNode); + this(new MemberNode(memberName, parent.memberNode)); + } + + /** Create a child tree node for the given type, whose node is linked to the given parent. */ + private ObjectTree(ResolvedType resolvedType, ObjectTree parent) { + this(new PolyMemberNode(resolvedType, parent.memberNode)); + } + + /** Create a child tree with the given member node. */ + private ObjectTree(MemberNode memberNode) { + this.memberNode = memberNode; } /** The name of the variable or field represented by this tree. It doesn't include ancestors. */ @@ -58,6 +75,44 @@ public class ObjectTree implements Cloneable { return !childrenMap.isEmpty(); } + /** Whether the field passed as argument has children. */ + public boolean hasChildren(String memberWithRoot) { + String member = removeRoot(memberWithRoot); + if (member.isEmpty()) + return hasChildren(); + return hasChildrenInternal(member); + } + + protected boolean hasChildrenInternal(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)); + return childrenMap.get(first).hasChildrenInternal(rest); + } else { + return childrenMap.get(members).hasChildren(); + } + } + + /** Whether this object tree immediately contains polymorphic nodes. */ + public boolean hasPoly() { + return childrenMap.values().stream().anyMatch(ot -> ot.getMemberNode() instanceof PolyMemberNode); + } + + /** A set of entry pairs, containing the field name and its corresponding tree. It is unmodifiable. */ + public Set> entrySet() { + return Collections.unmodifiableSet(childrenMap.entrySet()); + } + + /** Insert a polymorphic node for the given type. The type node will be + * generated immediately beneath this tree node. */ + public ObjectTree addType(ResolvedType rt) { + assert !rt.describe().isBlank(); + assert !(memberNode instanceof PolyMemberNode); + return childrenMap.computeIfAbsent(rt.describe(), n -> new ObjectTree(rt, this)); + } + /** * Insert a field with the given name. This method should only be called on a root object tree. * This method may be used to add multiple levels simultaneously, calling this method with @@ -66,22 +121,30 @@ public class ObjectTree implements Cloneable { * @param fieldName The field to be added, should include the root variable name. For example, * to add the field "x" to a variable "a", this argument should be "a.x". */ - public void addField(String fieldName) { + public ObjectTree addField(String fieldName) { String members = removeRoot(fieldName); - addNonRootField(members); + return addNonRootField(members); + } + + /** Insert a field in the current level of object tree. The field should be a variable name, + * and not contain dots or be blank. */ + public ObjectTree addImmediateField(String fieldName) { + if (fieldName.contains(".") || fieldName.isBlank()) + throw new IllegalArgumentException("field name must not include dots or be blank!"); + return childrenMap.computeIfAbsent(fieldName, f -> new ObjectTree(f, this)); } /** Similar to {@link #addField(String)}, but may be called at any level * and the argument must not contain the root variable. */ - private void addNonRootField(String members) { + private ObjectTree 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); + return childrenMap.get(first).addNonRootField(rest); } else { - childrenMap.computeIfAbsent(members, f -> new ObjectTree(f, this)); + return childrenMap.computeIfAbsent(members, f -> new ObjectTree(f, this)); } } @@ -96,25 +159,39 @@ public class ObjectTree implements Cloneable { } /** - * Copies a subtree from source into another subtree in target. - * + * Copies a subtree from source into another subtree in target. The tree may be + * pasted multiple times, if there are polymorphic nodes that are not explicitly marked + * in the prefix arguments. * @param source The source of the nodes. * @param target The tree where nodes will be added * @param sourcePrefix The prefix to be consumed before copying nodes. Without root. * @param targetPrefix The prefix to be consumed before copying nodes. Without root. */ public static void copyTargetTreeToSource(ObjectTree source, ObjectTree target, String sourcePrefix, String targetPrefix) { - ObjectTree a = source.findObjectTreeOfMember(sourcePrefix); - ObjectTree b = target.findObjectTreeOfMember(targetPrefix); - a.addAll(b); + Collection a = source.findObjectTreeOfPolyMember(sourcePrefix); + Collection b = target.findObjectTreeOfPolyMember(targetPrefix); + for (ObjectTree sourceTree : a) + for (ObjectTree targetTree : b) + sourceTree.addAll(targetTree); } - /** - * Locate an object tree that represents a field of this object. - * @param member The field, without a root prefix. - */ - ObjectTree findObjectTreeOfMember(String member) { - ObjectTree result = this; + /** Obtains the set of nodes in this object tree that have no children. */ + public Collection leaves() { + return streamLeaves().collect(Collectors.toSet()); + } + + /** @see #leaves() */ + protected Stream streamLeaves() { + if (childrenMap.isEmpty()) + return Stream.of(memberNode); + return childrenMap.values().stream() + .flatMap(ObjectTree::streamLeaves); + } + + /** Similar to {@link #getNodesForPoly(String)}, but returns object trees + * instead of member nodes. */ + Collection findObjectTreeOfPolyMember(String member) { + Collection result = List.of(this); while (!member.isEmpty()) { int firstDot = member.indexOf('.'); String first, rest; @@ -125,7 +202,22 @@ public class ObjectTree implements Cloneable { first = member; rest = ""; } - result = result.childrenMap.get(first); + result = result.stream().flatMap(res -> { + ObjectTree ot = res.childrenMap.get(first); + if (ot == null && res.childrenMap.size() > 0) { + Collection collection = new LinkedList<>(); + for (ObjectTree child : childrenMap.values()) { + if (!(child.getMemberNode() instanceof PolyMemberNode) || !child.childrenMap.containsKey(first)) + throw new IllegalArgumentException("Could not locate member in object tree"); + collection.add(child.childrenMap.get(first)); + } + return collection.stream(); + } else if (ot == null) { + throw new IllegalArgumentException("Could not locate member in object tree"); + } else { + return Stream.of(ot); + } + }).collect(Collectors.toList()); member = rest; } return result; @@ -134,19 +226,34 @@ public class ObjectTree implements Cloneable { /** Whether this object tree contains the given member. The argument should contain the root variable name. */ public boolean hasMember(String member) { String field = removeRoot(member); - return hasNonRootMember(field); + return hasNonRootMember(field, false); + } + + /** Whether this object tree contains the given member. The argument may omit typing + * information (i.e., 'a.x' will find 'a.A.x', where A is a polymorphic node). */ + public boolean hasPolyMember(String member) { + String field = removeRoot(member); + return hasNonRootMember(field, true); } /** Similar to hasMember, but valid at any level of the tree and the argument should not contain * the root variable's name. * @see #hasMember(String) */ - private boolean hasNonRootMember(String members) { + private boolean hasNonRootMember(String members, boolean polymorphic) { 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); + if (polymorphic && !childrenMap.containsKey(first) && !childrenMap.isEmpty()) + return childrenMap.values().stream() + .filter(ot -> ot.getMemberNode() instanceof PolyMemberNode) + .anyMatch(ot -> ot.hasNonRootMember(members, true)); + return childrenMap.containsKey(first) && childrenMap.get(first).hasNonRootMember(rest, polymorphic); } else { + if (polymorphic && !childrenMap.containsKey(members) && !childrenMap.isEmpty()) + return childrenMap.values().stream() + .filter(ot -> ot.getMemberNode() instanceof PolyMemberNode) + .anyMatch(ot -> ot.hasNonRootMember(members, true)); return childrenMap.containsKey(members); } } @@ -174,6 +281,16 @@ public class ObjectTree implements Cloneable { } } + /** Similar to {@link #getNodeFor(String)}, but if the argument does not contain + * types, it will obtain all member nodes that represent a given field (in multiple + * types). For example, the argument 'a.x' may produce 'a.A.x' and 'a.B.x'; whereas + * the argument 'a.A.x' will only produce one node. */ + public Collection getNodesForPoly(String memberWithRoot) { + return findObjectTreeOfPolyMember(removeRoot(memberWithRoot)).stream() + .map(ObjectTree::getMemberNode) + .collect(Collectors.toList()); + } + /** @return An iterable through the names (with full prefixes) of all members of this tree, * excluding the root. */ public Iterable nameIterable() { @@ -192,14 +309,16 @@ public class ObjectTree implements Cloneable { MemberNode node = element.memberNode; if (node == null) return ROOT_NAME; + else if (node instanceof PolyMemberNode) + return next(); else builder.append(node.getLabel()); - while (node.getParent() instanceof MemberNode && node.getParent().getLabel().matches("^(USE|DEF|DEC)\\{")) { + while (node.getParent() instanceof MemberNode) { node = (MemberNode) node.getParent(); builder.insert(0, '.'); builder.insert(0, node.getLabel()); } - return builder.insert(0, ROOT_NAME + ".").toString(); + return builder.toString(); } }; } @@ -248,11 +367,6 @@ public class ObjectTree implements Cloneable { }; } - /** @see #treeIterator() */ - Iterable treeIterable() { - return this::treeIterator; - } - @SuppressWarnings("MethodDoesntCallSuperMethod") @Override public Object clone() { @@ -263,7 +377,7 @@ public class ObjectTree implements Cloneable { } private ObjectTree clone(ObjectTree parent) { - ObjectTree clone = new ObjectTree(getMemberName(), parent); + ObjectTree clone = new ObjectTree(getMemberNode().copyToParent(parent.getMemberNode())); for (Map.Entry entry : childrenMap.entrySet()) clone.childrenMap.put(entry.getKey(), entry.getValue().clone(clone)); return clone; diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ObjectTreeConnection.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ObjectTreeConnection.java index 035c1a9a64d82d8cef8543340d1d2c435069293f..cf48bbdba3976350aa9a4bdb42e8f80e8c12c01d 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ObjectTreeConnection.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ObjectTreeConnection.java @@ -9,6 +9,8 @@ import es.upv.mist.slicing.graphs.jsysdg.JSysDG; import es.upv.mist.slicing.graphs.jsysdg.JSysPDG; import es.upv.mist.slicing.nodes.oo.MemberNode; +import java.util.Collection; +import java.util.Collections; import java.util.function.Supplier; /** A connection between two object trees. This object can specify the connection between two different @@ -46,37 +48,38 @@ class ObjectTreeConnection implements VariableAction.PDGConnection { } protected void connectTrees(Graph graph, Supplier flowSupplier, Supplier objFlowSupplier) { - Supplier valueSupplier = flowSupplier; - ObjectTree source = null, target = null; - GraphNode rootSrc, rootTgt; + Collection source = Collections.singleton(null); + Collection target = Collections.singleton(null); assert sourceMember.isEmpty() || sourceAction.hasObjectTree(); assert targetMember.isEmpty() || targetAction.hasObjectTree(); - if (sourceAction.hasObjectTree()) { - source = sourceAction.getObjectTree().findObjectTreeOfMember(sourceMember); - rootSrc = source.getMemberNode(); - } else { - rootSrc = sourceAction.getGraphNode(); - } - if (targetAction.hasObjectTree()) { - target = targetAction.getObjectTree().findObjectTreeOfMember(targetMember); - rootTgt = target.getMemberNode(); - } else { - rootTgt = targetAction.getGraphNode(); - } + if (sourceAction.hasObjectTree()) + source = sourceAction.getObjectTree().findObjectTreeOfPolyMember(sourceMember); + if (targetAction.hasObjectTree()) + target = targetAction.getObjectTree().findObjectTreeOfPolyMember(targetMember); + for (ObjectTree treeSrc : source) + for (ObjectTree treeTgt : target) + connectOT(treeSrc, treeTgt, + treeSrc != null ? treeSrc.getMemberNode() : sourceAction.getGraphNode(), + treeTgt != null ? treeTgt.getMemberNode() : targetAction.getGraphNode(), + graph, flowSupplier, objFlowSupplier); + } + + private void connectOT(ObjectTree source, ObjectTree target, GraphNode rootSrc, GraphNode rootTgt, + Graph graph, Supplier flowSupplier, Supplier objFlowSupplier) { if (source == null || target == null) { if (!rootSrc.equals(rootTgt)) - graph.addEdge(rootSrc, rootTgt, valueSupplier.get()); + graph.addEdge(rootSrc, rootTgt, flowSupplier.get()); } else { graph.addEdge(rootSrc, rootTgt, objFlowSupplier.get()); - graph.addEdge(rootSrc, rootTgt, valueSupplier.get()); - for (ObjectTree tree : target.treeIterable()) { - MemberNode src = source.getNodeForNonRoot(tree.getMemberName()); - MemberNode tgt = tree.getMemberNode(); - if (tree.hasChildren()) { + graph.addEdge(rootSrc, rootTgt, flowSupplier.get()); + for (String treeMember : target.nameIterable()) { + if (!source.hasMember(treeMember)) + continue; + MemberNode src = source.getNodeFor(treeMember); + MemberNode tgt = target.getNodeFor(treeMember); + if (target.hasChildren(treeMember)) graph.addEdge(src, tgt, objFlowSupplier.get()); - graph.addEdge(src, tgt, valueSupplier.get()); - } else - graph.addEdge(src, tgt, flowSupplier.get()); + graph.addEdge(src, tgt, flowSupplier.get()); } } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ValueConnection.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ValueConnection.java index f3648fd7592b2ffc8c18cde3f6fb8931b85467e8..006a875f84ac61432065b212f4b5f24cde64bb54 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ValueConnection.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ValueConnection.java @@ -2,6 +2,7 @@ package es.upv.mist.slicing.nodes; import es.upv.mist.slicing.arcs.pdg.FlowDependencyArc; import es.upv.mist.slicing.graphs.jsysdg.JSysPDG; +import es.upv.mist.slicing.nodes.oo.MemberNode; import static es.upv.mist.slicing.nodes.ObjectTree.ROOT_NAME; @@ -23,7 +24,8 @@ public class ValueConnection implements VariableAction.PDGConnection { statementNode = ((VariableAction.Movable) action).getRealNode(); else statementNode = action.getGraphNode(); - if (action.hasTreeMember(member)) - graph.addEdge(action.getObjectTree().getNodeFor(member), statementNode, new FlowDependencyArc()); + if (action.hasPolyTreeMember(member)) + for (MemberNode source : action.getObjectTree().getNodesForPoly(member)) + graph.addEdge(source, statementNode, new FlowDependencyArc()); } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableAction.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableAction.java index 9a0fabbfde8cd9163bce15ba25861a7e8245f736..aa49ca88e6cb2d8ba0eb97150fa3a74bd2273364 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableAction.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableAction.java @@ -1,22 +1,23 @@ package es.upv.mist.slicing.nodes; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.expr.*; 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.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserSymbolDeclaration; import es.upv.mist.slicing.arcs.Arc; import es.upv.mist.slicing.arcs.pdg.DataDependencyArc; +import es.upv.mist.slicing.graphs.ClassGraph; import es.upv.mist.slicing.graphs.Graph; import es.upv.mist.slicing.graphs.jsysdg.JSysDG; import es.upv.mist.slicing.graphs.jsysdg.JSysPDG; import es.upv.mist.slicing.graphs.pdg.PDG; +import es.upv.mist.slicing.utils.ASTUtils; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import static es.upv.mist.slicing.nodes.VariableAction.DeclarationType.*; @@ -65,7 +66,9 @@ public abstract class VariableAction { protected final String name; protected final DeclarationType declarationType; + protected final Set dynamicTypes = new HashSet<>(); + protected ResolvedType staticType; protected GraphNode graphNode; protected ObjectTree objectTree; protected boolean optional = false; @@ -156,10 +159,32 @@ public abstract class VariableAction { return isRootAction() && !hasObjectTree(); } + public void setStaticType(ResolvedType staticType) { + this.staticType = staticType; + dynamicTypes.clear(); + dynamicTypes.add(staticType); + if (staticType.isReferenceType() && ClassGraph.getInstance().containsType(staticType.asReferenceType())) { + ClassGraph.getInstance().subclassesOf(staticType.asReferenceType()).stream() + .map(ClassOrInterfaceDeclaration::resolve) + .map(ASTUtils::resolvedTypeDeclarationToResolvedType) + .forEach(dynamicTypes::add); + } + } + + public ResolvedType getStaticType() { + return staticType; + } + + public Set getDynamicTypes() { + return dynamicTypes; + } + // ====================================================== // =================== OBJECT TREE ====================== // ====================================================== + /** Whether there is an object tree and it contains the given member. + * The search will match exactly the argument given in the tree's structure. */ public boolean hasTreeMember(String member) { if (member.isEmpty()) return hasObjectTree(); @@ -168,6 +193,16 @@ public abstract class VariableAction { return getObjectTree().hasMember(member); } + /** Whether there is an object tree and it contains the given member. + * The search will skip polymorphic nodes if they haven't been specified in the argument. */ + public boolean hasPolyTreeMember(String member) { + if (member.isEmpty()) + return hasObjectTree(); + if (!hasObjectTree()) + return false; + return getObjectTree().hasPolyMember(member); + } + public boolean hasObjectTree() { return objectTree != null; } @@ -199,13 +234,17 @@ public abstract class VariableAction { Movable movable = (Movable) this; return new Movable(movable.inner.getRootAction(), movable.getRealNode()); } + VariableAction action; if (this instanceof Usage) - return new Usage(declarationType, ObjectTree.removeFields(name), graphNode); - if (this instanceof Definition) - return new Definition(declarationType, ObjectTree.removeFields(name), graphNode, asDefinition().expression); - if (this instanceof Declaration) + action = new Usage(declarationType, ObjectTree.removeFields(name), graphNode); + else if (this instanceof Definition) + action = new Definition(declarationType, ObjectTree.removeFields(name), graphNode, asDefinition().expression); + else if (this instanceof Declaration) throw new UnsupportedOperationException("Can't create a root node for a declaration!"); - throw new IllegalStateException("Invalid action type"); + else + throw new IllegalStateException("Invalid action type"); + action.setStaticType(staticType); + return action; } public boolean isRootAction() { @@ -465,6 +504,21 @@ public abstract class VariableAction { return inner.objectTree != null; } + @Override + public void setStaticType(ResolvedType staticType) { + inner.setStaticType(staticType); + } + + @Override + public ResolvedType getStaticType() { + return inner.getStaticType(); + } + + @Override + public Set getDynamicTypes() { + return inner.getDynamicTypes(); + } + /** The final location of this action. This node may not yet be present * in the graph, if {@link #move(Graph)} has not been invoked. */ public SyntheticNode getRealNode() { diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableVisitor.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableVisitor.java index 8afd7e5f4d65d137e9f053bebb6444a210539486..66fd51ada4cfa3569b19b20ebc9097f80cd6864c 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableVisitor.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableVisitor.java @@ -1,10 +1,7 @@ package es.upv.mist.slicing.nodes; import com.github.javaparser.ast.Node; -import com.github.javaparser.ast.body.CallableDeclaration; -import com.github.javaparser.ast.body.FieldDeclaration; -import com.github.javaparser.ast.body.Parameter; -import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.body.*; import com.github.javaparser.ast.expr.*; import com.github.javaparser.ast.nodeTypes.NodeWithArguments; import com.github.javaparser.ast.stmt.*; @@ -14,6 +11,7 @@ import com.github.javaparser.resolution.UnsolvedSymbolException; import com.github.javaparser.resolution.declarations.AssociableToAST; import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration; import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserMethodDeclaration; import es.upv.mist.slicing.graphs.ClassGraph; import es.upv.mist.slicing.graphs.ExpressionObjectTreeFinder; @@ -24,10 +22,8 @@ import es.upv.mist.slicing.nodes.io.CallNode; import es.upv.mist.slicing.utils.ASTUtils; import es.upv.mist.slicing.utils.Logger; -import java.util.Deque; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; +import java.util.*; +import java.util.stream.Collectors; import static es.upv.mist.slicing.graphs.cfg.CFGBuilder.VARIABLE_NAME_OUTPUT; import static es.upv.mist.slicing.graphs.exceptionsensitive.ESCFG.ACTIVE_EXCEPTION_VARIABLE; @@ -76,11 +72,18 @@ public class VariableVisitor extends GraphNodeContentVisitor node) { startVisit(node, USE); groupActionsByRoot(node); + generatePolyTrees(node); } @Override public void visit(NameExpr n, Action action) { - acceptAction(n, action); + String realName = getRealName(n); + if (realName.equals(n.toString())) { + acceptAction(n, action); + } else { + acceptAction(DeclarationType.valueOf(n), realName, action); + graphNode.getLastVariableAction().setStaticType(ASTUtils.resolvedTypeDeclarationToResolvedType(n.findAncestor(ClassOrInterfaceDeclaration.class).orElseThrow().resolve())); + } } @Override @@ -110,14 +113,22 @@ public class VariableVisitor extends GraphNodeContentVisitor { init.accept(this, action); definitionStack.push(init); acceptAction(LOCAL_VARIABLE, v.getNameAsString(), DEFINITION); + graphNode.getLastVariableAction().setStaticType(v.getType().resolve()); definitionStack.pop(); if (v.getType().isClassOrInterfaceType()) getLastDefinition().setTotallyDefinedMember(v.getNameAsString()); @@ -348,13 +369,16 @@ public class VariableVisitor extends GraphNodeContentVisitor ASTUtils.initializerForField(n)); init.accept(this, action); definitionStack.push(init); acceptAction(FIELD, realName, DEFINITION); + graphNode.getLastVariableAction().setStaticType(staticType); definitionStack.pop(); if (v.getType().isClassOrInterfaceType()) getLastDefinition().setTotallyDefinedMember(realName); @@ -382,7 +406,9 @@ public class VariableVisitor extends GraphNodeContentVisitor vaList = graphNode.getVariableActions(); if (vaList.size() >= 5) { // call-super, DEC(this), USE(-output-), ret-super, DEF(this) @@ -433,15 +461,20 @@ public class VariableVisitor extends GraphNodeContentVisitor scope.accept(this, action), - () -> acceptAction(FIELD, "this", USE)); + () -> { + acceptAction(FIELD, "this", USE); + graphNode.getLastVariableAction().setStaticType(ASTUtils.resolvedTypeDeclarationToResolvedType(((MethodCallExpr) call).findAncestor(ClassOrInterfaceDeclaration.class).orElseThrow().resolve())); + }); realNodeStack.pop(); } // Args @@ -470,12 +503,15 @@ public class VariableVisitor extends GraphNodeContentVisitor (ObjectTree) tree.clone()).orElse(null)); def.setTotallyDefinedMember(ROOT_NAME); var defMov = new VariableAction.Movable(def, CallNode.Return.create(call)); + defMov.setStaticType(ASTUtils.getCallResolvedType(call)); graphNode.addVariableAction(defMov); // The container of the call uses -output-, unless the call is wrapped in an ExpressionStmt Optional parentNode = ((Node) call).getParentNode(); - if (parentNode.isEmpty() || !(parentNode.get() instanceof ExpressionStmt)) + if (parentNode.isEmpty() || !(parentNode.get() instanceof ExpressionStmt)) { graphNode.addVariableAction(new VariableAction.Usage(SYNTHETIC, VARIABLE_NAME_OUTPUT, graphNode, fields.map(tree -> (ObjectTree) tree.clone()).orElse(null))); + graphNode.getLastVariableAction().setStaticType(ASTUtils.getCallResolvedType(call)); + } } protected Optional getFieldsForReturn(Resolvable call) { @@ -550,7 +586,7 @@ public class VariableVisitor extends GraphNodeContentVisitor + * Static variable actions are excluded from this process. + *
+ * Polymorphic nodes are only generated if (a) at least one of the dynamic types is + * contained in the class graph, (b) the polymorphic variable has fields and (c) there + * aren't any polymorphic nodes in the object tree already. */ + protected void generatePolyTrees(GraphNode graphNode) { + ClassGraph classGraph = ClassGraph.getInstance(); + for (VariableAction va : graphNode.getVariableActions()) { + if (va.isStatic() || !va.hasObjectTree()) + continue; + ObjectTree newTree = new ObjectTree(va.getName()); + polyUnit(va.getObjectTree(), newTree, va.getDynamicTypes(), classGraph); + va.setObjectTree(newTree); + } + } + + /** Handle a single level of conversion between object tree without polymorphic nodes + * to new object tree with polymorphic nodes. + * @see #generatePolyTrees(GraphNode) */ + protected void polyUnit(ObjectTree oldOT, ObjectTree newOT, Set types, ClassGraph classGraph) { + boolean skipPolyNodes = types.stream().noneMatch(classGraph::containsType) || !oldOT.hasChildren() || oldOT.hasPoly(); + if (skipPolyNodes) { + // Copy as-is + newOT.addAll(oldOT); + } else { + // Copy with typing information + for (ResolvedType rt : types) { + boolean rtInGraph = classGraph.containsType(rt); + ObjectTree typeRoot = newOT.addType(rt); + // Insert type node and copy members over + for (Map.Entry entry : oldOT.entrySet()) + polyUnit(entry.getValue(), + typeRoot.addImmediateField(entry.getKey()), + rtInGraph ? dynamicTypesOf(rt, entry.getKey(), classGraph) : Collections.emptySet(), + classGraph); + } + } + } + + /** Obtain the set of possible dynamic types of the given field within a given type. + * Doesn't take into account the CFG, only the class graph. */ + protected Set dynamicTypesOf(ResolvedType rt, String fieldName, ClassGraph classGraph) { + Optional field = classGraph.findClassField(rt, fieldName); + if (field.isEmpty()) + return Collections.emptySet(); + ResolvedType fieldType = field.get().getVariable(0).getType().resolve(); + if (!fieldType.isReferenceType() || !classGraph.containsType(fieldType)) + return Set.of(fieldType); + return classGraph.subclassesOf(fieldType.asReferenceType()).stream() + .map(ClassOrInterfaceDeclaration::resolve) + .map(ASTUtils::resolvedTypeDeclarationToResolvedType) + .collect(Collectors.toSet()); + } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/oo/MemberNode.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/oo/MemberNode.java index dc2cd0bd384e0e58ff4dbbe20174aab384ab8143..19d9bd71c57d1ed9d3615a61fb3924987c53b45e 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/oo/MemberNode.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/oo/MemberNode.java @@ -33,4 +33,9 @@ public class MemberNode extends SyntheticNode { getLabel() ); } + + /** Create a copy of this node with the given argument as its parent node. */ + public MemberNode copyToParent(GraphNode parent) { + return new MemberNode(label, parent); + } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/oo/PolyMemberNode.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/oo/PolyMemberNode.java new file mode 100644 index 0000000000000000000000000000000000000000..45d2d9dce15347736153a9d6e5af39234218c8dc --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/oo/PolyMemberNode.java @@ -0,0 +1,23 @@ +package es.upv.mist.slicing.nodes.oo; + +import com.github.javaparser.resolution.types.ResolvedType; +import es.upv.mist.slicing.nodes.GraphNode; + +/** A node in an object tree that represents a type in a polymorphic object. */ +public class PolyMemberNode extends MemberNode { + /** Create a new polymorphic member node based on the given type and with the given parent. */ + public PolyMemberNode(ResolvedType type, GraphNode parent) { + this(type.describe(), parent); + } + + /** Internal constructor for cloning purposes. + * @see #copyToParent(GraphNode) */ + private PolyMemberNode(String label, GraphNode parent) { + super(label, parent); + } + + @Override + public MemberNode copyToParent(GraphNode parent) { + return new PolyMemberNode(label, parent); + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/ExceptionSensitiveSlicingAlgorithm.java b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/ExceptionSensitiveSlicingAlgorithm.java index c8d382e623b4ca3d99a51f7d8a1b3818a62f120f..ec651569825820f061e31600998ce9843e99f08f 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/ExceptionSensitiveSlicingAlgorithm.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/ExceptionSensitiveSlicingAlgorithm.java @@ -61,7 +61,7 @@ public class ExceptionSensitiveSlicingAlgorithm implements SlicingAlgorithm { this.slicingCriterion = Set.of(slicingCriterion); Slice slice = new Slice(); slice.add(slicingCriterion); - pass(slice, INTRAPROCEDURAL); + pass(slice, INTRAPROCEDURAL.or(this::commonIgnoreConditions)); return slice; } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/LineNumberCriterion.java b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/LineNumberCriterion.java index de7bd6f2cf5380d13c0fac3f1359ae9425b8b183..b9da0c18f84f73e47aa62b2e87c6c07458e6530b 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/LineNumberCriterion.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/LineNumberCriterion.java @@ -9,7 +9,10 @@ import es.upv.mist.slicing.graphs.sdg.SDG; import es.upv.mist.slicing.nodes.GraphNode; import es.upv.mist.slicing.nodes.ObjectTree; -import java.util.*; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -60,22 +63,21 @@ public class LineNumberCriterion implements SlicingCriterion { return locateAllNodes(graphNode, graph); return locateAllNodes(graphNode, graph) .map(GraphNode::getVariableActions).flatMap(List::stream) - .map(variableAction -> { + .flatMap(variableAction -> { if (variableAction.getName().equals(variable)) { if (variableAction.hasObjectTree()) - return variableAction.getObjectTree().getMemberNode(); + return Stream.of(variableAction.getObjectTree().getMemberNode()); else - return variableAction.getGraphNode(); + return Stream.of(variableAction.getGraphNode()); } else if (variable.contains(".") && variableAction.getName().equals(ObjectTree.removeFields(variable))) { - if (variableAction.hasTreeMember(variable)) - return variableAction.getObjectTree().getNodeFor(variable); + if (variableAction.hasPolyTreeMember(variable)) + return variableAction.getObjectTree().getNodesForPoly(variable).stream(); else - return null; + return Stream.empty(); } else { - return null; + return Stream.empty(); } - }) - .filter(Objects::nonNull); + }); } protected Stream> locateAllNodes(GraphNode graphNode, SDG graph) { diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/utils/ASTUtils.java b/sdg-core/src/main/java/es/upv/mist/slicing/utils/ASTUtils.java index 3ab147b15e1e0676e227b877e67ee9b035c6a823..f6ac1910ae34605c41f8956417561bab6d957ede 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/utils/ASTUtils.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/utils/ASTUtils.java @@ -99,6 +99,17 @@ public class ASTUtils { throw new IllegalStateException("The method must have a body!"); } + /** Compute the resolved type that is returned from a given method call. */ + public static ResolvedType getCallResolvedType(Resolvable call) { + if (call instanceof MethodCallExpr) + return ((MethodCallExpr) call).calculateResolvedType(); + if (call instanceof ObjectCreationExpr) + return ((ObjectCreationExpr) call).calculateResolvedType(); + if (call instanceof ExplicitConstructorInvocationStmt) + return resolvedTypeDeclarationToResolvedType(((ExplicitConstructorInvocationStmt) call).resolve().declaringType()); + throw new IllegalArgumentException("Call wasn't of a compatible type!"); + } + public static Optional getResolvableScope(Resolvable call) { if (call instanceof MethodCallExpr) return ((MethodCallExpr) call).getScope(); diff --git a/sdg-core/src/test/res/regression/carlos/Classic.java b/sdg-core/src/test/res/regression/carlos/Classic.java index 074070ffc6a3f8bd555b4d2b15d7531af94d7853..07658e0baf73879df4d637149524488a4c30c042 100644 --- a/sdg-core/src/test/res/regression/carlos/Classic.java +++ b/sdg-core/src/test/res/regression/carlos/Classic.java @@ -5,6 +5,7 @@ public class Classic { int sum = 0; int product = 1; int w = 7; + int N = 10; for (int i = 1; i < N; i++) { sum = sum + i + w; product *= i; diff --git a/sdg-core/src/test/res/regression/carlos/Classic.java.sdg.criterion b/sdg-core/src/test/res/regression/carlos/Classic.java.sdg.criterion index b1bd38b62a0800a4f6a80c34e21c5acffae52c7e..8351c19397f4fcd5238d10034fa7fa384f14d580 100644 --- a/sdg-core/src/test/res/regression/carlos/Classic.java.sdg.criterion +++ b/sdg-core/src/test/res/regression/carlos/Classic.java.sdg.criterion @@ -1 +1 @@ -13 +14 diff --git a/sdg-core/src/test/res/regression/carlos/Classic.java.sdg.sliced b/sdg-core/src/test/res/regression/carlos/Classic.java.sdg.sliced index b1cec7bdd3f7cde5831d2f4ee2e1a9b22c15047e..e98974e892c2568394fb56af4a0e5462f05bc9b9 100644 --- a/sdg-core/src/test/res/regression/carlos/Classic.java.sdg.sliced +++ b/sdg-core/src/test/res/regression/carlos/Classic.java.sdg.sliced @@ -4,6 +4,7 @@ public class Classic { public static void main(String[] args) { int product = 1; + int N = 10; for (int i = 1; i < N; i++) { product *= i; }