Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package soot.jimple.toolkits.callgraph;
/*-
* #%L
* Soot - a J*va Optimization Framework
* %%
* Copyright (C) 2003 Ondrej Lhotak
* %%
* 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 java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import soot.AnySubType;
import soot.ArrayType;
import soot.Body;
import soot.BooleanType;
import soot.ByteType;
import soot.CharType;
import soot.Context;
import soot.DoubleType;
import soot.EntryPoints;
import soot.FastHierarchy;
import soot.FloatType;
import soot.IntType;
import soot.Kind;
import soot.Local;
import soot.LongType;
import soot.MethodContext;
import soot.MethodOrMethodContext;
import soot.NullType;
import soot.PackManager;
import soot.PhaseOptions;
import soot.PrimType;
import soot.RefLikeType;
import soot.RefType;
import soot.Scene;
import soot.SceneTransformer;
import soot.ShortType;
import soot.SootClass;
import soot.SootMethod;
import soot.SootMethodRef;
import soot.Transform;
import soot.Type;
import soot.Unit;
import soot.Value;
import soot.javaToJimple.LocalGenerator;
import soot.jimple.AssignStmt;
import soot.jimple.DynamicInvokeExpr;
import soot.jimple.FieldRef;
import soot.jimple.InstanceInvokeExpr;
import soot.jimple.InvokeExpr;
import soot.jimple.InvokeStmt;
import soot.jimple.Jimple;
import soot.jimple.NewArrayExpr;
import soot.jimple.NewExpr;
import soot.jimple.NewMultiArrayExpr;
import soot.jimple.NullConstant;
import soot.jimple.SpecialInvokeExpr;
import soot.jimple.StaticFieldRef;
import soot.jimple.StaticInvokeExpr;
import soot.jimple.Stmt;
import soot.jimple.StringConstant;
import soot.jimple.VirtualInvokeExpr;
import soot.jimple.spark.pag.AllocDotField;
import soot.jimple.spark.pag.PAG;
import soot.jimple.toolkits.annotation.nullcheck.NullnessAnalysis;
import soot.jimple.toolkits.callgraph.ConstantArrayAnalysis.ArrayTypes;
import soot.jimple.toolkits.reflection.ReflectionTraceInfo;
import soot.options.CGOptions;
import soot.options.Options;
import soot.options.SparkOptions;
import soot.toolkits.graph.ExceptionalUnitGraph;
import soot.util.HashMultiMap;
import soot.util.LargeNumberedMap;
import soot.util.MultiMap;
import soot.util.NumberedString;
import soot.util.SmallNumberedMap;
import soot.util.queue.ChunkedQueue;
import soot.util.queue.QueueReader;
/**
* Models the call graph.
*
* @author Ondrej Lhotak
*/
public final class OnFlyCallGraphBuilder {
private static final Logger logger = LoggerFactory.getLogger(OnFlyCallGraphBuilder.class);
private static final PrimType[] CHAR_NARROWINGS = new PrimType[] { CharType.v() };
private static final PrimType[] INT_NARROWINGS
= new PrimType[] { IntType.v(), CharType.v(), ShortType.v(), ByteType.v(), ShortType.v() };
private static final PrimType[] SHORT_NARROWINGS = new PrimType[] { ShortType.v(), ByteType.v() };
private static final PrimType[] LONG_NARROWINGS
= new PrimType[] { LongType.v(), IntType.v(), CharType.v(), ShortType.v(), ByteType.v(), ShortType.v() };
private static final ByteType[] BYTE_NARROWINGS = new ByteType[] { ByteType.v() };
private static final PrimType[] FLOAT_NARROWINGS = new PrimType[] { FloatType.v(), LongType.v(), IntType.v(), CharType.v(),
ShortType.v(), ByteType.v(), ShortType.v(), };
private static final PrimType[] BOOLEAN_NARROWINGS = new PrimType[] { BooleanType.v() };
private static final PrimType[] DOUBLE_NARROWINGS = new PrimType[] { DoubleType.v(), FloatType.v(), LongType.v(),
IntType.v(), CharType.v(), ShortType.v(), ByteType.v(), ShortType.v(), };
protected final NumberedString sigFinalize = Scene.v().getSubSigNumberer().findOrAdd("void finalize()");
protected final NumberedString sigInit = Scene.v().getSubSigNumberer().findOrAdd("void ()");
protected final NumberedString sigStart = Scene.v().getSubSigNumberer().findOrAdd("void start()");
protected final NumberedString sigRun = Scene.v().getSubSigNumberer().findOrAdd("void run()");
protected final NumberedString sigExecute
= Scene.v().getSubSigNumberer().findOrAdd("android.os.AsyncTask execute(java.lang.Object[])");
protected final NumberedString sigExecutorExecute
= Scene.v().getSubSigNumberer().findOrAdd("void execute(java.lang.Runnable)");
protected final NumberedString sigHandlerPost
= Scene.v().getSubSigNumberer().findOrAdd("boolean post(java.lang.Runnable)");
protected final NumberedString sigHandlerPostAtFrontOfQueue
= Scene.v().getSubSigNumberer().findOrAdd("boolean postAtFrontOfQueue(java.lang.Runnable)");
// type based reflection resolution state
protected final NumberedString sigHandlerPostAtTime
= Scene.v().getSubSigNumberer().findOrAdd("boolean postAtTime(java.lang.Runnable,long)");
protected final NumberedString sigHandlerPostAtTimeWithToken
= Scene.v().getSubSigNumberer().findOrAdd("boolean postAtTime(java.lang.Runnable,java.lang.Object,long)");
protected final NumberedString sigHandlerPostDelayed
= Scene.v().getSubSigNumberer().findOrAdd("boolean postDelayed(java.lang.Runnable,long)");
protected final NumberedString sigHandlerSendEmptyMessage
= Scene.v().getSubSigNumberer().findOrAdd("boolean sendEmptyMessage(int)");
protected final NumberedString sigHandlerSendEmptyMessageAtTime
= Scene.v().getSubSigNumberer().findOrAdd("boolean sendEmptyMessageAtTime(int,long)");
protected final NumberedString sigHandlerSendEmptyMessageDelayed
= Scene.v().getSubSigNumberer().findOrAdd("boolean sendEmptyMessageDelayed(int,long)");
protected final NumberedString sigHandlerSendMessage
= Scene.v().getSubSigNumberer().findOrAdd("boolean postAtTime(java.lang.Runnable,long)");
protected final NumberedString sigHandlerSendMessageAtFrontOfQueue
= Scene.v().getSubSigNumberer().findOrAdd("boolean sendMessageAtFrontOfQueue(android.os.Message)");
protected final NumberedString sigHandlerSendMessageAtTime
= Scene.v().getSubSigNumberer().findOrAdd("boolean sendMessageAtTime(android.os.Message,long)");
protected final NumberedString sigHandlerSendMessageDelayed
= Scene.v().getSubSigNumberer().findOrAdd("boolean sendMessageDelayed(android.os.Message,long)");
protected final NumberedString sigHandlerHandleMessage
= Scene.v().getSubSigNumberer().findOrAdd("void handleMessage(android.os.Message)");
protected final NumberedString sigObjRun = Scene.v().getSubSigNumberer().findOrAdd("java.lang.Object run()");
protected final NumberedString sigDoInBackground
= Scene.v().getSubSigNumberer().findOrAdd("java.lang.Object doInBackground(java.lang.Object[])");
protected final NumberedString sigForName
= Scene.v().getSubSigNumberer().findOrAdd("java.lang.Class forName(java.lang.String)");
protected final RefType clRunnable = RefType.v("java.lang.Runnable");
protected final RefType clAsyncTask = RefType.v("android.os.AsyncTask");
protected final RefType clHandler = RefType.v("android.os.Handler");
/** context-insensitive stuff */
private final CallGraph cicg = Scene.v().internalMakeCallGraph();
private final HashSet analyzedMethods = new HashSet();
// end type based reflection resolution
private final LargeNumberedMap> receiverToSites
= new LargeNumberedMap>(Scene.v().getLocalNumberer()); // Local -> List(VirtualCallSite)
private final LargeNumberedMap> methodToReceivers
= new LargeNumberedMap>(Scene.v().getMethodNumberer()); // SootMethod -> List(Local)
private final LargeNumberedMap> methodToInvokeBases
= new LargeNumberedMap>(Scene.v().getMethodNumberer());
private final LargeNumberedMap> methodToInvokeArgs
= new LargeNumberedMap>(Scene.v().getMethodNumberer());
private final MultiMap baseToInvokeSite = new HashMultiMap<>();
private final MultiMap invokeArgsToInvokeSite = new HashMultiMap<>();
private final Map invokeArgsToSize = new IdentityHashMap<>();
private final MultiMap allocDotFieldToLocal = new HashMultiMap<>();
private final MultiMap reachingArgTypes = new HashMultiMap<>();
private final MultiMap reachingBaseTypes = new HashMultiMap<>();
private final SmallNumberedMap> stringConstToSites = new SmallNumberedMap>();
// Local
// ->
// List(VirtualCallSite)
private final LargeNumberedMap> methodToStringConstants
= new LargeNumberedMap>(Scene.v().getMethodNumberer()); // SootMethod -> List(Local)
private final ChunkedQueue targetsQueue = new ChunkedQueue();
private final QueueReader targets = targetsQueue.reader();
ReflectionModel reflectionModel;
private CGOptions options;
private boolean appOnly;
/** context-sensitive stuff */
private ReachableMethods rm;
private QueueReader worklist;
private ContextManager cm;
private FastHierarchy fh;
private NullnessAnalysis nullnessCache = null;
private ConstantArrayAnalysis arrayCache = null;
private SootMethod analysisKey = null;
public OnFlyCallGraphBuilder(ContextManager cm, ReachableMethods rm) {
this.cm = cm;
this.rm = rm;
worklist = rm.listener();
options = new CGOptions(PhaseOptions.v().getPhaseOptions("cg"));
if (!options.verbose()) {
logger.debug("" + "[Call Graph] For information on where the call graph may be incomplete,"
+ "use the verbose option to the cg phase.");
}
if (options.reflection_log() == null || options.reflection_log().length() == 0) {
if (options.types_for_invoke() && new SparkOptions(PhaseOptions.v().getPhaseOptions("cg.spark")).enabled()) {
reflectionModel = new TypeBasedReflectionModel();
} else {
reflectionModel = new DefaultReflectionModel();
}
} else {
reflectionModel = new TraceBasedReflectionModel();
}
this.fh = Scene.v().getOrMakeFastHierarchy();
}
public OnFlyCallGraphBuilder(ContextManager cm, ReachableMethods rm, boolean appOnly) {
this(cm, rm);
this.appOnly = appOnly;
}
public LargeNumberedMap> methodToReceivers() {
return methodToReceivers;
}
public LargeNumberedMap> methodToInvokeArgs() {
return methodToInvokeArgs;
}
public LargeNumberedMap> methodToInvokeBases() {
return methodToInvokeBases;
}
public LargeNumberedMap> methodToStringConstants() {
return methodToStringConstants;
}
public void processReachables() {
while (true) {
if (!worklist.hasNext()) {
rm.update();
if (!worklist.hasNext()) {
break;
}
}
MethodOrMethodContext momc = worklist.next();
SootMethod m = momc.method();
if (appOnly && !m.getDeclaringClass().isApplicationClass()) {
continue;
}
if (analyzedMethods.add(m)) {
processNewMethod(m);
}
processNewMethodContext(momc);
}
}
public boolean wantTypes(Local receiver) {
return receiverToSites.get(receiver) != null || baseToInvokeSite.get(receiver) != null;
}
public void addBaseType(Local base, Context context, Type ty) {
assert context == null;
final Set invokeSites = baseToInvokeSite.get(base);
if (invokeSites != null) {
if (reachingBaseTypes.put(base, ty)) {
resolveInvoke(invokeSites);
}
}
}
public void addInvokeArgType(Local argArray, Context context, Type t) {
assert context == null;
final Set invokeSites = invokeArgsToInvokeSite.get(argArray);
if (invokeSites != null) {
if (reachingArgTypes.put(argArray, t)) {
resolveInvoke(invokeSites);
}
}
}
public void setArgArrayNonDetSize(Local argArray, Context context) {
assert context == null;
final Set invokeSites = invokeArgsToInvokeSite.get(argArray);
if (invokeSites != null) {
if (invokeArgsToSize.containsKey(argArray)) {
return;
}
invokeArgsToSize.put(argArray, null);
resolveInvoke(invokeSites);
}
}
public void addPossibleArgArraySize(Local argArray, int value, Context context) {
assert context == null;
final Set invokeSites = invokeArgsToInvokeSite.get(argArray);
if (invokeSites != null) {
// non-det size
BitSet sizeSet = invokeArgsToSize.get(argArray);
if (sizeSet != null && sizeSet.isEmpty()) {
return;
} else {
if (sizeSet == null) {
invokeArgsToSize.put(argArray, sizeSet = new BitSet());
}
if (!sizeSet.get(value)) {
sizeSet.set(value);
resolveInvoke(invokeSites);
}
}
}
}
private Set resolveToClasses(Set rawTypes) {
Set toReturn = new HashSet();
for (Type ty : rawTypes) {
if (ty instanceof AnySubType) {
AnySubType anySubType = (AnySubType) ty;
RefType base = anySubType.getBase();
Set classRoots;
if (base.getSootClass().isInterface()) {
classRoots = fh.getAllImplementersOfInterface(base.getSootClass());
} else {
classRoots = Collections.singleton(base.getSootClass());
}
toReturn.addAll(getTransitiveSubClasses(classRoots));
} else if (ty instanceof ArrayType || ty instanceof RefType) {
toReturn.add(ty);
}
}
return toReturn;
}
private Collection getTransitiveSubClasses(Set classRoots) {
LinkedList worklist = new LinkedList<>(classRoots);
Set resolved = new HashSet<>();
while (!worklist.isEmpty()) {
SootClass cls = worklist.removeFirst();
if (!resolved.add(cls.getType())) {
continue;
}
worklist.addAll(fh.getSubclassesOf(cls));
}
return resolved;
}
private void resolveInvoke(Collection list) {
for (InvokeCallSite ics : list) {
Set s = reachingBaseTypes.get(ics.base());
if (s == null || s.isEmpty()) {
continue;
}
if (ics.reachingTypes() != null) {
assert ics.nullnessCode() != InvokeCallSite.MUST_BE_NULL;
resolveStaticTypes(s, ics);
continue;
}
boolean mustNotBeNull = ics.nullnessCode() == InvokeCallSite.MUST_NOT_BE_NULL;
boolean mustBeNull = ics.nullnessCode() == InvokeCallSite.MUST_BE_NULL;
// if the arg array may be null and we haven't seen a size or type
// yet, then generate nullary methods
if (mustBeNull || (ics.nullnessCode() == InvokeCallSite.MAY_BE_NULL
&& (!invokeArgsToSize.containsKey(ics.argArray()) || !reachingArgTypes.containsKey(ics.argArray())))) {
for (Type bType : resolveToClasses(s)) {
assert bType instanceof RefType;
SootClass baseClass = ((RefType) bType).getSootClass();
assert !baseClass.isInterface();
Iterator mIt = getPublicNullaryMethodIterator(baseClass);
while (mIt.hasNext()) {
SootMethod sm = mIt.next();
cm.addVirtualEdge(ics.container(), ics.stmt(), sm, Kind.REFL_INVOKE, null);
}
}
} else {
/*
* In this branch, either the invoke arg must not be null, or may be null and we have size and type information.
* Invert the above condition: ~mustBeNull && (~mayBeNull || (has-size && has-type)) => (~mustBeNull && ~mayBeNull)
* || (~mustBeNull && has-size && has-type) => mustNotBeNull || (~mustBeNull && has-types && has-size) =>
* mustNotBeNull || (mayBeNull && has-types && has-size)
*/
Set reachingTypes = reachingArgTypes.get(ics.argArray());
/*
* the path condition allows must-not-be null without type and size info. Do nothing in this case. THIS IS UNSOUND if
* default null values in an argument array are used.
*/
if (reachingTypes == null || !invokeArgsToSize.containsKey(ics.argArray())) {
assert ics.nullnessCode() == InvokeCallSite.MUST_NOT_BE_NULL : ics;
return;
}
assert reachingTypes != null && invokeArgsToSize.containsKey(ics.argArray());
BitSet methodSizes = invokeArgsToSize.get(ics.argArray());
for (Type bType : resolveToClasses(s)) {
assert bType instanceof RefLikeType;
// we do not handle static methods or array reflection
if (bType instanceof NullType || bType instanceof ArrayType) {
continue;
} else {
SootClass baseClass = ((RefType) bType).getSootClass();
Iterator mIt = getPublicMethodIterator(baseClass, reachingTypes, methodSizes, mustNotBeNull);
while (mIt.hasNext()) {
SootMethod sm = mIt.next();
cm.addVirtualEdge(ics.container(), ics.stmt(), sm, Kind.REFL_INVOKE, null);
}
}
}
}
}
}
/* End of public methods. */
private void resolveStaticTypes(Set s, InvokeCallSite ics) {
ArrayTypes at = ics.reachingTypes();
for (Type bType : resolveToClasses(s)) {
SootClass baseClass = ((RefType) bType).getSootClass();
Iterator mIt = getPublicMethodIterator(baseClass, at);
while (mIt.hasNext()) {
SootMethod sm = mIt.next();
cm.addVirtualEdge(ics.container(), ics.stmt(), sm, Kind.REFL_INVOKE, null);
}
}
}
private Iterator getPublicMethodIterator(SootClass baseClass, final ArrayTypes at) {
return new AbstractMethodIterator(baseClass) {
@Override
protected boolean acceptMethod(SootMethod m) {
if (!at.possibleSizes.contains(m.getParameterCount())) {
return false;
}
for (int i = 0; i < m.getParameterCount(); i++) {
if (at.possibleTypes[i].isEmpty()) {
continue;
}
if (!isReflectionCompatible(m.getParameterType(i), at.possibleTypes[i])) {
return false;
}
}
return true;
}
};
}
private PrimType[] narrowings(PrimType f) {
if (f instanceof IntType) {
return INT_NARROWINGS;
} else if (f instanceof ShortType) {
return SHORT_NARROWINGS;
} else if (f instanceof LongType) {
return LONG_NARROWINGS;
} else if (f instanceof ByteType) {
return BYTE_NARROWINGS;
} else if (f instanceof FloatType) {
return FLOAT_NARROWINGS;
} else if (f instanceof BooleanType) {
return BOOLEAN_NARROWINGS;
} else if (f instanceof DoubleType) {
return DOUBLE_NARROWINGS;
} else if (f instanceof CharType) {
return CHAR_NARROWINGS;
} else {
throw new RuntimeException("Unexpected primitive type: " + f);
}
}
private boolean isReflectionCompatible(Type paramType, Set reachingTypes) {
/*
* attempting to pass in a null will match any type (although attempting to pass it to a primitive arg will give an NPE)
*/
if (reachingTypes.contains(NullType.v())) {
return true;
}
if (paramType instanceof RefLikeType) {
for (Type rType : reachingTypes) {
if (fh.canStoreType(paramType, rType)) {
return true;
}
}
return false;
} else if (paramType instanceof PrimType) {
PrimType primType = (PrimType) paramType;
/*
* It appears, java reflection allows for unboxing followed by widening, so if there is a wrapper type that whose
* corresponding primitive type can be widened into the expected primitive type, we're set
*/
for (PrimType narrowings : narrowings(primType)) {
if (reachingTypes.contains(narrowings.boxedType())) {
return true;
}
}
return false;
} else {
// impossible?
return false;
}
}
private Iterator getPublicMethodIterator(final SootClass baseClass, final Set reachingTypes,
final BitSet methodSizes, final boolean mustNotBeNull) {
if (baseClass.isPhantom()) {
return Collections.emptyIterator();
}
return new AbstractMethodIterator(baseClass) {
@Override
protected boolean acceptMethod(SootMethod n) {
int nParams = n.getParameterCount();
if (methodSizes != null) {
// if the arg array can be null we have to still allow for
// nullary methods
boolean compatibleSize = methodSizes.get(nParams) || (!mustNotBeNull && nParams == 0);
if (!compatibleSize) {
return false;
}
}
List t = n.getParameterTypes();
for (Type pTy : t) {
if (!isReflectionCompatible(pTy, reachingTypes)) {
return false;
}
}
return true;
}
};
}
private Iterator getPublicNullaryMethodIterator(final SootClass baseClass) {
if (baseClass.isPhantom()) {
return Collections.emptyIterator();
}
return new AbstractMethodIterator(baseClass) {
@Override
protected boolean acceptMethod(SootMethod n) {
int nParams = n.getParameterCount();
return nParams == 0;
}
};
}
public void addType(Local receiver, Context srcContext, Type type, Context typeContext) {
FastHierarchy fh = Scene.v().getOrMakeFastHierarchy();
if (receiverToSites.get(receiver) != null) {
for (Iterator siteIt = receiverToSites.get(receiver).iterator(); siteIt.hasNext();) {
final VirtualCallSite site = siteIt.next();
if (site.kind() == Kind.THREAD && !fh.canStoreType(type, clRunnable)) {
continue;
}
if (site.kind() == Kind.EXECUTOR && !fh.canStoreType(type, clRunnable)) {
continue;
}
if (site.kind() == Kind.ASYNCTASK && !fh.canStoreType(type, clAsyncTask)) {
continue;
}
if (site.kind() == Kind.HANDLER && !fh.canStoreType(type, clHandler)) {
continue;
}
if (site.iie() instanceof SpecialInvokeExpr && site.kind != Kind.THREAD && site.kind != Kind.EXECUTOR
&& site.kind != Kind.ASYNCTASK) {
SootMethod target
= VirtualCalls.v().resolveSpecial((SpecialInvokeExpr) site.iie(), site.subSig(), site.container(), appOnly);
// if the call target resides in a phantom class then
// "target" will be null;
// simply do not add the target in that case
if (target != null) {
targetsQueue.add(target);
}
} else {
VirtualCalls.v().resolve(type, receiver.getType(), site.subSig(), site.container(), targetsQueue, appOnly);
if (!targets.hasNext() && options.resolve_all_abstract_invokes()) {
/* In the situation where we find nothing to resolve an invoke to in the first call, this
* might be because the type for the invoking object is a abstract class and the method is
* declared in a parent class. In this situation, when the abstract class has no classes
* that extend it in the scene, resolve would not find any targets for the invoke, even
* if the parent contained a possible target.
*
* This may have been by design since without a concrete class, we have no idea if the
* method in the parent class is overridden. However, the same could be said for any non
* private method in the abstract class (and these all resolve fine inside the abstract
* class even though there are no sub classes of the abstract class). This makes this
* situation a corner case.
*
* Where as, it used to not resolve any targets in this situation, I want to at least
* resolve the method in the parent class if there is one (as this is technically a
* possibility and the only information we have).
*/
VirtualCalls.v().resolveSuperType(type, receiver.getType(), site.subSig(), targetsQueue, appOnly);
}
}
while (targets.hasNext()) {
SootMethod target = targets.next();
cm.addVirtualEdge(MethodContext.v(site.container(), srcContext), site.stmt(), target, site.kind(), typeContext);
}
}
}
if (baseToInvokeSite.get(receiver) != null) {
addBaseType(receiver, srcContext, type);
}
}
public boolean wantStringConstants(Local stringConst) {
return stringConstToSites.get(stringConst) != null;
}
public void addStringConstant(Local l, Context srcContext, String constant) {
for (Iterator siteIt = (stringConstToSites.get(l)).iterator(); siteIt.hasNext();) {
final VirtualCallSite site = siteIt.next();
if (constant == null) {
if (options.verbose()) {
logger.debug("" + "Warning: Method " + site.container() + " is reachable, and calls Class.forName on a"
+ " non-constant String; graph will be incomplete!" + " Use safe-forname option for a conservative result.");
}
} else {
if (constant.length() > 0 && constant.charAt(0) == '[') {
if (constant.length() > 1 && constant.charAt(1) == 'L' && constant.charAt(constant.length() - 1) == ';') {
constant = constant.substring(2, constant.length() - 1);
} else {
continue;
}
}
if (!Scene.v().containsClass(constant)) {
if (options.verbose()) {
logger.debug("" + "Warning: Class " + constant + " is" + " a dynamic class, and you did not specify"
+ " it as such; graph will be incomplete!");
}
} else {
SootClass sootcls = Scene.v().getSootClass(constant);
if (!sootcls.isApplicationClass() && !sootcls.isPhantom()) {
sootcls.setLibraryClass();
}
for (SootMethod clinit : EntryPoints.v().clinitsOf(sootcls)) {
cm.addStaticEdge(MethodContext.v(site.container(), srcContext), site.stmt(), clinit, Kind.CLINIT);
}
}
}
}
}
public boolean wantArrayField(AllocDotField df) {
return allocDotFieldToLocal.containsKey(df);
}
public void addInvokeArgType(AllocDotField df, Context context, Type type) {
if (!allocDotFieldToLocal.containsKey(df)) {
return;
}
for (Local l : allocDotFieldToLocal.get(df)) {
addInvokeArgType(l, context, type);
}
}
public boolean wantInvokeArg(Local receiver) {
return invokeArgsToInvokeSite.containsKey(receiver);
}
public void addInvokeArgDotField(Local receiver, AllocDotField dot) {
allocDotFieldToLocal.put(dot, receiver);
}
/*
* How type based reflection resolution works:
*
* In general, for each call to invoke(), we record the local of the receiver argument and the argument array. Whenever a
* new type is added to the points to set of the receiver argument we add that type to the reachingBaseTypes and try to
* resolve the reflective method call (see addType, addBaseType, and updatedNode() in OnFlyCallGraph).
*
* For added precision, we also record the second argument to invoke. If it is always null, this means the invoke() call
* resolves only to nullary methods.
*
* When the second argument is a variable that must not be null we can narrow down the called method based on the possible
* sizes of the argument array and the types it contains. Whenever a new allocation reaches this variable we record the
* possible size of the array (by looking at the allocation site) and the possible types stored in the array (see
* updatedNode in OnFlyCallGraph in the branch wantInvokeArg()). If the size of the array isn't statically known, the
* analysis considers methods of all possible arities. In addition, we track the PAG node corresponding to the array
* contents. If a new type reaches this node, we update the possible argument types. (see propagate() in PropWorklist and
* the visitor, and updatedFieldRef in OnFlyCallGraph).
*
* For details on the method resolution process, see resolveInvoke()
*
* Finally, for cases like o.invoke(b, foo, bar, baz); it is very easy to statically determine precisely which types are in
* which argument positions. This is computed using the ConstantArrayAnalysis and are resolved using resolveStaticTypes().
*/
private void addInvokeCallSite(Stmt s, SootMethod container, InstanceInvokeExpr d) {
Local l = (Local) d.getArg(0);
Value argArray = d.getArg(1);
InvokeCallSite ics;
if (argArray instanceof NullConstant) {
ics = new InvokeCallSite(s, container, d, l);
} else {
if (analysisKey != container) {
ExceptionalUnitGraph graph = new ExceptionalUnitGraph(container.getActiveBody());
nullnessCache = new NullnessAnalysis(graph);
arrayCache = new ConstantArrayAnalysis(graph, container.getActiveBody());
analysisKey = container;
}
Local argLocal = (Local) argArray;
int nullnessCode;
if (nullnessCache.isAlwaysNonNullBefore(s, argLocal)) {
nullnessCode = InvokeCallSite.MUST_NOT_BE_NULL;
} else if (nullnessCache.isAlwaysNullBefore(s, argLocal)) {
nullnessCode = InvokeCallSite.MUST_BE_NULL;
} else {
nullnessCode = InvokeCallSite.MAY_BE_NULL;
}
if (nullnessCode != InvokeCallSite.MUST_BE_NULL && arrayCache.isConstantBefore(s, argLocal)) {
ArrayTypes reachingArgTypes = arrayCache.getArrayTypesBefore(s, argLocal);
if (nullnessCode == InvokeCallSite.MAY_BE_NULL) {
reachingArgTypes.possibleSizes.add(0);
}
ics = new InvokeCallSite(s, container, d, l, reachingArgTypes, nullnessCode);
} else {
ics = new InvokeCallSite(s, container, d, l, argLocal, nullnessCode);
invokeArgsToInvokeSite.put(argLocal, ics);
}
}
baseToInvokeSite.put(l, ics);
}
private void addVirtualCallSite(Stmt s, SootMethod m, Local receiver, InstanceInvokeExpr iie, NumberedString subSig,
Kind kind) {
List sites = receiverToSites.get(receiver);
if (sites == null) {
receiverToSites.put(receiver, sites = new ArrayList());
List receivers = methodToReceivers.get(m);
if (receivers == null) {
methodToReceivers.put(m, receivers = new ArrayList());
}
receivers.add(receiver);
}
sites.add(new VirtualCallSite(s, m, iie, subSig, kind));
}
private void processNewMethod(SootMethod m) {
if (!m.isConcrete()) {
return;
}
Body b = m.retrieveActiveBody();
getImplicitTargets(m);
findReceivers(m, b);
}
private void findReceivers(SootMethod m, Body b) {
for (final Unit u : b.getUnits()) {
final Stmt s = (Stmt) u;
if (s.containsInvokeExpr()) {
InvokeExpr ie = s.getInvokeExpr();
if (ie instanceof InstanceInvokeExpr) {
InstanceInvokeExpr iie = (InstanceInvokeExpr) ie;
Local receiver = (Local) iie.getBase();
NumberedString subSig = iie.getMethodRef().getSubSignature();
addVirtualCallSite(s, m, receiver, iie, subSig, Edge.ieToKind(iie));
if (subSig == sigStart) {
addVirtualCallSite(s, m, receiver, iie, sigRun, Kind.THREAD);
} else if (subSig == sigExecutorExecute || subSig == sigHandlerPost || subSig == sigHandlerPostAtFrontOfQueue
|| subSig == sigHandlerPostAtTime || subSig == sigHandlerPostAtTimeWithToken
|| subSig == sigHandlerPostDelayed) {
if (iie.getArgCount() > 0) {
Value runnable = iie.getArg(0);
if (runnable instanceof Local) {
addVirtualCallSite(s, m, (Local) runnable, iie, sigRun, Kind.EXECUTOR);
}
}
} else if (subSig == sigHandlerSendEmptyMessage || subSig == sigHandlerSendEmptyMessageAtTime
|| subSig == sigHandlerSendEmptyMessageDelayed || subSig == sigHandlerSendMessage
|| subSig == sigHandlerSendMessageAtFrontOfQueue || subSig == sigHandlerSendMessageAtTime
|| subSig == sigHandlerSendMessageDelayed) {
addVirtualCallSite(s, m, receiver, iie, sigHandlerHandleMessage, Kind.HANDLER);
} else if (subSig == sigExecute) {
addVirtualCallSite(s, m, receiver, iie, sigDoInBackground, Kind.ASYNCTASK);
}
} else if (ie instanceof DynamicInvokeExpr) {
if (options.verbose()) {
logger.debug("" + "WARNING: InvokeDynamic to " + ie + " not resolved during call-graph construction.");
}
} else {
SootMethod tgt = ie.getMethod();
if (tgt != null) {
addEdge(m, s, tgt);
String signature = tgt.getSignature();
if (signature
.equals("")
|| signature.equals("")
|| signature.equals("")
|| signature.equals("")) {
Local receiver = (Local) ie.getArg(0);
addVirtualCallSite(s, m, receiver, null, sigObjRun, Kind.PRIVILEGED);
}
} else {
if (!Options.v().ignore_resolution_errors()) {
throw new InternalError(
"Unresolved target " + ie.getMethod() + ". Resolution error should have occured earlier.");
}
}
}
}
}
}
private void getImplicitTargets(SootMethod source) {
final SootClass scl = source.getDeclaringClass();
if (!source.isConcrete()) {
return;
}
if (source.getSubSignature().indexOf("") >= 0) {
handleInit(source, scl);
}
Body b = source.retrieveActiveBody();
for (Unit u : b.getUnits()) {
final Stmt s = (Stmt) u;
if (s.containsInvokeExpr()) {
InvokeExpr ie = s.getInvokeExpr();
SootMethodRef methodRef = ie.getMethodRef();
switch (methodRef.declaringClass().getName()) {
case "java.lang.reflect.Method":
if (methodRef.getSubSignature().getString()
.equals("java.lang.Object invoke(java.lang.Object,java.lang.Object[])")) {
reflectionModel.methodInvoke(source, s);
}
break;
case "java.lang.Class":
if (methodRef.getSubSignature().getString().equals("java.lang.Object newInstance()")) {
reflectionModel.classNewInstance(source, s);
}
break;
case "java.lang.reflect.Constructor":
if (methodRef.getSubSignature().getString().equals("java.lang.Object newInstance(java.lang.Object[])")) {
reflectionModel.contructorNewInstance(source, s);
}
break;
}
if (methodRef.getSubSignature() == sigForName) {
reflectionModel.classForName(source, s);
}
if (ie instanceof StaticInvokeExpr) {
SootClass cl = ie.getMethodRef().declaringClass();
for (SootMethod clinit : EntryPoints.v().clinitsOf(cl)) {
addEdge(source, s, clinit, Kind.CLINIT);
}
}
}
if (s.containsFieldRef()) {
FieldRef fr = s.getFieldRef();
if (fr instanceof StaticFieldRef) {
SootClass cl = fr.getFieldRef().declaringClass();
for (SootMethod clinit : EntryPoints.v().clinitsOf(cl)) {
addEdge(source, s, clinit, Kind.CLINIT);
}
}
}
if (s instanceof AssignStmt) {
Value rhs = ((AssignStmt) s).getRightOp();
if (rhs instanceof NewExpr) {
NewExpr r = (NewExpr) rhs;
SootClass cl = r.getBaseType().getSootClass();
for (SootMethod clinit : EntryPoints.v().clinitsOf(cl)) {
addEdge(source, s, clinit, Kind.CLINIT);
}
} else if (rhs instanceof NewArrayExpr || rhs instanceof NewMultiArrayExpr) {
Type t = rhs.getType();
if (t instanceof ArrayType) {
t = ((ArrayType) t).baseType;
}
if (t instanceof RefType) {
SootClass cl = ((RefType) t).getSootClass();
for (SootMethod clinit : EntryPoints.v().clinitsOf(cl)) {
addEdge(source, s, clinit, Kind.CLINIT);
}
}
}
}
}
}
private void processNewMethodContext(MethodOrMethodContext momc) {
SootMethod m = momc.method();
Iterator it = cicg.edgesOutOf(m);
while (it.hasNext()) {
Edge e = it.next();
cm.addStaticEdge(momc, e.srcUnit(), e.tgt(), e.kind());
}
}
private void handleInit(SootMethod source, final SootClass scl) {
addEdge(source, null, scl, sigFinalize, Kind.FINALIZE);
}
private void constantForName(String cls, SootMethod src, Stmt srcUnit) {
if (cls.length() > 0 && cls.charAt(0) == '[') {
if (cls.length() > 1 && cls.charAt(1) == 'L' && cls.charAt(cls.length() - 1) == ';') {
cls = cls.substring(2, cls.length() - 1);
constantForName(cls, src, srcUnit);
}
} else {
if (!Scene.v().containsClass(cls)) {
if (options.verbose()) {
logger.warn("Class " + cls + " is" + " a dynamic class, and you did not specify"
+ " it as such; graph will be incomplete!");
}
} else {
SootClass sootcls = Scene.v().getSootClass(cls);
if (!sootcls.isPhantomClass()) {
if (!sootcls.isApplicationClass()) {
sootcls.setLibraryClass();
}
for (SootMethod clinit : EntryPoints.v().clinitsOf(sootcls)) {
addEdge(src, srcUnit, clinit, Kind.CLINIT);
}
}
}
}
}
private void addEdge(SootMethod src, Stmt stmt, SootMethod tgt, Kind kind) {
cicg.addEdge(new Edge(src, stmt, tgt, kind));
}
private void addEdge(SootMethod src, Stmt stmt, SootClass cls, NumberedString methodSubSig, Kind kind) {
SootMethod sm = cls.getMethodUnsafe(methodSubSig);
if (sm != null) {
addEdge(src, stmt, sm, kind);
}
}
private void addEdge(SootMethod src, Stmt stmt, SootMethod tgt) {
InvokeExpr ie = stmt.getInvokeExpr();
addEdge(src, stmt, tgt, Edge.ieToKind(ie));
}
public class DefaultReflectionModel implements ReflectionModel {
protected CGOptions options = new CGOptions(PhaseOptions.v().getPhaseOptions("cg"));
protected HashSet warnedAlready = new HashSet();
@Override
public void classForName(SootMethod source, Stmt s) {
List stringConstants = methodToStringConstants.get(source);
if (stringConstants == null) {
methodToStringConstants.put(source, stringConstants = new ArrayList());
}
InvokeExpr ie = s.getInvokeExpr();
Value className = ie.getArg(0);
if (className instanceof StringConstant) {
String cls = ((StringConstant) className).value;
constantForName(cls, source, s);
} else if (className instanceof Local) {
Local constant = (Local) className;
if (options.safe_forname()) {
for (SootMethod tgt : EntryPoints.v().clinits()) {
addEdge(source, s, tgt, Kind.CLINIT);
}
} else {
for (SootClass cls : Scene.v().dynamicClasses()) {
for (SootMethod clinit : EntryPoints.v().clinitsOf(cls)) {
addEdge(source, s, clinit, Kind.CLINIT);
}
}
VirtualCallSite site = new VirtualCallSite(s, source, null, null, Kind.CLINIT);
List sites = stringConstToSites.get(constant);
if (sites == null) {
stringConstToSites.put(constant, sites = new ArrayList());
stringConstants.add(constant);
}
sites.add(site);
}
}
}
@Override
public void classNewInstance(SootMethod source, Stmt s) {
if (options.safe_newinstance()) {
for (SootMethod tgt : EntryPoints.v().inits()) {
addEdge(source, s, tgt, Kind.NEWINSTANCE);
}
} else {
for (SootClass cls : Scene.v().dynamicClasses()) {
SootMethod sm = cls.getMethodUnsafe(sigInit);
if (sm != null) {
addEdge(source, s, sm, Kind.NEWINSTANCE);
}
}
if (options.verbose()) {
logger.warn("Method " + source + " is reachable, and calls Class.newInstance;" + " graph will be incomplete!"
+ " Use safe-newinstance option for a conservative result.");
}
}
}
@Override
public void contructorNewInstance(SootMethod source, Stmt s) {
if (options.safe_newinstance()) {
for (SootMethod tgt : EntryPoints.v().allInits()) {
addEdge(source, s, tgt, Kind.NEWINSTANCE);
}
} else {
for (SootClass cls : Scene.v().dynamicClasses()) {
for (SootMethod m : cls.getMethods()) {
if (m.getName().equals("")) {
addEdge(source, s, m, Kind.NEWINSTANCE);
}
}
}
if (options.verbose()) {
logger.warn("Method " + source + " is reachable, and calls Constructor.newInstance;" + " graph will be incomplete!"
+ " Use safe-newinstance option for a conservative result.");
}
}
}
@Override
public void methodInvoke(SootMethod container, Stmt invokeStmt) {
if (!warnedAlready(container)) {
if (options.verbose()) {
logger.warn("call to " + "java.lang.reflect.Method: invoke() from " + container + "; graph will be incomplete!");
}
markWarned(container);
}
}
private void markWarned(SootMethod m) {
warnedAlready.add(m);
}
private boolean warnedAlready(SootMethod m) {
return warnedAlready.contains(m);
}
}
public class TypeBasedReflectionModel extends DefaultReflectionModel {
@Override
public void methodInvoke(SootMethod container, Stmt invokeStmt) {
if (container.getDeclaringClass().isJavaLibraryClass()) {
super.methodInvoke(container, invokeStmt);
return;
}
InstanceInvokeExpr d = (InstanceInvokeExpr) invokeStmt.getInvokeExpr();
Value base = d.getArg(0);
// TODO no support for statics at the moment
// SA: Better just fall back to degraded functionality than fail
// altogether
if (!(base instanceof Local)) {
super.methodInvoke(container, invokeStmt);
return;
}
addInvokeCallSite(invokeStmt, container, d);
}
}
public class TraceBasedReflectionModel implements ReflectionModel {
protected Set guards;
protected ReflectionTraceInfo reflectionInfo;
private boolean registeredTransformation = false;
private TraceBasedReflectionModel() {
guards = new HashSet();
String logFile = options.reflection_log();
if (logFile == null) {
throw new InternalError("Trace based refection model enabled but no trace file given!?");
} else {
reflectionInfo = new ReflectionTraceInfo(logFile);
}
}
/**
* Adds an edge to all class initializers of all possible receivers of Class.forName() calls within source.
*/
@Override
public void classForName(SootMethod container, Stmt forNameInvokeStmt) {
Set classNames = reflectionInfo.classForNameClassNames(container);
if (classNames == null || classNames.isEmpty()) {
registerGuard(container, forNameInvokeStmt,
"Class.forName() call site; Soot did not expect this site to be reached");
} else {
for (String clsName : classNames) {
constantForName(clsName, container, forNameInvokeStmt);
}
}
}
/**
* Adds an edge to the constructor of the target class from this call to {@link Class#newInstance()}.
*/
@Override
public void classNewInstance(SootMethod container, Stmt newInstanceInvokeStmt) {
Set classNames = reflectionInfo.classNewInstanceClassNames(container);
if (classNames == null || classNames.isEmpty()) {
registerGuard(container, newInstanceInvokeStmt,
"Class.newInstance() call site; Soot did not expect this site to be reached");
} else {
for (String clsName : classNames) {
SootClass cls = Scene.v().getSootClass(clsName);
SootMethod constructor = cls.getMethodUnsafe(sigInit);
if (constructor != null) {
addEdge(container, newInstanceInvokeStmt, constructor, Kind.REFL_CLASS_NEWINSTANCE);
}
}
}
}
/**
* Adds a special edge of kind {@link Kind#REFL_CONSTR_NEWINSTANCE} to all possible target constructors of this call to
* {@link Constructor#newInstance(Object...)}. Those kinds of edges are treated specially in terms of how parameters are
* assigned, as parameters to the reflective call are passed into the argument array of
* {@link Constructor#newInstance(Object...)}.
*
* @see PAG#addCallTarget(Edge)
*/
@Override
public void contructorNewInstance(SootMethod container, Stmt newInstanceInvokeStmt) {
Set constructorSignatures = reflectionInfo.constructorNewInstanceSignatures(container);
if (constructorSignatures == null || constructorSignatures.isEmpty()) {
registerGuard(container, newInstanceInvokeStmt,
"Constructor.newInstance(..) call site; Soot did not expect this site to be reached");
} else {
for (String constructorSignature : constructorSignatures) {
SootMethod constructor = Scene.v().getMethod(constructorSignature);
addEdge(container, newInstanceInvokeStmt, constructor, Kind.REFL_CONSTR_NEWINSTANCE);
}
}
}
/**
* Adds a special edge of kind {@link Kind#REFL_INVOKE} to all possible target methods of this call to
* {@link Method#invoke(Object, Object...)}. Those kinds of edges are treated specially in terms of how parameters are
* assigned, as parameters to the reflective call are passed into the argument array of
* {@link Method#invoke(Object, Object...)}.
*
* @see PAG#addCallTarget(Edge)
*/
@Override
public void methodInvoke(SootMethod container, Stmt invokeStmt) {
Set methodSignatures = reflectionInfo.methodInvokeSignatures(container);
if (methodSignatures == null || methodSignatures.isEmpty()) {
registerGuard(container, invokeStmt, "Method.invoke(..) call site; Soot did not expect this site to be reached");
} else {
for (String methodSignature : methodSignatures) {
SootMethod method = Scene.v().getMethod(methodSignature);
addEdge(container, invokeStmt, method, Kind.REFL_INVOKE);
}
}
}
private void registerGuard(SootMethod container, Stmt stmt, String string) {
guards.add(new Guard(container, stmt, string));
if (options.verbose()) {
logger.debug("Incomplete trace file: Class.forName() is called in method '" + container
+ "' but trace contains no information about the receiver class of this call.");
if (options.guards().equals("ignore")) {
logger.debug("Guarding strategy is set to 'ignore'. Will ignore this problem.");
} else if (options.guards().equals("print")) {
logger.debug("Guarding strategy is set to 'print'. "
+ "Program will print a stack trace if this location is reached during execution.");
} else if (options.guards().equals("throw")) {
logger.debug("Guarding strategy is set to 'throw'. Program will throw an "
+ "Error if this location is reached during execution.");
} else {
throw new RuntimeException("Invalid value for phase option (guarding): " + options.guards());
}
}
if (!registeredTransformation) {
registeredTransformation = true;
PackManager.v().getPack("wjap").add(new Transform("wjap.guards", new SceneTransformer() {
@Override
protected void internalTransform(String phaseName, Map options) {
for (Guard g : guards) {
insertGuard(g);
}
}
}));
PhaseOptions.v().setPhaseOption("wjap.guards", "enabled");
}
}
private void insertGuard(Guard guard) {
if (options.guards().equals("ignore")) {
return;
}
SootMethod container = guard.container;
Stmt insertionPoint = guard.stmt;
if (!container.hasActiveBody()) {
logger.warn("Tried to insert guard into " + container + " but couldn't because method has no body.");
} else {
Body body = container.getActiveBody();
// exc = new Error
RefType runtimeExceptionType = RefType.v("java.lang.Error");
NewExpr newExpr = Jimple.v().newNewExpr(runtimeExceptionType);
LocalGenerator lg = new LocalGenerator(body);
Local exceptionLocal = lg.generateLocal(runtimeExceptionType);
AssignStmt assignStmt = Jimple.v().newAssignStmt(exceptionLocal, newExpr);
body.getUnits().insertBefore(assignStmt, insertionPoint);
// exc.(message)
SootMethodRef cref = runtimeExceptionType.getSootClass()
.getMethod("", Collections.singletonList(RefType.v("java.lang.String"))).makeRef();
SpecialInvokeExpr constructorInvokeExpr
= Jimple.v().newSpecialInvokeExpr(exceptionLocal, cref, StringConstant.v(guard.message));
InvokeStmt initStmt = Jimple.v().newInvokeStmt(constructorInvokeExpr);
body.getUnits().insertAfter(initStmt, assignStmt);
if (options.guards().equals("print")) {
// logger.error(exc.getMessage(), exc);
VirtualInvokeExpr printStackTraceExpr = Jimple.v().newVirtualInvokeExpr(exceptionLocal, Scene.v()
.getSootClass("java.lang.Throwable").getMethod("printStackTrace", Collections.emptyList()).makeRef());
InvokeStmt printStackTraceStmt = Jimple.v().newInvokeStmt(printStackTraceExpr);
body.getUnits().insertAfter(printStackTraceStmt, initStmt);
} else if (options.guards().equals("throw")) {
body.getUnits().insertAfter(Jimple.v().newThrowStmt(exceptionLocal), initStmt);
} else {
throw new RuntimeException("Invalid value for phase option (guarding): " + options.guards());
}
}
}
class Guard {
final SootMethod container;
final Stmt stmt;
final String message;
public Guard(SootMethod container, Stmt stmt, String message) {
this.container = container;
this.stmt = stmt;
this.message = message;
}
}
}
private abstract class AbstractMethodIterator implements Iterator {
private SootMethod next;
private SootClass currClass;
private Iterator methodIterator;
AbstractMethodIterator(SootClass baseClass) {
this.currClass = baseClass;
this.next = null;
this.methodIterator = baseClass.methodIterator();
this.findNextMethod();
}
protected void findNextMethod() {
next = null;
if (methodIterator == null) {
return;
}
do {
while (methodIterator.hasNext()) {
SootMethod n = methodIterator.next();
if (!n.isPublic()) {
continue;
}
if (n.isStatic() || n.isConstructor() || n.isStaticInitializer() || !n.isConcrete()) {
continue;
}
if (!acceptMethod(n)) {
continue;
}
next = n;
return;
}
if (currClass.hasSuperclass() && !currClass.getSuperclass().isPhantom()
&& !currClass.getSuperclass().getName().equals("java.lang.Object")) {
currClass = currClass.getSuperclass();
methodIterator = currClass.methodIterator();
continue;
} else {
methodIterator = null;
return;
}
} while (true);
}
@Override
public boolean hasNext() {
return next != null;
}
@Override
public SootMethod next() {
SootMethod toRet = next;
findNextMethod();
return toRet;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
protected abstract boolean acceptMethod(SootMethod m);
}
}