From f5d7da3542e23cf3dbfed67612c7eeeb212ea415 Mon Sep 17 00:00:00 2001 From: Bing Ren Date: Fri, 16 Aug 2024 00:47:58 +1000 Subject: [PATCH 1/2] Implement call graph pruning feature --- .../callgraph/AbstractCallGraphAlgorithm.java | 26 ++- .../test/java/sootup/tests/CallGraphTest.java | 180 ++++++++++++++++++ 2 files changed, 205 insertions(+), 1 deletion(-) diff --git a/sootup.callgraph/src/main/java/sootup/callgraph/AbstractCallGraphAlgorithm.java b/sootup.callgraph/src/main/java/sootup/callgraph/AbstractCallGraphAlgorithm.java index be599ea6adc..60210e727e7 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.BiFunction; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nonnull; @@ -59,6 +60,8 @@ public abstract class AbstractCallGraphAlgorithm implements CallGraphAlgorithm { private static final Logger logger = LoggerFactory.getLogger(AbstractCallGraphAlgorithm.class); + private BiFunction boundFunction; + @Nonnull protected final View view; protected AbstractCallGraphAlgorithm(@Nonnull View view) { @@ -232,7 +235,7 @@ protected void resolveAllCallsFromSourceMethod( .map(Stmt::asInvokableStmt) .forEach( stmt -> - resolveCall(sourceMethod, stmt) + (boundFunction == null || Boolean.TRUE.equals(boundFunction.apply(sourceMethod, stmt)) ? resolveCall(sourceMethod, stmt) : Stream. empty()) .forEach( targetMethod -> addCallToCG( @@ -586,4 +589,25 @@ public static Optional findConcreteMethod( + " and in its superclasses and interfaces"); return Optional.empty(); } + + /** + * @return the bound function, see @setBoundFunction + */ + public BiFunction 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(BiFunction boundFunction) + { + this.boundFunction = 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..033409b51e5 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.BiFunction; + 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(BiFunction 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"; From 1ff0cf6a6a000704d104e12fa072ca95f5afc2bc Mon Sep 17 00:00:00 2001 From: Bing Ren Date: Fri, 16 Aug 2024 03:42:36 +1000 Subject: [PATCH 2/2] use of a method that just returns always true instead of null lets use BiPredicate as type to reduce unnecessary AutoBoxing and simplify the condition check to just the method call --- .../callgraph/AbstractCallGraphAlgorithm.java | 18 ++++++++++-------- .../test/java/sootup/tests/CallGraphTest.java | 4 ++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/sootup.callgraph/src/main/java/sootup/callgraph/AbstractCallGraphAlgorithm.java b/sootup.callgraph/src/main/java/sootup/callgraph/AbstractCallGraphAlgorithm.java index 60210e727e7..9fc3067070e 100644 --- a/sootup.callgraph/src/main/java/sootup/callgraph/AbstractCallGraphAlgorithm.java +++ b/sootup.callgraph/src/main/java/sootup/callgraph/AbstractCallGraphAlgorithm.java @@ -23,7 +23,7 @@ */ import java.util.*; -import java.util.function.BiFunction; +import java.util.function.BiPredicate; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nonnull; @@ -60,12 +60,14 @@ public abstract class AbstractCallGraphAlgorithm implements CallGraphAlgorithm { private static final Logger logger = LoggerFactory.getLogger(AbstractCallGraphAlgorithm.class); - private BiFunction boundFunction; + 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; } /** @@ -235,7 +237,7 @@ protected void resolveAllCallsFromSourceMethod( .map(Stmt::asInvokableStmt) .forEach( stmt -> - (boundFunction == null || Boolean.TRUE.equals(boundFunction.apply(sourceMethod, stmt)) ? resolveCall(sourceMethod, stmt) : Stream. empty()) + (boundFunction.test(sourceMethod, stmt) ? resolveCall(sourceMethod, stmt) : Stream. empty()) .forEach( targetMethod -> addCallToCG( @@ -593,7 +595,7 @@ public static Optional findConcreteMethod( /** * @return the bound function, see @setBoundFunction */ - public BiFunction getBoundFunction() + public BiPredicate getBoundFunction() { return boundFunction; } @@ -606,8 +608,8 @@ public BiFunction getBoundFunction() * cost. * * @param boundFunction */ - public void setBoundFunction(BiFunction boundFunction) + public void setBoundFunction(BiPredicate boundFunction) { - this.boundFunction = 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 033409b51e5..13ed295d66f 100644 --- a/sootup.tests/src/test/java/sootup/tests/CallGraphTest.java +++ b/sootup.tests/src/test/java/sootup/tests/CallGraphTest.java @@ -5,7 +5,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.function.BiFunction; +import java.util.function.BiPredicate; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -54,7 +54,7 @@ CallGraph loadCallGraph() { return loadCallGraph(null); } - CallGraph loadCallGraph(BiFunction boundFunction) + CallGraph loadCallGraph(BiPredicate boundFunction) { double version = Double.parseDouble(System.getProperty("java.specification.version")); if (version > 1.8) {