diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/CallGraph.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/CallGraph.java index ea738c4d414a9a425a7968f5f2133af785ae72c8..6b1b52749e56da2ea75e12f98a72b0966894582b 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/CallGraph.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/CallGraph.java @@ -2,10 +2,8 @@ package es.upv.mist.slicing.graphs; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.NodeList; -import com.github.javaparser.ast.body.CallableDeclaration; -import com.github.javaparser.ast.body.ConstructorDeclaration; -import com.github.javaparser.ast.body.InitializerDeclaration; -import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.body.*; +import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.ObjectCreationExpr; import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt; @@ -15,16 +13,17 @@ import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclarati import es.upv.mist.slicing.graphs.cfg.CFG; import es.upv.mist.slicing.nodes.GraphNode; import es.upv.mist.slicing.utils.ASTUtils; +import es.upv.mist.slicing.utils.NodeHashSet; import es.upv.mist.slicing.utils.NodeNotFoundException; import es.upv.mist.slicing.utils.Utils; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.DirectedPseudograph; import org.jgrapht.nio.dot.DOTExporter; -import java.util.Deque; -import java.util.LinkedList; -import java.util.Map; -import java.util.Objects; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * A directed graph which displays the available method declarations as nodes and their @@ -41,23 +40,23 @@ import java.util.Objects; public class CallGraph extends DirectedPseudograph> implements Buildable> { private final Map, CFG> cfgMap; private final Map, Vertex> vertexDeclarationMap = ASTUtils.newIdentityHashMap(); + private final ClassGraph classGraph; private boolean built = false; - public CallGraph(Map, CFG> cfgMap) { + public CallGraph(Map, CFG> cfgMap, ClassGraph classGraph) { super(null, null, false); this.cfgMap = cfgMap; + this.classGraph = classGraph; } - /** Resolve a call to its declaration, by using the call AST nodes stored on the edges. */ - public CallableDeclaration getCallTarget(Resolvable call) { + /** Resolve a call to all its possible declarations, by using the call AST nodes stored on the edges. */ + public Stream> getCallTargets(Resolvable call) { return edgeSet().stream() .filter(e -> e.getCall() == call) .map(this::getEdgeTarget) .map(Vertex::getDeclaration) - .map(CallableDeclaration.class::cast) - .findFirst() - .orElse(null); + .map(decl -> (CallableDeclaration) decl); } @Override @@ -105,8 +104,16 @@ public class CallGraph extends DirectedPseudograph arg) { arg.accept(new VoidVisitorAdapter() { + private final Deque classStack = new LinkedList<>(); private final Deque> declStack = new LinkedList<>(); + @Override + public void visit(ClassOrInterfaceDeclaration n, Void arg) { + classStack.push(n); + super.visit(n, arg); + classStack.pop(); + } + // ============ Method declarations =========== // There are some locations not considered, which may lead to an error in the stack. // 1. Method calls in non-static field initializations are assigned to all constructors of that class @@ -129,24 +136,68 @@ public class CallGraph extends DirectedPseudograph addEdge(declStack.peek(), decl, n)); + n.resolve().toAst().ifPresent(decl -> createPolyEdges(decl, n)); if (ASTUtils.shouldVisitArgumentsForMethodCalls(n)) super.visit(n, arg); } @Override public void visit(ObjectCreationExpr n, Void arg) { - n.resolve().toAst().ifPresent(decl -> addEdge(declStack.peek(), decl, n)); + n.resolve().toAst().ifPresent(decl -> createNormalEdge(decl, n)); if (ASTUtils.shouldVisitArgumentsForMethodCalls(n)) super.visit(n, arg); } @Override public void visit(ExplicitConstructorInvocationStmt n, Void arg) { - n.resolve().toAst().ifPresent(decl -> addEdge(declStack.peek(), decl, n)); + n.resolve().toAst().ifPresent(decl -> createNormalEdge(decl, n)); if (ASTUtils.shouldVisitArgumentsForMethodCalls(n)) super.visit(n, arg); } + + protected void createPolyEdges(MethodDeclaration decl, MethodCallExpr call) { + // Static calls have no polymorphism, ignore + if (decl.isStatic()) { + createNormalEdge(decl, call); + return; + } + Optional scope = call.getScope(); + // Determine the type of the call's scope + Set dynamicTypes; + if (scope.isEmpty()) { + // a) No scope: any class the method is in, or any outer class if the class is not static. + // Early exit: it is easier to find the methods that override the + // detected call than to account for all cases (implicit inner or outer class) + classGraph.overriddenSetOf(decl) + .forEach(methodDecl -> createNormalEdge(methodDecl, call)); + return; + } else if (scope.get().isThisExpr() && scope.get().asThisExpr().getTypeName().isEmpty()) { + // b) just 'this', the current class and any subclass + dynamicTypes = classGraph.subclassesOf(classStack.peek()); + } else if (scope.get().isThisExpr()) { + // c) 'ClassName.this', the given class and any subclass + dynamicTypes = classGraph.subclassesOf(scope.get().asThisExpr().resolve().asClass()); + } else { + // d) others: compute possible dynamic types of the expression (TODO) + dynamicTypes = classGraph.subclassesOf(scope.get().calculateResolvedType().asReferenceType()); + } + // Locate the corresponding methods for each possible dynamic type, they must be available to all + // To locate them, use the method signature and search for it in the class graph + // Connect to each declaration + AtomicInteger edgesCreated = new AtomicInteger(); + dynamicTypes.stream() + .map(t -> classGraph.findMethodByTypeAndSignature(t, decl.getSignature())) + .collect(Collectors.toCollection(NodeHashSet::new)) + .forEach(methodDecl -> { + edgesCreated.getAndIncrement(); + createNormalEdge(methodDecl, call); + }); + assert edgesCreated.get() > 0; + } + + protected void createNormalEdge(CallableDeclaration decl, Resolvable call) { + addEdge(declStack.peek(), decl, call); + } }, null); } 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 899dfb404ae5afaaf0f0bf48ec41a72da067ceec..bd11813025c76dd863e51abf65bb97aca5d5c4c9 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 @@ -5,6 +5,7 @@ import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.body.*; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import com.github.javaparser.resolution.declarations.ResolvedClassDeclaration; +import com.github.javaparser.resolution.types.ResolvedReferenceType; import es.upv.mist.slicing.arcs.Arc; import es.upv.mist.slicing.utils.ASTUtils; import es.upv.mist.slicing.utils.Utils; @@ -30,16 +31,75 @@ public class ClassGraph extends DirectedPseudograph vertex = vertexSet().stream() - .filter(v -> v.declaration instanceof ClassOrInterfaceDeclaration) + protected Vertex findClassVertex(ClassOrInterfaceDeclaration declaration) { + return vertexSet().stream() + .filter(v -> v.declaration.isClassOrInterfaceDeclaration()) + .filter(v -> ASTUtils.equalsWithRangeInCU(v.declaration, declaration)) + .findFirst().orElseThrow(); + } + + /** Locates the vertex that represents a given class or interface declaration. + * The vertex must exist, or an exception will be thrown. */ + protected Vertex findClassVertex(ResolvedClassDeclaration declaration) { + return vertexSet().stream() + .filter(v -> v.declaration.isClassOrInterfaceDeclaration()) .filter(v -> v.declaration.asClassOrInterfaceDeclaration().resolve().asClass().equals(declaration)) - .findFirst(); - return vertex.orElseThrow(); + .findFirst().orElseThrow(); + } + + protected Vertex findClassVertex(ResolvedReferenceType type) { + return vertexSet().stream() + .filter(v -> v.declaration.isClassOrInterfaceDeclaration()) + .filter(v -> ASTUtils.resolvedTypeDeclarationToResolvedType(v.declaration.asClassOrInterfaceDeclaration().resolve()).equals(type)) + .findFirst().orElseThrow(); + } + + protected Vertex findMethodVertex(CallableDeclaration declaration) { + return vertexSet().stream() + .filter(v -> v.declaration.isCallableDeclaration()) + .filter(v -> ASTUtils.equalsWithRangeInCU(v.declaration, declaration)) + .findFirst().orElseThrow(); + } + + public Set overriddenSetOf(MethodDeclaration method) { + return subclassesStreamOf(classVertexOf(findMethodVertex(method))) + .flatMap(vertex -> outgoingEdgesOf(vertex).stream() + .filter(ClassArc.Member.class::isInstance) + .map(ClassGraph.this::getEdgeTarget) + .filter(v -> v.declaration.isMethodDeclaration()) + .filter(v -> v.declaration.asMethodDeclaration().getSignature().equals(method.getSignature())) + .map(v -> v.declaration.asMethodDeclaration())) + .collect(Collectors.toSet()); + } + + protected Vertex classVertexOf(Vertex member) { + assert member.declaration.isFieldDeclaration() || + member.declaration.isCallableDeclaration() || + member.declaration.isInitializerDeclaration(); + return incomingEdgesOf(member).stream() + .filter(ClassArc.Member.class::isInstance) + .map(this::getEdgeSource) + .findFirst().orElseThrow(); } + /** Returns all child classes of the given class, including itself. */ + public Set subclassesOf(ClassOrInterfaceDeclaration clazz) { + return subclassesOf(findClassVertex(clazz)); + } + + /** Returns all child classes of the given class, including itself. */ public Set subclassesOf(ResolvedClassDeclaration clazz) { - return subclassesStreamOf(findVertex(clazz)) + return subclassesOf(findClassVertex(clazz)); + } + + public Set subclassesOf(ResolvedReferenceType type) { + return subclassesOf(findClassVertex(type)); + } + + /** @see #subclassesOf(ClassOrInterfaceDeclaration) */ + protected Set subclassesOf(Vertex v) { + return subclassesStreamOf(v) + .map(Vertex::getDeclaration) .map(ClassOrInterfaceDeclaration.class::cast) .collect(Collectors.toSet()); } @@ -51,6 +111,37 @@ public class ClassGraph extends DirectedPseudograph result = outgoingEdgesOf(findClassVertex(type)).stream() + .filter(ClassArc.Member.class::isInstance) + .map(this::getEdgeTarget) + .map(Vertex::getDeclaration) + .filter(BodyDeclaration::isMethodDeclaration) + .map(BodyDeclaration::asMethodDeclaration) + .filter(decl -> signature.equals(decl.getSignature())) + .findFirst(); + if (result.isPresent()) + return result.get(); + Optional parentType = parentOf(type); + if (parentType.isEmpty()) + throw new IllegalArgumentException("Cannot find the given signature: " + signature); + return findMethodByTypeAndSignature(parentType.get(), signature); + } + + /** Find the parent class or interface of a given class. */ + public Optional parentOf(ClassOrInterfaceDeclaration declaration) { + return incomingEdgesOf(findClassVertex(declaration)).stream() + .filter(ClassArc.Extends.class::isInstance) + .map(this::getEdgeSource) + .map(Vertex::getDeclaration) + .filter(BodyDeclaration::isClassOrInterfaceDeclaration) + .map(BodyDeclaration::asClassOrInterfaceDeclaration) + .findFirst(); + } + @Override public void build(NodeList arg) { if (isBuilt()) diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/ExceptionSensitiveCallConnector.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/ExceptionSensitiveCallConnector.java index 7c4f2134c9c5d42f6fb8205bf8fcd3b5bd836c42..d43d57bdde9aafb537d6d91c8879fa3437d2258b 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/ExceptionSensitiveCallConnector.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/ExceptionSensitiveCallConnector.java @@ -27,7 +27,8 @@ public class ExceptionSensitiveCallConnector extends CallConnector { @Override protected void connectCall(CallNode callNode, CallGraph callGraph) { var callExpr = callNode.getCallASTNode(); - if (callGraph.getCallTarget(callExpr).getThrownExceptions().size() > 0) + // We can pick any call, because the signatures must match + if (callGraph.getCallTargets(callExpr).findFirst().orElseThrow().getThrownExceptions().size() > 0) handleExceptionReturnArcs(callExpr, callGraph); super.connectCall(callNode, callGraph); } @@ -44,12 +45,10 @@ public class ExceptionSensitiveCallConnector extends CallConnector { .filter(SyntheticNode.class::isInstance) .map(n -> (SyntheticNode) n) .collect(Collectors.toSet()); - CallableDeclaration decl = callGraph.getCallTarget(call); - if (decl == null) - throw new IllegalArgumentException("Unknown call!"); - - connectNormalNodes(synthNodes, call, decl); - connectExceptionNodes(synthNodes, call, decl); + callGraph.getCallTargets(call).forEach(decl -> { + connectNormalNodes(synthNodes, call, decl); + connectExceptionNodes(synthNodes, call, decl); + }); } /** Connects normal exit nodes to their corresponding return node. */ diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/CallConnector.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/CallConnector.java index b00417df328207bb3ad36104116722c8b02f4cea..971f3736c49beb1aecdd2dc6702fca40c7293fc7 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/CallConnector.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/CallConnector.java @@ -14,8 +14,6 @@ import es.upv.mist.slicing.nodes.io.FormalIONode; import es.upv.mist.slicing.nodes.io.OutputNode; import es.upv.mist.slicing.utils.Logger; -import java.util.Optional; - /** Adds interprocedural arcs between the 'PDG components' of an SDG. * Arcs generated include {@link ParameterInOutArc parameter input/output} and * {@link CallArc call} arcs. */ @@ -34,20 +32,23 @@ public class CallConnector { .forEach(node -> connectCall(node, callGraph)); } - /** Connects a given call to its declaration, via call and in/out arcs. */ + /** Connects a given call to all possible matching declarations. */ + @SuppressWarnings("unchecked") protected void connectCall(CallNode callNode, CallGraph callGraph) { - @SuppressWarnings("unchecked") var callExpr = (Resolvable) callNode.getAstNode(); - GraphNode> declarationNode; - try { - declarationNode = Optional.ofNullable(callGraph.getCallTarget(callExpr)) - .flatMap(sdg::findNodeByASTNode) - .orElseThrow(IllegalArgumentException::new); - } catch (IllegalArgumentException e) { - Logger.format("Method declaration not found: '%s'. Discarding", callExpr); - return; - } + callGraph.getCallTargets(callExpr) + .map(sdg::findNodeByASTNode) + .filter(opt -> { + if (opt.isEmpty()) + Logger.format("Method declaration not found: '%s'. Discarding", callExpr); + return opt.isPresent(); + }) + .map(opt -> opt.orElseThrow(IllegalArgumentException::new)) + .forEach(node -> connectCall(callNode, node)); + } + /** Connects a given call to its declaration, via call and in/out arcs. */ + protected void connectCall(CallNode callNode, GraphNode> declarationNode) { // Connect the call and declaration nodes sdg.addCallArc(callNode, declarationNode); diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/SDG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/SDG.java index 2f0cb03f35a1ec5013d932e3d3ddb827a03d83b4..35d6ceee61b4502fe425ca7854d3bcbe50c72579 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/SDG.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/SDG.java @@ -110,8 +110,8 @@ public class SDG extends Graph implements Sliceable, Buildable nodeList) { // See creation strategy at http://kaz2.dsic.upv.es:3000/Fzg46cQvT1GzHQG9hFnP1g#Using-data-flow-in-the-SDG buildCFGs(nodeList); // 1 - CallGraph callGraph = createCallGraph(nodeList); // 2 ClassGraph classGraph = createClassGraph(nodeList); // TODO: Update order and creation strategy + CallGraph callGraph = createCallGraph(nodeList, classGraph); // 2 dataFlowAnalysis(callGraph); // 3 buildAndCopyPDGs(); // 4 connectCalls(callGraph); // 5 @@ -138,8 +138,8 @@ public class SDG extends Graph implements Sliceable, Buildable nodeList) { - CallGraph callGraph = new CallGraph(cfgMap); + protected CallGraph createCallGraph(NodeList nodeList, ClassGraph classGraph) { + CallGraph callGraph = new CallGraph(cfgMap, classGraph); callGraph.build(nodeList); return callGraph; } diff --git a/sdg-core/src/test/java/es/upv/mist/slicing/SlicerTest.java b/sdg-core/src/test/java/es/upv/mist/slicing/SlicerTest.java index 1d03298f5b3b0ccb713f6695564a2d1230530777..21f022482da8d58f66b67f94e5aa36ee73247661 100644 --- a/sdg-core/src/test/java/es/upv/mist/slicing/SlicerTest.java +++ b/sdg-core/src/test/java/es/upv/mist/slicing/SlicerTest.java @@ -3,14 +3,12 @@ package es.upv.mist.slicing; import com.github.javaparser.StaticJavaParser; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.NodeList; -import com.github.javaparser.symbolsolver.JavaSymbolSolver; -import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver; -import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver; import es.upv.mist.slicing.graphs.exceptionsensitive.ESSDG; import es.upv.mist.slicing.graphs.sdg.SDG; import es.upv.mist.slicing.slicing.FileLineSlicingCriterion; import es.upv.mist.slicing.slicing.Slice; import es.upv.mist.slicing.slicing.SlicingCriterion; +import es.upv.mist.slicing.utils.StaticTypeSolver; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -25,10 +23,7 @@ import java.util.function.Supplier; public class SlicerTest { static { - StaticJavaParser.getConfiguration().setAttributeComments(false); - CombinedTypeSolver combinedTypeSolver = new CombinedTypeSolver(); - combinedTypeSolver.add(new ReflectionTypeSolver(true)); - StaticJavaParser.getConfiguration().setSymbolResolver(new JavaSymbolSolver(combinedTypeSolver)); + StaticTypeSolver.addTypeSolverJRE(); StaticJavaParser.getConfiguration().setAttributeComments(false); } diff --git a/sdg-core/src/test/res/regression/dinsa-tests/Josep2.java.sdg.sliced b/sdg-core/src/test/res/regression/dinsa-tests/Josep2.java.sdg.sliced index d9ca3aa880a82d949d5318c4bd6878574fe5d0fe..49c80edb67799d606ed51870e8d8e8edc6211080 100644 --- a/sdg-core/src/test/res/regression/dinsa-tests/Josep2.java.sdg.sliced +++ b/sdg-core/src/test/res/regression/dinsa-tests/Josep2.java.sdg.sliced @@ -29,6 +29,9 @@ class Numeros { class GrandesNumeros extends Numeros { GrandesNumeros(double x) { - haceFalta = 0; + } + + int random() { + return haceFalta; } } diff --git a/sdg-core/src/test/res/regression/review-07-2020/P5.java.sdg.sliced b/sdg-core/src/test/res/regression/review-07-2020/P5.java.sdg.sliced index d9ca3aa880a82d949d5318c4bd6878574fe5d0fe..49c80edb67799d606ed51870e8d8e8edc6211080 100644 --- a/sdg-core/src/test/res/regression/review-07-2020/P5.java.sdg.sliced +++ b/sdg-core/src/test/res/regression/review-07-2020/P5.java.sdg.sliced @@ -29,6 +29,9 @@ class Numeros { class GrandesNumeros extends Numeros { GrandesNumeros(double x) { - haceFalta = 0; + } + + int random() { + return haceFalta; } }