diff --git a/sootup.callgraph/src/main/java/sootup/callgraph/AbstractCallGraphAlgorithm.java b/sootup.callgraph/src/main/java/sootup/callgraph/AbstractCallGraphAlgorithm.java index 95ff7c7588b..554cdeeb0c2 100644 --- a/sootup.callgraph/src/main/java/sootup/callgraph/AbstractCallGraphAlgorithm.java +++ b/sootup.callgraph/src/main/java/sootup/callgraph/AbstractCallGraphAlgorithm.java @@ -23,6 +23,7 @@ */ import java.util.*; +import java.util.function.BiPredicate; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nonnull; @@ -59,10 +60,14 @@ public abstract class AbstractCallGraphAlgorithm implements CallGraphAlgorithm { private static final Logger logger = LoggerFactory.getLogger(AbstractCallGraphAlgorithm.class); + private BiPredicate boundFunction; + @Nonnull protected final View view; - protected AbstractCallGraphAlgorithm(@Nonnull View view) { - this.view = view; + protected AbstractCallGraphAlgorithm(@Nonnull View view) + { + this.view = view; + this.boundFunction = (method, statement) -> true; } /** @@ -232,7 +237,7 @@ protected void resolveAllCallsFromSourceMethod( .map(Stmt::asInvokableStmt) .forEach( stmt -> - resolveCall(sourceMethod, stmt) + (boundFunction.test(sourceMethod, stmt) ? resolveCall(sourceMethod, stmt) : Stream. empty()) .forEach( targetMethod -> addCallToCG( @@ -572,4 +577,25 @@ public static Optional findConcreteMethod( + " and in its superclasses and interfaces"); return Optional.empty(); } + + /** + * @return the bound function, see @setBoundFunction + */ + public BiPredicate getBoundFunction() + { + return boundFunction; + } + + /** Set a bound function to prune the call graph. The bound function accepts the source method and + * an InvokableStatement, which is in the source method's body, and determines if the call from + * the source method to the invocation target shall be added into the call graph. + * + * This allows building an pruned & potentially incomplete call graph, with lower memory / CPU + * cost. + * + * @param boundFunction */ + public void setBoundFunction(BiPredicate boundFunction) + { + this.boundFunction = boundFunction == null ? (method, statement) -> true : boundFunction; + } } diff --git a/sootup.tests/src/test/java/sootup/tests/CallGraphTest.java b/sootup.tests/src/test/java/sootup/tests/CallGraphTest.java index 63f02d032ce..13ed295d66f 100644 --- a/sootup.tests/src/test/java/sootup/tests/CallGraphTest.java +++ b/sootup.tests/src/test/java/sootup/tests/CallGraphTest.java @@ -5,6 +5,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.BiPredicate; + import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import sootup.callgraph.AbstractCallGraphAlgorithm; @@ -49,6 +51,11 @@ private JavaView createViewForClassPath(String classPath) { } CallGraph loadCallGraph() { + return loadCallGraph(null); + } + + CallGraph loadCallGraph(BiPredicate boundFunction) + { double version = Double.parseDouble(System.getProperty("java.specification.version")); if (version > 1.8) { fail("The rt.jar is not available after Java 8. You are using version " + version); @@ -71,6 +78,8 @@ CallGraph loadCallGraph() { assertNotNull(m, mainMethodSignature + " not found in classloader"); AbstractCallGraphAlgorithm algorithm = createAlgorithm(view); + algorithm.setBoundFunction(boundFunction); + CallGraph cg = algorithm.initialize(Collections.singletonList(mainMethodSignature)); assertNotNull(cg); @@ -205,6 +214,68 @@ public void testRTA() { methodInterfaceImplementationNotInstatiated, getInvokableStmt(mainMethodSignature, methodInterface))); } + + @Test + public void testRTAWithNegativeBoundFunction() + { + algorithmName = "RTA"; + CallGraph cg = loadCallGraph((fromMethod, statement) -> false); + + MethodSignature methodAbstract = + identifierFactory.getMethodSignature( + identifierFactory.getClassType("AbstractClass"), + "method", + "int", + Collections.emptyList()); + MethodSignature methodMethodImplementedInstantiatedInSubClass = + identifierFactory.getMethodSignature( + identifierFactory.getClassType("MethodImplementedInstantiatedInSubClass"), + "method", + "int", + Collections.emptyList()); + MethodSignature methodSubClassMethodImplemented = + identifierFactory.getMethodSignature( + identifierFactory.getClassType("SubClassMethodImplemented"), + "method", + "int", + Collections.emptyList()); + + assertFalse( + cg.containsCall( + mainMethodSignature, + methodMethodImplementedInstantiatedInSubClass, + getInvokableStmt(mainMethodSignature, methodAbstract))); + assertFalse( + cg.containsCall( + mainMethodSignature, + methodSubClassMethodImplemented, + getInvokableStmt(mainMethodSignature, methodAbstract))); + + MethodSignature methodInterface = + identifierFactory.getMethodSignature( + identifierFactory.getClassType("Interface"), + "defaultMethod", + "int", + Collections.emptyList()); + MethodSignature methodInterfaceImplementation = + identifierFactory.getMethodSignature( + identifierFactory.getClassType("InterfaceImplementation"), + "defaultMethod", + "int", + Collections.emptyList()); + + + assertFalse( + cg.containsCall( + mainMethodSignature, + methodInterface, + getInvokableStmt(mainMethodSignature, methodInterface))); + assertFalse( + cg.containsCall( + mainMethodSignature, + methodInterfaceImplementation, + getInvokableStmt(mainMethodSignature, methodInterface))); + } @Test public void testCHA() { @@ -315,6 +386,115 @@ public void testCHA() { getInvokableStmt(mainMethodSignature, methodInterface))); } + @Test + public void testCHAWithPositiveBoundFunction() { + algorithmName = "CHA"; + CallGraph cg = loadCallGraph((method, statement)->true); + + MethodSignature methodAbstract = + identifierFactory.getMethodSignature( + identifierFactory.getClassType("AbstractClass"), + "method", + "int", + Collections.emptyList()); + MethodSignature methodMethodImplemented = + identifierFactory.getMethodSignature( + identifierFactory.getClassType("MethodImplemented"), + "method", + "int", + Collections.emptyList()); + MethodSignature methodMethodImplementedInstantiatedInSubClass = + identifierFactory.getMethodSignature( + identifierFactory.getClassType("MethodImplementedInstantiatedInSubClass"), + "method", + "int", + Collections.emptyList()); + MethodSignature methodSubClassMethodImplemented = + identifierFactory.getMethodSignature( + identifierFactory.getClassType("SubClassMethodImplemented"), + "method", + "int", + Collections.emptyList()); + MethodSignature methodSubClassMethodNotImplemented = + identifierFactory.getMethodSignature( + identifierFactory.getClassType("SubClassMethodNotImplemented"), + "method", + "int", + Collections.emptyList()); + + assertFalse( + cg.containsCall( + mainMethodSignature, + methodAbstract, + getInvokableStmt(mainMethodSignature, methodAbstract))); + assertTrue( + cg.containsCall( + mainMethodSignature, + methodMethodImplemented, + getInvokableStmt(mainMethodSignature, methodAbstract))); + assertTrue( + cg.containsCall( + mainMethodSignature, + methodMethodImplementedInstantiatedInSubClass, + getInvokableStmt(mainMethodSignature, methodAbstract))); + assertFalse( + cg.containsCall( + mainMethodSignature, + methodSubClassMethodNotImplemented, + getInvokableStmt(mainMethodSignature, methodAbstract))); + assertTrue( + cg.containsCall( + mainMethodSignature, + methodSubClassMethodImplemented, + getInvokableStmt(mainMethodSignature, methodAbstract))); + + MethodSignature methodInterface = + identifierFactory.getMethodSignature( + identifierFactory.getClassType("Interface"), + "defaultMethod", + "int", + Collections.emptyList()); + MethodSignature methodInterfaceNoImplementation = + identifierFactory.getMethodSignature( + identifierFactory.getClassType("InterfaceNoImplementation"), + "defaultMethod", + "int", + Collections.emptyList()); + MethodSignature methodInterfaceImplementation = + identifierFactory.getMethodSignature( + identifierFactory.getClassType("InterfaceImplementation"), + "defaultMethod", + "int", + Collections.emptyList()); + MethodSignature methodInterfaceImplementationNotInstatiated = + identifierFactory.getMethodSignature( + identifierFactory.getClassType("InterfaceImplementationNotInstatiated"), + "defaultMethod", + "int", + Collections.emptyList()); + + assertTrue( + cg.containsCall( + mainMethodSignature, + methodInterface, + getInvokableStmt(mainMethodSignature, methodInterface))); + assertTrue( + cg.containsCall( + mainMethodSignature, + methodInterfaceImplementation, + getInvokableStmt(mainMethodSignature, methodInterface))); + assertFalse( + cg.containsCall( + mainMethodSignature, + methodInterfaceNoImplementation, + getInvokableStmt(mainMethodSignature, methodInterface))); + assertTrue( + cg.containsCall( + mainMethodSignature, + methodInterfaceImplementationNotInstatiated, + getInvokableStmt(mainMethodSignature, methodInterface))); + } + @Test public void checkCallGraphDotExporter() { algorithmName = "RTA";