commit GetMethod context, interpreter and selector
This commit is contained in:
parent
51d4b0e462
commit
6c5ef65d5f
|
@ -0,0 +1,116 @@
|
|||
/* Copyright (c) 2013 Michael Heilmann */
|
||||
package com.ibm.wala.analysis.reflection.ext;
|
||||
|
||||
import com.ibm.wala.analysis.typeInference.PointType;
|
||||
import com.ibm.wala.analysis.typeInference.TypeAbstraction;
|
||||
import com.ibm.wala.classLoader.IClass;
|
||||
import com.ibm.wala.ipa.callgraph.Context;
|
||||
import com.ibm.wala.ipa.callgraph.ContextItem;
|
||||
import com.ibm.wala.ipa.callgraph.ContextKey;
|
||||
import com.ibm.wala.ipa.callgraph.propagation.ConstantKey;
|
||||
import com.ibm.wala.ipa.callgraph.propagation.FilteredPointerKey;
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* A context which may only be used if the following is true:
|
||||
* - The method to be interpreted is either
|
||||
* {@link java.lang.Class#getMethod(String, Class...)} or
|
||||
* {@link java.lang.Class#getDeclaredMethod(String, Class...)}.
|
||||
* - The type of the "this" argument is known.
|
||||
* - The value of the first argument (the method name) is a constant.
|
||||
* @author
|
||||
* Michael Heilmann
|
||||
*/
|
||||
public class GetMethodContext implements Context {
|
||||
/**
|
||||
* @brief
|
||||
* The type abstraction.
|
||||
*/
|
||||
private final TypeAbstraction type;
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* The method name.
|
||||
*/
|
||||
private final ConstantKey name;
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Construct this GetMethodContext.
|
||||
* @param type
|
||||
* The type.
|
||||
* @param name
|
||||
* The name of the method.
|
||||
*/
|
||||
public GetMethodContext(TypeAbstraction type,ConstantKey name) {
|
||||
if (type == null) {
|
||||
throw new IllegalArgumentException("null == type");
|
||||
}
|
||||
this.type = type;
|
||||
if (name == null) {
|
||||
throw new IllegalArgumentException("null == name");
|
||||
}
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContextItem get(ContextKey name) {
|
||||
if (name == ContextKey.RECEIVER) {
|
||||
return type;
|
||||
} else if (name == ContextKey.PARAMETERS[0]) {
|
||||
if (type instanceof PointType) {
|
||||
IClass cls = ((PointType) type).getIClass();
|
||||
return new FilteredPointerKey.SingleClassFilter(cls);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else if (name == ContextKey.PARAMETERS[1]) {
|
||||
return new FilteredPointerKey.SingleClassFilter(this.name.getConcreteType());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GetMethodContext<" + type + ", " + name + ">";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 6367 * type.hashCode() * name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass().equals(obj.getClass())) {
|
||||
GetMethodContext other = (GetMethodContext) obj;
|
||||
return type.equals(other.type) && name.equals(other.name);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Get the type.
|
||||
* @return
|
||||
* The type.
|
||||
*/
|
||||
public TypeAbstraction getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Get the name.
|
||||
* @return
|
||||
* The name.
|
||||
*/
|
||||
public String getName() {
|
||||
return (String)name.getValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,251 @@
|
|||
/* Copyright (c) 2013 Michael Heilmann */
|
||||
package com.ibm.wala.analysis.reflection.ext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import com.ibm.wala.cfg.ControlFlowGraph;
|
||||
import com.ibm.wala.cfg.InducedCFG;
|
||||
import com.ibm.wala.classLoader.CallSiteReference;
|
||||
import com.ibm.wala.classLoader.IClass;
|
||||
import com.ibm.wala.classLoader.IMethod;
|
||||
import com.ibm.wala.classLoader.NewSiteReference;
|
||||
import com.ibm.wala.ipa.callgraph.CGNode;
|
||||
import com.ibm.wala.ipa.callgraph.propagation.SSAContextInterpreter;
|
||||
import com.ibm.wala.ipa.summaries.SyntheticIR;
|
||||
import com.ibm.wala.ssa.ConstantValue;
|
||||
import com.ibm.wala.ssa.DefUse;
|
||||
import com.ibm.wala.ssa.IR;
|
||||
import com.ibm.wala.ssa.ISSABasicBlock;
|
||||
import com.ibm.wala.ssa.SSAInstruction;
|
||||
import com.ibm.wala.ssa.SSAInstructionFactory;
|
||||
import com.ibm.wala.ssa.SSAOptions;
|
||||
import com.ibm.wala.ssa.SSAReturnInstruction;
|
||||
import com.ibm.wala.types.FieldReference;
|
||||
import com.ibm.wala.types.MethodReference;
|
||||
import com.ibm.wala.types.TypeReference;
|
||||
import com.ibm.wala.util.collections.EmptyIterator;
|
||||
import com.ibm.wala.util.collections.HashMapFactory;
|
||||
import com.ibm.wala.util.collections.HashSetFactory;
|
||||
import com.ibm.wala.util.collections.NonNullSingletonIterator;
|
||||
import com.ibm.wala.util.debug.Assertions;
|
||||
import com.ibm.wala.util.strings.Atom;
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Understands {@link com.ibm.wala.analysis.reflection.ext.GetMethodContext}.
|
||||
* @author
|
||||
* Michael Heilmann
|
||||
*/
|
||||
public class GetMethodContextInterpreter implements SSAContextInterpreter {
|
||||
/**
|
||||
* TODO
|
||||
* MH: Hard-code those in {@link com.ibm.wala.types.TypeReference}?
|
||||
*/
|
||||
public final static MethodReference GET_METHOD = MethodReference.findOrCreate(TypeReference.JavaLangClass, "getMethod",
|
||||
"(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;");
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* MH: Hard-code those in {@link com.ibm.wala.types.TypeReference}?
|
||||
*/
|
||||
public final static MethodReference GET_DECLARED_METHOD = MethodReference.findOrCreate(TypeReference.JavaLangClass,
|
||||
"getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;");
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
/*
|
||||
* @see com.ibm.wala.ipa.callgraph.propagation.SSAContextInterpreter#getIR(com.ibm.wala.ipa.callgraph.CGNode)
|
||||
*/
|
||||
@Override
|
||||
public IR getIR(CGNode node) {
|
||||
if (node == null) {
|
||||
throw new IllegalArgumentException("node is null");
|
||||
}
|
||||
assert understands(node);
|
||||
if (DEBUG) {
|
||||
System.err.println("generating IR for " + node);
|
||||
}
|
||||
IMethod method = node.getMethod();
|
||||
GetMethodContext context = (GetMethodContext) node.getContext();
|
||||
Map<Integer,ConstantValue> constants = HashMapFactory.make();
|
||||
if (method.getReference().equals(GET_METHOD)) {
|
||||
Atom name = Atom.findOrCreateAsciiAtom(context.getName());
|
||||
SSAInstruction instrs[] = makeGetMethodStatements(context,constants,name);
|
||||
return new SyntheticIR(method, context, new InducedCFG(instrs, method, context), instrs, SSAOptions.defaultOptions(), constants);
|
||||
}
|
||||
if (method.getReference().equals(GET_DECLARED_METHOD)) {
|
||||
Atom name = Atom.findOrCreateAsciiAtom(context.getName());
|
||||
SSAInstruction instrs[] = makeGetDeclaredMethodStatements(context,constants,name);
|
||||
return new SyntheticIR(method, context, new InducedCFG(instrs, method, context), instrs, SSAOptions.defaultOptions(), constants);
|
||||
}
|
||||
Assertions.UNREACHABLE("Unexpected method " + node);
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see com.ibm.wala.ipa.callgraph.propagation.SSAContextInterpreter#getNumberOfStatements(com.ibm.wala.ipa.callgraph.CGNode)
|
||||
*/
|
||||
@Override
|
||||
public int getNumberOfStatements(CGNode node) {
|
||||
assert understands(node);
|
||||
return getIR(node).getInstructions().length;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see com.ibm.wala.ipa.callgraph.propagation.rta.RTAContextInterpreter#understands(com.ibm.wala.ipa.callgraph.CGNode)
|
||||
*/
|
||||
@Override
|
||||
public boolean understands(CGNode node) {
|
||||
if (node == null) {
|
||||
throw new IllegalArgumentException("node is null");
|
||||
}
|
||||
if (!(node.getContext() instanceof GetMethodContext)) {
|
||||
return false;
|
||||
}
|
||||
MethodReference mRef = node.getMethod().getReference();
|
||||
return mRef.equals(GET_METHOD) || mRef.equals(GET_DECLARED_METHOD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<NewSiteReference> iterateNewSites(CGNode node) {
|
||||
if (node == null) {
|
||||
throw new IllegalArgumentException("node is null");
|
||||
}
|
||||
assert understands(node);
|
||||
GetMethodContext context = (GetMethodContext) node.getContext();
|
||||
TypeReference tr = context.getType().getTypeReference();
|
||||
if (tr != null) {
|
||||
return new NonNullSingletonIterator<NewSiteReference>(NewSiteReference.make(0, tr));
|
||||
}
|
||||
return EmptyIterator.instance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<CallSiteReference> iterateCallSites(CGNode node) {
|
||||
assert understands(node);
|
||||
return EmptyIterator.instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all non-constructor, non-class-initializer methods declared by a class
|
||||
* if their name is equal to the specified name.
|
||||
*/
|
||||
private Collection<IMethod> getDeclaredNormalMethods(IClass cls,Atom name) {
|
||||
Collection<IMethod> result = HashSetFactory.make();
|
||||
for (IMethod m : cls.getDeclaredMethods()) {
|
||||
if (!m.isInit() && !m.isClinit() && m.getSelector().getName().equals(name)) {
|
||||
result.add(m);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all non-constructor, non-class-initializer methods declared by a class
|
||||
* and all its superclasses if their name is equal to the specifed name.
|
||||
*/
|
||||
private Collection<IMethod> getAllNormalPublicMethods(IClass cls,Atom name) {
|
||||
Collection<IMethod> result = HashSetFactory.make();
|
||||
Collection<IMethod> allMethods = null;
|
||||
allMethods = cls.getAllMethods();
|
||||
for (IMethod m : allMethods) {
|
||||
if (!m.isInit() && !m.isClinit() && m.isPublic() && m.getSelector().getName().equals(name)) {
|
||||
result.add(m);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create statements for methods like getMethod() and getDeclaredMethod(),
|
||||
* which return a single method. This creates a return statement for each
|
||||
* possible return value, each of which is a {@link ConstantValue} for an
|
||||
* {@link IMethod}.
|
||||
*
|
||||
* @param returnValues the possible return values for this method.
|
||||
*/
|
||||
private SSAInstruction[] getParticularMethodStatements
|
||||
(
|
||||
MethodReference ref,
|
||||
Collection<IMethod> returnValues,
|
||||
GetMethodContext context,
|
||||
Map<Integer, ConstantValue> constants
|
||||
) {
|
||||
ArrayList<SSAInstruction> statements = new ArrayList<SSAInstruction>();
|
||||
int nextLocal = ref.getNumberOfParameters() + 2;
|
||||
IClass cls = context.getType().getType();
|
||||
SSAInstructionFactory insts = context.getType().getType().getClassLoader().getInstructionFactory();
|
||||
if (cls != null) {
|
||||
for (IMethod m : returnValues) {
|
||||
int c = nextLocal++;
|
||||
constants.put(c, new ConstantValue(m));
|
||||
SSAReturnInstruction R = insts.ReturnInstruction(c, false);
|
||||
statements.add(R);
|
||||
}
|
||||
} else {
|
||||
// SJF: This is incorrect. TODO: fix and enable.
|
||||
// SSAThrowInstruction t = insts.ThrowInstruction(retValue);
|
||||
// statements.add(t);
|
||||
}
|
||||
SSAInstruction[] result = new SSAInstruction[statements.size()];
|
||||
Iterator<SSAInstruction> it = statements.iterator();
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = it.next();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private SSAInstruction[] makeGetMethodStatements
|
||||
(
|
||||
GetMethodContext context,
|
||||
Map<Integer,ConstantValue> constants,
|
||||
Atom name
|
||||
) {
|
||||
IClass cls = context.getType().getType();
|
||||
if (cls == null) {
|
||||
return getParticularMethodStatements(GET_METHOD, null, context, constants);
|
||||
} else {
|
||||
return getParticularMethodStatements(GET_METHOD, getAllNormalPublicMethods(cls,name), context, constants);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create statements for getDeclaredMethod()
|
||||
*/
|
||||
private SSAInstruction[] makeGetDeclaredMethodStatements(GetMethodContext context, Map<Integer, ConstantValue> constants,Atom name) {
|
||||
IClass cls = context.getType().getType();
|
||||
if (cls == null) {
|
||||
return getParticularMethodStatements(GET_DECLARED_METHOD, null, context, constants);
|
||||
} else {
|
||||
return getParticularMethodStatements(GET_DECLARED_METHOD, getDeclaredNormalMethods(cls,name), context, constants);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean recordFactoryType(CGNode node, IClass klass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<FieldReference> iterateFieldsRead(CGNode node) {
|
||||
return EmptyIterator.instance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<FieldReference> iterateFieldsWritten(CGNode node) {
|
||||
return EmptyIterator.instance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ControlFlowGraph<SSAInstruction,ISSABasicBlock> getCFG(CGNode N) {
|
||||
return getIR(N).getControlFlowGraph();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefUse getDU(CGNode node) {
|
||||
return new DefUse(getIR(node));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/* Copyright (c) 2013 Michael Heilmann */
|
||||
package com.ibm.wala.analysis.reflection.ext;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import com.ibm.wala.analysis.reflection.JavaLangClassContextInterpreter;
|
||||
import com.ibm.wala.analysis.typeInference.PointType;
|
||||
import com.ibm.wala.classLoader.CallSiteReference;
|
||||
import com.ibm.wala.classLoader.IClass;
|
||||
import com.ibm.wala.classLoader.IMethod;
|
||||
import com.ibm.wala.ipa.callgraph.CGNode;
|
||||
import com.ibm.wala.ipa.callgraph.Context;
|
||||
import com.ibm.wala.ipa.callgraph.ContextSelector;
|
||||
import com.ibm.wala.ipa.callgraph.propagation.ConstantKey;
|
||||
import com.ibm.wala.ipa.callgraph.propagation.InstanceKey;
|
||||
import com.ibm.wala.ipa.cha.IClassHierarchy;
|
||||
import com.ibm.wala.ssa.IR;
|
||||
import com.ibm.wala.ssa.SSAAbstractInvokeInstruction;
|
||||
import com.ibm.wala.ssa.SymbolTable;
|
||||
import com.ibm.wala.types.MethodReference;
|
||||
import com.ibm.wala.types.TypeReference;
|
||||
import com.ibm.wala.util.collections.HashSetFactory;
|
||||
import com.ibm.wala.util.intset.EmptyIntSet;
|
||||
import com.ibm.wala.util.intset.IntSet;
|
||||
import com.ibm.wala.util.intset.IntSetUtil;
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Produces {@link com.ibm.wala.analysis.reflection.ext.GetMethodContext} if the following is true:
|
||||
* - The method to be interpreted is either
|
||||
* {@link java.lang.Class#getMethod(String, Class...)} or
|
||||
* {@link java.lang.Class#getDeclaredMethod(String, Class...)}.
|
||||
* - The type of the "this" argument is known.
|
||||
* - The value of the first argument (the method name) is a constant.
|
||||
* @author
|
||||
* Michael Heilmann
|
||||
*/
|
||||
public class GetMethodContextSelector implements ContextSelector {
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* If @a true, debug information is emitted.
|
||||
*/
|
||||
protected static final boolean DEBUG = false;
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* If
|
||||
* - the {@link CallSiteReference} invokes either {@link java.lang.Class#getMethod}
|
||||
* or {@link java.lang.Class#getDeclaredMethod},
|
||||
* - and the receiver is a type constant and
|
||||
* - the first argument is a constant,
|
||||
* then return a {@link GetMethodContextSelector}.
|
||||
*/
|
||||
@Override
|
||||
public Context getCalleeTarget(CGNode caller,CallSiteReference site,IMethod callee,InstanceKey[] receiver) {
|
||||
if (receiver != null && receiver.length > 0 && mayUnderstand(caller, site, callee, receiver[0])) {
|
||||
if (DEBUG) {
|
||||
System.out.print("site := " + site + ", receiver := " + receiver[0]);
|
||||
}
|
||||
// If the first argument is a constant ...
|
||||
IR ir = caller.getIR();
|
||||
SymbolTable symbolTable = ir.getSymbolTable();
|
||||
SSAAbstractInvokeInstruction[] invokeInstructions = caller.getIR().getCalls(site);
|
||||
if (invokeInstructions.length != 1) {
|
||||
return null;
|
||||
}
|
||||
int use = invokeInstructions[0].getUse(1);
|
||||
if (symbolTable.isStringConstant(invokeInstructions[0].getUse(1))) {
|
||||
String sym = symbolTable.getStringValue(use);
|
||||
if (DEBUG) {
|
||||
System.out.println(invokeInstructions);
|
||||
System.out.println(", with constant := `" + sym + "`");
|
||||
for (InstanceKey instanceKey:receiver) {
|
||||
System.out.println(" " + instanceKey);
|
||||
}
|
||||
}
|
||||
// ... return an GetMethdContext.
|
||||
ConstantKey ck = makeConstantKey(caller.getClassHierarchy(),sym);
|
||||
System.out.println(ck);
|
||||
return new GetMethodContext(new PointType(getTypeConstant(receiver[0])),ck);
|
||||
}
|
||||
if (DEBUG) {
|
||||
System.out.println(", with constant := no");
|
||||
}
|
||||
// Otherwise, return null.
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* If @a instance is a ConstantKey and its value is an instance of IClass,
|
||||
* return that value. Otherwise, return @a null.
|
||||
*/
|
||||
private IClass getTypeConstant(InstanceKey instance) {
|
||||
if (instance instanceof ConstantKey) {
|
||||
ConstantKey c = (ConstantKey) instance;
|
||||
if (c.getValue() instanceof IClass) {
|
||||
return (IClass) c.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Create a constant key for a string.
|
||||
* @param cha
|
||||
* The class hierarchy.
|
||||
* @param str
|
||||
* The string.
|
||||
* @return
|
||||
* The constant key.
|
||||
*/
|
||||
protected static ConstantKey<String> makeConstantKey(IClassHierarchy cha,String str) {
|
||||
IClass cls = cha.lookupClass(TypeReference.JavaLangString);
|
||||
ConstantKey<String> ck = new ConstantKey<String>(str,cls);
|
||||
return ck;
|
||||
}
|
||||
|
||||
private static final Collection<MethodReference> UNDERSTOOD_METHOD_REFS = HashSetFactory.make();
|
||||
|
||||
static {
|
||||
UNDERSTOOD_METHOD_REFS.add(JavaLangClassContextInterpreter.GET_METHOD);
|
||||
UNDERSTOOD_METHOD_REFS.add(JavaLangClassContextInterpreter.GET_DECLARED_METHOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* This object might understand a dispatch to
|
||||
* {@link java.lang.Class#getMethod(String, Class...)}
|
||||
* or
|
||||
* {@link java.lang.Class#getDeclaredMethod}
|
||||
* when the receiver is a type constant.
|
||||
*/
|
||||
private boolean mayUnderstand(CGNode caller,CallSiteReference site,IMethod targetMethod,InstanceKey instance) {
|
||||
return UNDERSTOOD_METHOD_REFS.contains(targetMethod.getReference())
|
||||
&& getTypeConstant(instance) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* MH: Shouldn't be the first TWO parameters be relevant?
|
||||
*/
|
||||
private static final IntSet thisParameter = IntSetUtil.make(new int[]{0});
|
||||
|
||||
@Override
|
||||
public IntSet getRelevantParameters(CGNode caller, CallSiteReference site) {
|
||||
if (site.isDispatch() || site.getDeclaredTarget().getNumberOfParameters() > 0) {
|
||||
return thisParameter;
|
||||
} else {
|
||||
return EmptyIntSet.instance;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* This package provides a context, a context interpreter and a context selector
|
||||
* to handle the special case of calls to {@link java.lang.Class#getMethod} and
|
||||
* {@link java.lang.Class#getDeclaredMethod} with higher precision than usual.
|
||||
*
|
||||
* The context interpreter and context selector should both be placed in front
|
||||
* of {@link com.ibm.wala.analysis.reflection.JavaLangClassContextInterpreter}
|
||||
* and {@link com.ibm.wala.analysis.reflection.JavaLangClassContextSelector} in
|
||||
* order to work.
|
||||
*
|
||||
* Problem description:
|
||||
* {@link com.ibm.wala.analysis.reflection.JavaLangClassContextInterpreter} and
|
||||
* and {@link com.ibm.wala.analysis.reflection.JavaLangClassContextSelector} ignore
|
||||
* the chance to optimize the set of methods returned if the first argument to
|
||||
* {@link java.lang.Class#getMethod} or {@link java.lang.Class#getDeclaredMethod}
|
||||
* is a string constant.
|
||||
* @author
|
||||
* Michael Heilmann
|
||||
*
|
||||
*/
|
||||
package com.ibm.wala.analysis.reflection.ext;
|
Loading…
Reference in New Issue