sootup.analysis.interprocedural.icfg.JimpleBasedInterproceduralCFG Maven / Gradle / Ivy
package sootup.analysis.interprocedural.icfg;
/*-
* #%L
* Soot - a J*va Optimization Framework
* %%
* Copyright (C) 2022 Kadiray Karakaya and others
* %%
* 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
* .
* #L%
*/
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import heros.DontSynchronize;
import heros.InterproceduralCFG;
import heros.SynchronizedBy;
import heros.ThreadSafe;
import heros.solver.IDESolver;
import java.util.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sootup.callgraph.CallGraph;
import sootup.callgraph.CallGraphAlgorithm;
import sootup.callgraph.ClassHierarchyAnalysisAlgorithm;
import sootup.core.graph.StmtGraph;
import sootup.core.jimple.common.stmt.Stmt;
import sootup.core.model.SootMethod;
import sootup.core.signatures.MethodSignature;
import sootup.core.views.View;
/**
* Default implementation for the {@link InterproceduralCFG} interface. Includes all statements
* reachable from entrypoints through explicit call statements or through calls to {@link
* Thread#start()}.
*
* This class is designed to be thread safe, and subclasses of this class must be designed in a
* thread-safe way, too.
*/
@ThreadSafe
public class JimpleBasedInterproceduralCFG extends AbstractJimpleBasedICFG {
protected static final Logger logger =
LoggerFactory.getLogger(JimpleBasedInterproceduralCFG.class);
private final MethodSignature mainMethodSignature;
protected boolean includeReflectiveCalls;
@DontSynchronize("readonly")
protected final CallGraph cg;
protected CacheLoader> loaderUnitToCallees =
new CacheLoader>() {
@Nonnull
@Override
public Collection load(Stmt stmt) {
ArrayList res = new ArrayList<>();
MethodSignature methodSignature = stmt.getInvokeExpr().getMethodSignature();
Optional extends SootMethod> smOpt = view.getMethod(methodSignature);
if (smOpt.isPresent()) {
SootMethod sm = smOpt.get();
if (sm.hasBody()) {
res.add(sm);
} else {
logger.error(
"Method {} is referenced but has no body!", sm.getSignature(), new Exception());
}
}
res.trimToSize();
return res;
}
};
@SynchronizedBy("by use of synchronized LoadingCache class")
protected final LoadingCache> stmtToCallees =
IDESolver.DEFAULT_CACHE_BUILDER.build(loaderUnitToCallees);
protected CacheLoader> loaderMethodToCallers =
new CacheLoader>() {
@Nonnull
@Override
public Collection load(SootMethod method) {
ArrayList res = new ArrayList<>();
// only retain callers that are explicit call sites or
// Thread.start()
Set callsToMethod = cg.callsTo(method.getSignature());
for (MethodSignature methodSignature : callsToMethod) {
Stmt stmt = filterEdgeAndGetCallerStmt(methodSignature);
if (stmt != null) {
res.add(stmt);
}
}
res.trimToSize();
return res;
}
@Nullable
private Stmt filterEdgeAndGetCallerStmt(@Nonnull MethodSignature methodSignature) {
Set> callEdges =
CGEdgeUtil.getCallEdges(view, cg);
for (Pair callEdge : callEdges) {
CalleeMethodSignature callee = callEdge.getValue();
if (callee.getMethodSignature().equals(methodSignature)) {
CGEdgeUtil.CallGraphEdgeType edgeType = callee.getEdgeType();
if (edgeType.isExplicit()
|| edgeType.isFake()
|| edgeType.isClinit()
|| (includeReflectiveCalls && edgeType.isReflection())) {
return callee.getSourceStmt();
}
}
}
return null;
}
};
@SynchronizedBy("by use of synchronized LoadingCache class")
protected final LoadingCache> methodToCallers =
IDESolver.DEFAULT_CACHE_BUILDER.build(loaderMethodToCallers);
public JimpleBasedInterproceduralCFG(
View view,
MethodSignature mainMethodSignature,
boolean enableExceptions,
boolean includeReflectiveCalls) {
super(enableExceptions);
this.includeReflectiveCalls = includeReflectiveCalls;
this.view = view;
this.mainMethodSignature = mainMethodSignature;
cg = initCallGraph();
initializeStmtToOwner();
}
public String buildICFGGraph(CallGraph callGraph) {
Map> signatureToStmtGraph = new LinkedHashMap<>();
computeAllCalls(mainMethodSignature, signatureToStmtGraph, callGraph);
return ICFGDotExporter.buildICFGGraph(signatureToStmtGraph, view, callGraph);
}
public void computeAllCalls(
MethodSignature methodSignature,
Map> signatureToStmtGraph,
CallGraph callGraph) {
ArrayList visitedMethods = new ArrayList<>();
computeAllCalls(methodSignature, signatureToStmtGraph, callGraph, visitedMethods);
}
private void computeAllCalls(
MethodSignature methodSignature,
Map> signatureToStmtGraph,
CallGraph callGraph,
List 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;
if (methodOpt.isPresent()) {
SootMethod sootMethod = methodOpt.get();
if (sootMethod.hasBody()) {
StmtGraph> stmtGraph = sootMethod.getBody().getStmtGraph();
signatureToStmtGraph.put(methodSignature, stmtGraph);
}
}
callGraph.callsFrom(methodSignature).stream()
.filter(methodSignature1 -> !visitedMethods.contains(methodSignature1))
.forEach(
nextMethodSignature ->
computeAllCalls(
nextMethodSignature, signatureToStmtGraph, callGraph, visitedMethods));
}
private CallGraph initCallGraph() {
CallGraphAlgorithm cga = new ClassHierarchyAnalysisAlgorithm(view);
return cga.initialize(Collections.singletonList(mainMethodSignature));
}
protected void initializeStmtToOwner() {
for (MethodSignature methodSignature : cg.getMethodSignatures()) {
final Optional extends SootMethod> methodOpt = view.getMethod(methodSignature);
methodOpt.ifPresent(this::initializeStmtToOwner);
}
}
@Override
public Collection getCalleesOfCallAt(@Nonnull Stmt u) {
return stmtToCallees.getUnchecked(u);
}
@Override
public Collection getCallersOf(@Nonnull SootMethod m) {
return methodToCallers.getUnchecked(m);
}
public static Set> getCallEdges(
@Nonnull View view, @Nonnull CallGraph cg) {
Set methodSigs = cg.getMethodSignatures();
Set> callEdges = new HashSet<>();
for (MethodSignature caller : methodSigs) {
Optional extends SootMethod> methodOpt = view.getMethod(caller);
if (methodOpt.isPresent()) {
final SootMethod method = methodOpt.get();
if (method.hasBody()) {
for (Stmt s : method.getBody().getStmtGraph().getNodes()) {
if (s.containsInvokeExpr()) {
CalleeMethodSignature callee =
new CalleeMethodSignature(
s.getInvokeExpr().getMethodSignature(),
CGEdgeUtil.findCallGraphEdgeType(s.getInvokeExpr()),
s);
callEdges.add(new ImmutablePair<>(caller, callee));
}
}
}
}
}
return callEdges;
}
}