Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement call graph pruning feature #1025

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -59,10 +60,14 @@ public abstract class AbstractCallGraphAlgorithm implements CallGraphAlgorithm {

private static final Logger logger = LoggerFactory.getLogger(AbstractCallGraphAlgorithm.class);

private BiPredicate<SootMethod, InvokableStmt> 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;
}

/**
Expand Down Expand Up @@ -232,7 +237,7 @@ protected void resolveAllCallsFromSourceMethod(
.map(Stmt::asInvokableStmt)
.forEach(
stmt ->
resolveCall(sourceMethod, stmt)
(boundFunction.test(sourceMethod, stmt) ? resolveCall(sourceMethod, stmt) : Stream.<MethodSignature> empty())
.forEach(
targetMethod ->
addCallToCG(
Expand Down Expand Up @@ -572,4 +577,25 @@ public static Optional<SootMethod> findConcreteMethod(
+ " and in its superclasses and interfaces");
return Optional.empty();
}

/**
* @return the bound function, see @setBoundFunction
*/
public BiPredicate<SootMethod, InvokableStmt> 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<SootMethod, InvokableStmt> boundFunction)
{
this.boundFunction = boundFunction == null ? (method, statement) -> true : boundFunction;
}
}
180 changes: 180 additions & 0 deletions sootup.tests/src/test/java/sootup/tests/CallGraphTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -49,6 +51,11 @@ private JavaView createViewForClassPath(String classPath) {
}

CallGraph loadCallGraph() {
return loadCallGraph(null);
}

CallGraph loadCallGraph(BiPredicate<SootMethod, InvokableStmt> 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);
Expand All @@ -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);
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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";
Expand Down