diff --git a/src/main/java/soot/SootMethod.java b/src/main/java/soot/SootMethod.java index d45132e0882..658fb115b62 100644 --- a/src/main/java/soot/SootMethod.java +++ b/src/main/java/soot/SootMethod.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Objects; import java.util.StringTokenizer; +import java.util.function.Consumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -415,6 +416,21 @@ public synchronized void setActiveBody(Body body) { * get retrieve its active body. Please call {@link SootClass#setApplicationClass()} on the relevant class. */ public Body retrieveActiveBody() { + return retrieveActiveBody((b) -> { }); + } + + /** + * Returns the active body if present, else constructs an active body, calls the consumer and returns the body afterward. + * + * If you called Scene.v().loadClassAndSupport() for a class yourself, it will not be an application class, so you cannot + * get retrieve its active body. Please call {@link SootClass#setApplicationClass()} on the relevant class. + * + * @param consumer Consumer that takes in the body of the method. The consumer is only invoked if the current + * invocation constructs a new body and is guaranteed to terminate before the body is available + * to other threads. + * @return active body of the method + */ + public Body retrieveActiveBody(Consumer consumer) { // Retrieve the active body so thread changes do not affect the // synchronization between if the body exists and the returned body. // This is a quick check just in case the activeBody exists. @@ -445,6 +461,11 @@ public Body retrieveActiveBody() { // Method sources are not expected to be thread safe activeBody = ms.getBody(this, "jb"); + + // Call the consumer such that clients can update any data structures, caches, etc. + // atomically before the body is available to other threads. + consumer.accept(activeBody); + setActiveBody(activeBody); // If configured, we drop the method source to save memory diff --git a/src/main/java/soot/jimple/toolkits/ide/icfg/AbstractJimpleBasedICFG.java b/src/main/java/soot/jimple/toolkits/ide/icfg/AbstractJimpleBasedICFG.java index 2e13bb535aa..69377aa1160 100644 --- a/src/main/java/soot/jimple/toolkits/ide/icfg/AbstractJimpleBasedICFG.java +++ b/src/main/java/soot/jimple/toolkits/ide/icfg/AbstractJimpleBasedICFG.java @@ -25,18 +25,17 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; -import heros.DontSynchronize; import heros.SynchronizedBy; import heros.solver.IDESolver; import java.util.Collection; import java.util.Collections; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import soot.Body; import soot.PatchingChain; @@ -53,7 +52,7 @@ public abstract class AbstractJimpleBasedICFG implements BiDiInterproceduralCFG< protected final boolean enableExceptions; - @DontSynchronize("written by single thread; read afterwards") + @SynchronizedBy("thread-safe data structure") private final Map unitToOwner = createUnitToOwnerMap(); @SynchronizedBy("by use of synchronized LoadingCache class") @@ -87,8 +86,13 @@ public AbstractJimpleBasedICFG() { this(true); } + /** + * Creates a new map used for the unitToOwner map. Must be thread-safe. + * + * @return a new thread-safe map + */ protected Map createUnitToOwnerMap() { - return new LinkedHashMap(); + return new ConcurrentHashMap<>(); } public AbstractJimpleBasedICFG(boolean enableExceptions) { @@ -242,10 +246,14 @@ public Set getCallsFromWithin(SootMethod m) { public void initializeUnitToOwner(SootMethod m) { if (m.hasActiveBody()) { Body b = m.getActiveBody(); - PatchingChain units = b.getUnits(); - for (Unit unit : units) { - unitToOwner.put(unit, b); - } + initializeUnitToOwner(b); + } + } + + public void initializeUnitToOwner(Body b) { + PatchingChain units = b.getUnits(); + for (Unit unit : units) { + unitToOwner.put(unit, b); } }