Skip to content

Commit

Permalink
Merge pull request #734 from soot-oss/improve/CallgraphRuntime
Browse files Browse the repository at this point in the history
Improve CHA runtime and improve InvokeInterface resolving
  • Loading branch information
JonasKlauke authored Nov 20, 2023
2 parents 23503d7 + 0ddd798 commit 208413c
Show file tree
Hide file tree
Showing 104 changed files with 927 additions and 1,192 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,30 @@
package sootup.analysis.interprocedural.icfg;

/*-
* #%L
* Soot - a J*va Optimization Framework
* %%
* Copyright (C) 2022-2023 Palaniappan Muthuraman, Jonas Klauke
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 2.1 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-2.1.html>.
* #L%
*/

import java.util.*;
import java.util.stream.Collectors;
import sootup.callgraph.CallGraph;
import sootup.core.graph.BasicBlock;
import sootup.core.graph.StmtGraph;
import sootup.core.jimple.common.expr.JNewExpr;
Expand All @@ -11,19 +34,20 @@
import sootup.core.model.SootMethod;
import sootup.core.signatures.MethodSignature;
import sootup.core.signatures.MethodSubSignature;
import sootup.core.typehierarchy.MethodDispatchResolver;
import sootup.core.types.VoidType;
import sootup.core.util.DotExporter;
import sootup.core.views.View;

public class ICFGDotExporter {

public static String buildICFGGraph(
Map<MethodSignature, StmtGraph> signatureToStmtGraph, View<? extends SootClass<?>> view) {
Map<MethodSignature, StmtGraph> signatureToStmtGraph,
View<? extends SootClass<?>> view,
CallGraph callGraph) {
final StringBuilder sb = new StringBuilder();
DotExporter.buildDiGraphObject(sb);
Map<Integer, MethodSignature> calls;
calls = computeCalls(signatureToStmtGraph, view);
calls = computeCalls(signatureToStmtGraph, view, callGraph);
for (Map.Entry<MethodSignature, StmtGraph> entry : signatureToStmtGraph.entrySet()) {
String graph = DotExporter.buildGraph(entry.getValue(), true, calls, entry.getKey());
sb.append(graph + "\n");
Expand All @@ -37,9 +61,13 @@ public static String buildICFGGraph(
* methods.
*/
public static Map<Integer, MethodSignature> computeCalls(
Map<MethodSignature, StmtGraph> stmtGraphSet, View<? extends SootClass<?>> view) {
Map<MethodSignature, StmtGraph> stmtGraphSet,
View<? extends SootClass<?>> view,
CallGraph callgraph) {
Map<Integer, MethodSignature> calls = new HashMap<>();
for (StmtGraph stmtGraph : stmtGraphSet.values()) {
for (Map.Entry<MethodSignature, StmtGraph> entry : stmtGraphSet.entrySet()) {
StmtGraph stmtGraph = entry.getValue();
MethodSignature source = entry.getKey();
Collection<? extends BasicBlock<?>> blocks;
try {
blocks = stmtGraph.getBlocksSorted();
Expand All @@ -50,11 +78,11 @@ public static Map<Integer, MethodSignature> computeCalls(
List<Stmt> stmts = block.getStmts();
for (Stmt stmt : stmts) {
if (stmt.containsInvokeExpr()) {
MethodSignature methodSignature = stmt.getInvokeExpr().getMethodSignature();
MethodSignature target = stmt.getInvokeExpr().getMethodSignature();
int hashCode = stmt.hashCode();
calls.put(hashCode, methodSignature);
calls.put(hashCode, target);
// compute all the classes that are made to the subclasses as well
connectEdgesToSubClasses(methodSignature, view, calls);
connectEdgesToSubClasses(source, target, view, calls, callgraph);
} else if (stmt instanceof JAssignStmt) {
JAssignStmt jAssignStmt = (JAssignStmt) stmt;
Integer currentHashCode = stmt.hashCode();
Expand Down Expand Up @@ -85,44 +113,40 @@ public static Map<Integer, MethodSignature> computeCalls(
}

public static Set<MethodSignature> getMethodSignatureInSubClass(
MethodSignature targetMethodSignature, View<? extends SootClass<?>> view) {
try {
return MethodDispatchResolver.resolveAllDispatches(view, targetMethodSignature).stream()
.map(
methodSignature ->
MethodDispatchResolver.resolveConcreteDispatch(view, methodSignature))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toSet());
} catch (Exception e) {
return null;
MethodSignature source, MethodSignature target, CallGraph callGraph) {
if (!callGraph.containsMethod(source) || !callGraph.containsMethod(target)) {
return Collections.emptySet();
}
return callGraph.callsFrom(source).stream()
.filter(
methodSignature -> methodSignature.getSubSignature().equals(target.getSubSignature()))
.collect(Collectors.toSet());
}

public static void connectEdgesToSubClasses(
MethodSignature methodSignature,
MethodSignature source,
MethodSignature target,
View<? extends SootClass<?>> view,
Map<Integer, MethodSignature> calls) {
Map<Integer, MethodSignature> calls,
CallGraph callgraph) {
Set<MethodSignature> methodSignatureInSubClass =
getMethodSignatureInSubClass(methodSignature, view);
if (methodSignatureInSubClass != null) {
methodSignatureInSubClass.forEach(
subclassmethodSignature -> {
Optional<? extends SootMethod> method = view.getMethod(methodSignature);
MethodSignature initMethod =
new MethodSignature(
subclassmethodSignature.getDeclClassType(),
new MethodSubSignature(
"<init>", Collections.emptyList(), VoidType.getInstance()));
if (method.isPresent()
&& !subclassmethodSignature.toString().equals(initMethod.toString())) {
if (method.get().hasBody()) {
calls.put(
method.get().getBody().getStmtGraph().getStartingStmt().hashCode(),
subclassmethodSignature);
}
getMethodSignatureInSubClass(source, target, callgraph);
methodSignatureInSubClass.forEach(
subclassmethodSignature -> {
Optional<? extends SootMethod> method = view.getMethod(target);
MethodSignature initMethod =
new MethodSignature(
subclassmethodSignature.getDeclClassType(),
new MethodSubSignature(
"<init>", Collections.emptyList(), VoidType.getInstance()));
if (method.isPresent()
&& !subclassmethodSignature.toString().equals(initMethod.toString())) {
if (method.get().hasBody()) {
calls.put(
method.get().getBody().getStmtGraph().getStartingStmt().hashCode(),
subclassmethodSignature);
}
});
}
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,23 @@ public JimpleBasedInterproceduralCFG(
public String buildICFGGraph(CallGraph callGraph) {
Map<MethodSignature, StmtGraph> signatureToStmtGraph = new LinkedHashMap<>();
computeAllCalls(mainMethodSignature, signatureToStmtGraph, callGraph);
return ICFGDotExporter.buildICFGGraph(signatureToStmtGraph, view);
return ICFGDotExporter.buildICFGGraph(signatureToStmtGraph, view, callGraph);
}

public void computeAllCalls(
MethodSignature methodSignature,
Map<MethodSignature, StmtGraph> signatureToStmtGraph,
CallGraph callGraph) {
ArrayList<MethodSignature> visitedMethods = new ArrayList<>();
computeAllCalls(methodSignature, signatureToStmtGraph, callGraph, visitedMethods);
}

private void computeAllCalls(
MethodSignature methodSignature,
Map<MethodSignature, StmtGraph> signatureToStmtGraph,
CallGraph callGraph,
List<MethodSignature> visitedMethods) {
visitedMethods.add(methodSignature);
final Optional<? extends SootMethod> methodOpt = view.getMethod(methodSignature);
// return if the methodSignature is already added to the hashMap to avoid stackoverflow error.
if (signatureToStmtGraph.containsKey(methodSignature)) return;
Expand All @@ -169,11 +179,12 @@ public void computeAllCalls(
signatureToStmtGraph.put(methodSignature, stmtGraph);
}
}
callGraph
.callsFrom(methodSignature)
callGraph.callsFrom(methodSignature).stream()
.filter(methodSignature1 -> !visitedMethods.contains(methodSignature1))
.forEach(
nextMethodSignature ->
computeAllCalls(nextMethodSignature, signatureToStmtGraph, callGraph));
computeAllCalls(
nextMethodSignature, signatureToStmtGraph, callGraph, visitedMethods));
}

private CallGraph initCallGraph() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ public String edgesFromCallGraph(
Map<MethodSignature, StmtGraph> signatureToStmtGraph = new LinkedHashMap<>();
icfg.computeAllCalls(methodSignature, signatureToStmtGraph, callGraph);
Map<Integer, MethodSignature> calls;
calls = ICFGDotExporter.computeCalls(signatureToStmtGraph, view);
calls = ICFGDotExporter.computeCalls(signatureToStmtGraph, view, callGraph);
final Optional<? extends SootMethod> methodOpt = view.getMethod(methodSignature);
if (methodOpt.isPresent()) {
SootMethod sootMethod = methodOpt.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import javax.annotation.Nonnull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sootup.core.IdentifierFactory;
import sootup.core.jimple.basic.Value;
import sootup.core.jimple.common.expr.AbstractInvokeExpr;
import sootup.core.jimple.common.expr.JStaticInvokeExpr;
Expand All @@ -40,6 +41,8 @@
import sootup.core.model.SootMethod;
import sootup.core.signatures.MethodSignature;
import sootup.core.signatures.MethodSubSignature;
import sootup.core.typehierarchy.HierarchyComparator;
import sootup.core.typehierarchy.TypeHierarchy;
import sootup.core.types.ClassType;
import sootup.core.views.View;
import sootup.java.core.JavaIdentifierFactory;
Expand Down Expand Up @@ -143,6 +146,11 @@ final void processWorkList(
// skip if already processed
if (processed.contains(currentMethodSignature)) continue;

// skip if library class
SootClass<?> currentClass =
view.getClass(currentMethodSignature.getDeclClassType()).orElse(null);
if (currentClass == null || currentClass.isLibraryClass()) continue;

// perform pre-processing if needed
preProcessingMethod(view, currentMethodSignature, workList, cg);

Expand All @@ -151,9 +159,7 @@ final void processWorkList(

// transform the method signature to the actual SootMethod
SootMethod currentMethod =
view.getClass(currentMethodSignature.getDeclClassType())
.flatMap(c -> c.getMethod(currentMethodSignature.getSubSignature()))
.orElse(null);
currentClass.getMethod(currentMethodSignature.getSubSignature()).orElse(null);

// get all call targets of invocations in the method body
Stream<MethodSignature> invocationTargets = resolveAllCallsFromSourceMethod(currentMethod);
Expand Down Expand Up @@ -276,47 +282,6 @@ protected Stream<MethodSignature> resolveAllStaticInitializerCallsFromSourceMeth
.map(SootClassMember::getSignature);
}

/**
* searches the method object in the given hierarchy
*
* @param view it contains all classes
* @param sig the signature of the searched method
* @param <T> the generic type of the searched method object
* @return the found method object, or null if the method was not found.
*/
protected final <T extends Method> T findMethodInHierarchy(
@Nonnull View<? extends SootClass<?>> view, @Nonnull MethodSignature sig) {
Optional<? extends SootClass<?>> optSc = view.getClass(sig.getDeclClassType());

if (optSc.isPresent()) {
SootClass<?> sc = optSc.get();

List<ClassType> superClasses = view.getTypeHierarchy().superClassesOf(sc.getType());
Set<ClassType> interfaces = view.getTypeHierarchy().implementedInterfacesOf(sc.getType());
superClasses.addAll(interfaces);

for (ClassType superClassType : superClasses) {
Optional<? extends SootClass<?>> superClassOpt = view.getClass(superClassType);
if (superClassOpt.isPresent()) {
SootClass<?> superClass = superClassOpt.get();
Optional<? extends SootMethod> methodOpt = superClass.getMethod(sig.getSubSignature());
if (methodOpt.isPresent()) {
return (T) methodOpt.get();
}
}
}
logger.warn(
"Could not find \""
+ sig.getSubSignature()
+ "\" in "
+ sig.getDeclClassType().getClassName()
+ " and in its superclasses");
} else {
logger.trace("Could not find \"" + sig.getDeclClassType() + "\" in view");
}
return null;
}

/**
* This method enables optional pre-processing of a method in the call graph algorithm
*
Expand Down Expand Up @@ -438,7 +403,7 @@ public MethodSignature findMainMethod() {
"There are more than 1 main method present.\n Below main methods are found: \n"
+ mainMethods
+ "\n initialize() method can be used if only one main method exists. \n You can specify these main methods as entry points by passing them as parameter to initialize method.");
} else if (mainMethods.size() == 0) {
} else if (mainMethods.isEmpty()) {
throw new RuntimeException(
"No main method is present in the input programs. initialize() method can be used if only one main method exists in the input program and that should be used as entry point for call graph. \n Please specify entry point as a parameter to initialize method.");
}
Expand All @@ -458,4 +423,87 @@ public MethodSignature findMainMethod() {
@Nonnull
protected abstract Stream<MethodSignature> resolveCall(
SootMethod method, AbstractInvokeExpr invokeExpr);

/**
* Searches for the signature of the method that is the concrete implementation of <code>m</code>.
* This is done by checking each superclass and the class itself for whether it contains the
* concrete implementation.
*/
@Nonnull
public static Optional<MethodSignature> resolveConcreteDispatch(
View<? extends SootClass<?>> view, MethodSignature m) {
Optional<? extends SootMethod> methodOp = findConcreteMethod(view, m);
if (methodOp.isPresent()) {
SootMethod method = methodOp.get();
if (method.isAbstract()) {
return Optional.empty();
}
return Optional.of(method.getSignature());
}
return Optional.empty();
}

/**
* searches the method object in the given hierarchy
*
* @param view it contains all classes
* @param sig the signature of the searched method
* @return the found method object, or null if the method was not found.
*/
public static Optional<? extends SootMethod> findConcreteMethod(
@Nonnull View<? extends SootClass<?>> view, @Nonnull MethodSignature sig) {
IdentifierFactory identifierFactory = view.getIdentifierFactory();
SootClass<?> startclass = view.getClass(sig.getDeclClassType()).orElse(null);
if (startclass == null) {
logger.warn(
"Could not find \""
+ sig.getDeclClassType()
+ "\" of method"
+ sig
+ " to resolve the concrete method");
return Optional.empty();
}
Optional<? extends SootMethod> startMethod = startclass.getMethod(sig.getSubSignature());
if (startMethod.isPresent()) {
return startMethod;
}
TypeHierarchy typeHierarchy = view.getTypeHierarchy();

List<ClassType> superClasses = typeHierarchy.superClassesOf(sig.getDeclClassType());
for (ClassType superClassType : superClasses) {
Optional<? extends SootMethod> method =
view.getMethod(
identifierFactory.getMethodSignature(superClassType, sig.getSubSignature()));
if (method.isPresent()) {
return method;
}
}
Set<ClassType> interfaces = typeHierarchy.implementedInterfacesOf(sig.getDeclClassType());
// interface1 is a sub-interface of interface2
// interface1 is a super-interface of interface2
// due to multiple inheritance in interfaces
final HierarchyComparator hierarchyComparator = new HierarchyComparator(view);
Optional<? extends SootMethod> defaultMethod =
interfaces.stream()
.map(
classType ->
view.getMethod(
identifierFactory.getMethodSignature(classType, sig.getSubSignature())))
.filter(Optional::isPresent)
.map(Optional::get)
.min(
(m1, m2) ->
hierarchyComparator.compare(
m1.getDeclaringClassType(), m2.getDeclaringClassType()));
if (defaultMethod.isPresent()) {
return defaultMethod;
}
logger.warn(
"Could not find \""
+ sig.getSubSignature()
+ "\" in "
+ sig.getDeclClassType().getClassName()
+ " and in its superclasses and interfaces");
return Optional.empty();
}
}
Loading

0 comments on commit 208413c

Please sign in to comment.