001: /*
002: * Bytecode Analysis Framework
003: * Copyright (C) 2003,2004 University of Maryland
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: */
019:
020: package edu.umd.cs.findbugs.ba;
021:
022: import static edu.umd.cs.findbugs.ba.Hierarchy.DEBUG_METHOD_LOOKUP;
023: import static edu.umd.cs.findbugs.ba.Hierarchy.INSTANCE_METHOD;
024: import static edu.umd.cs.findbugs.ba.Hierarchy.STATIC_METHOD;
025:
026: import java.util.Collections;
027: import java.util.HashSet;
028: import java.util.Set;
029:
030: import org.apache.bcel.Constants;
031: import org.apache.bcel.generic.ArrayType;
032: import org.apache.bcel.generic.ConstantPoolGen;
033: import org.apache.bcel.generic.INVOKESTATIC;
034: import org.apache.bcel.generic.InvokeInstruction;
035: import org.apache.bcel.generic.ObjectType;
036: import org.apache.bcel.generic.ReferenceType;
037: import org.apache.bcel.generic.Type;
038:
039: import edu.umd.cs.findbugs.annotations.CheckForNull;
040: import edu.umd.cs.findbugs.annotations.NonNull;
041: import edu.umd.cs.findbugs.ba.type.TypeFrame;
042: import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
043: import edu.umd.cs.findbugs.classfile.ClassDescriptor;
044: import edu.umd.cs.findbugs.classfile.DescriptorFactory;
045: import edu.umd.cs.findbugs.classfile.Global;
046: import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
047: import edu.umd.cs.findbugs.internalAnnotations.SlashedClassName;
048: import edu.umd.cs.findbugs.util.ClassName;
049: import edu.umd.cs.findbugs.util.Util;
050:
051: /**
052: * Facade for class hierarchy queries.
053: * These typically access the class hierarchy using
054: * the {@link org.apache.bcel.Repository} class. Callers should generally
055: * expect to handle ClassNotFoundException for when referenced
056: * classes can't be found.
057: *
058: * @author William Pugh
059: */
060: public class Hierarchy2 {
061:
062: public static final ClassDescriptor ObjectDescriptor = DescriptorFactory
063: .createClassDescriptor("java/lang/Object");
064:
065: /**
066: * Look up the method referenced by given InvokeInstruction.
067: * This method does <em>not</em> look for implementations in
068: * super or subclasses according to the virtual dispatch rules.
069: *
070: * @param inv the InvokeInstruction
071: * @param cpg the ConstantPoolGen used by the class the InvokeInstruction belongs to
072: * @param chooser JavaClassAndMethodChooser to use to pick the method from among the candidates
073: * @return the JavaClassAndMethod, or null if no such method is defined in the class
074: */
075: public static XMethod findExactMethod(InvokeInstruction inv,
076: ConstantPoolGen cpg, JavaClassAndMethodChooser chooser)
077: throws ClassNotFoundException {
078: String className = inv.getClassName(cpg);
079: String methodName = inv.getName(cpg);
080: String methodSig = inv.getSignature(cpg);
081:
082: XMethod result = findMethod(DescriptorFactory
083: .createClassDescriptorFromDottedClassName(className),
084: methodName, methodSig, inv instanceof INVOKESTATIC);
085:
086: return this OrNothing(result, chooser);
087: }
088:
089: private static @CheckForNull
090: XMethod this OrNothing(@CheckForNull
091: XMethod m, JavaClassAndMethodChooser chooser) {
092: if (m == null)
093: return null;
094: if (chooser.choose(m))
095: return m;
096: return null;
097: }
098:
099: public static @CheckForNull
100: XMethod findInvocationLeastUpperBound(InvokeInstruction inv,
101: ConstantPoolGen cpg, JavaClassAndMethodChooser methodChooser)
102: throws ClassNotFoundException {
103:
104: if (DEBUG_METHOD_LOOKUP) {
105: System.out.println("Find prototype method for "
106: + SignatureConverter.convertMethodSignature(inv,
107: cpg));
108: }
109:
110: short opcode = inv.getOpcode();
111:
112: if (opcode == Constants.INVOKESTATIC) {
113: if (methodChooser == INSTANCE_METHOD)
114: return null;
115: } else {
116: if (methodChooser == STATIC_METHOD)
117: return null;
118: }
119:
120: // Find the method
121: if (opcode == Constants.INVOKESPECIAL) {
122: // Non-virtual dispatch
123: return findExactMethod(inv, cpg, methodChooser);
124: } else {
125: String className = inv.getClassName(cpg);
126: String methodName = inv.getName(cpg);
127: String methodSig = inv.getSignature(cpg);
128: if (DEBUG_METHOD_LOOKUP) {
129: System.out.println("[Class name is " + className + "]");
130: System.out.println("[Method name is " + methodName
131: + "]");
132: System.out.println("[Method signature is " + methodSig
133: + "]");
134: }
135:
136: if (className.startsWith("[")) {
137: // Java 1.5 allows array classes to appear as the class name
138: className = "java.lang.Object";
139: }
140:
141: try {
142: return this OrNothing(findInvocationLeastUpperBound(
143: getXClassFromDottedClassName(className),
144: methodName, methodSig,
145: opcode == Constants.INVOKESTATIC,
146: opcode == Constants.INVOKEINTERFACE),
147: methodChooser);
148: } catch (CheckedAnalysisException e) {
149: return null;
150: }
151:
152: }
153: }
154:
155: public static @CheckForNull
156: XMethod findInvocationLeastUpperBound(ClassDescriptor classDesc,
157: String methodName, String methodSig, boolean invokeStatic,
158: boolean invokeInterface) {
159: try {
160: return findInvocationLeastUpperBound(getXClass(classDesc),
161: methodName, methodSig, invokeStatic,
162: invokeInterface);
163: } catch (Exception e) {
164: return null;
165: }
166: }
167:
168: public static @CheckForNull
169: XMethod findInvocationLeastUpperBound(XClass jClass,
170: String methodName, String methodSig, boolean invokeStatic,
171: boolean invokeInterface) throws ClassNotFoundException {
172: XMethod result = findMethod(jClass.getClassDescriptor(),
173: methodName, methodSig, invokeStatic);
174: if (result != null)
175: return result;
176: if (invokeInterface)
177: for (ClassDescriptor i : jClass
178: .getInterfaceDescriptorList()) {
179: result = findInvocationLeastUpperBound(i, methodName,
180: methodSig, invokeStatic, invokeInterface);
181: if (result != null)
182: return null;
183: }
184: else {
185: ClassDescriptor sClass = jClass.getSuperclassDescriptor();
186: if (sClass != null)
187: return findInvocationLeastUpperBound(sClass,
188: methodName, methodSig, invokeStatic,
189: invokeInterface);
190: }
191: return null;
192:
193: }
194:
195: public static @CheckForNull
196: XMethod findMethod(ClassDescriptor classDescriptor,
197: String methodName, String methodSig, boolean isStatic) {
198: try {
199: return getXClass(classDescriptor).findMethod(methodName,
200: methodSig, isStatic);
201: } catch (CheckedAnalysisException e) {
202: return null;
203: }
204: }
205:
206: static XClass getXClass(@SlashedClassName
207: String c) throws CheckedAnalysisException {
208: return getXClass(DescriptorFactory.createClassDescriptor(c));
209: }
210:
211: static XClass getXClassFromDottedClassName(@DottedClassName
212: String c) throws CheckedAnalysisException {
213: return getXClass(DescriptorFactory
214: .createClassDescriptorFromDottedClassName(c));
215: }
216:
217: static XClass getXClass(ClassDescriptor c)
218: throws CheckedAnalysisException {
219: return Global.getAnalysisCache().getClassAnalysis(XClass.class,
220: c);
221: }
222:
223: /**
224: * Resolve possible method call targets.
225: * This works for both static and instance method calls.
226: *
227: * @param invokeInstruction the InvokeInstruction
228: * @param typeFrame the TypeFrame containing the types of stack values
229: * @param cpg the ConstantPoolGen
230: * @return Set of methods which might be called
231: * @throws DataflowAnalysisException
232: * @throws ClassNotFoundException
233: */
234: public static @NonNull
235: Set<XMethod> resolveMethodCallTargets(
236: InvokeInstruction invokeInstruction, TypeFrame typeFrame,
237: ConstantPoolGen cpg) throws DataflowAnalysisException,
238: ClassNotFoundException {
239:
240: short opcode = invokeInstruction.getOpcode();
241:
242: if (opcode == Constants.INVOKESTATIC) {
243: return Util
244: .emptyOrNonnullSingleton(findInvocationLeastUpperBound(
245: invokeInstruction, cpg, STATIC_METHOD));
246: }
247:
248: if (!typeFrame.isValid()) {
249: return Collections.emptySet();
250: }
251:
252: Type receiverType;
253: boolean receiverTypeIsExact;
254:
255: if (opcode == Constants.INVOKESPECIAL) {
256: // invokespecial instructions are dispatched to EXACTLY
257: // the class specified by the instruction
258: receiverType = ObjectTypeFactory
259: .getInstance(invokeInstruction.getClassName(cpg));
260: receiverTypeIsExact = false; // Doesn't actually matter
261: } else {
262: // For invokevirtual and invokeinterface instructions, we have
263: // virtual dispatch. By taking the receiver type (which may be a
264: // subtype of the class specified by the instruction),
265: // we may get a more precise set of call targets.
266: int instanceStackLocation = typeFrame
267: .getInstanceStackLocation(invokeInstruction, cpg);
268: receiverType = typeFrame
269: .getStackValue(instanceStackLocation);
270: if (!(receiverType instanceof ReferenceType)) {
271: return Collections.emptySet();
272: }
273: receiverTypeIsExact = typeFrame
274: .isExact(instanceStackLocation);
275: }
276: if (DEBUG_METHOD_LOOKUP) {
277: System.out.println("[receiver type is " + receiverType
278: + ", "
279: + (receiverTypeIsExact ? "exact]" : " not exact]"));
280: }
281:
282: return resolveMethodCallTargets((ReferenceType) receiverType,
283: invokeInstruction, cpg, receiverTypeIsExact);
284: }
285:
286: /**
287: * Resolve possible instance method call targets.
288: * Assumes that invokevirtual and invokeinterface methods may
289: * call any subtype of the receiver class.
290: *
291: * @param receiverType type of the receiver object
292: * @param invokeInstruction the InvokeInstruction
293: * @param cpg the ConstantPoolGen
294: * @return Set of methods which might be called
295: * @throws ClassNotFoundException
296: */
297: public static Set<XMethod> resolveMethodCallTargets(
298: ReferenceType receiverType,
299: InvokeInstruction invokeInstruction, ConstantPoolGen cpg)
300: throws ClassNotFoundException {
301: return resolveMethodCallTargets(receiverType,
302: invokeInstruction, cpg, false);
303: }
304:
305: /**
306: * Resolve possible instance method call targets.
307: *
308: * @param receiverType type of the receiver object
309: * @param invokeInstruction the InvokeInstruction
310: * @param cpg the ConstantPoolGen
311: * @param receiverTypeIsExact if true, the receiver type is known exactly,
312: * which should allow a precise result
313: * @return Set of methods which might be called
314: * @throws ClassNotFoundException
315: */
316: public static Set<XMethod> resolveMethodCallTargets(
317: ReferenceType receiverType,
318: InvokeInstruction invokeInstruction, ConstantPoolGen cpg,
319: boolean receiverTypeIsExact) throws ClassNotFoundException {
320: HashSet<XMethod> result = new HashSet<XMethod>();
321:
322: if (invokeInstruction.getOpcode() == Constants.INVOKESTATIC)
323: throw new IllegalArgumentException();
324:
325: String methodName = invokeInstruction.getName(cpg);
326: String methodSig = invokeInstruction.getSignature(cpg);
327:
328: // Array method calls aren't virtual.
329: // They should just resolve to Object methods.
330: if (receiverType instanceof ArrayType)
331: try {
332: return Util.emptyOrNonnullSingleton(getXClass(
333: ObjectDescriptor).findMethod(methodName,
334: methodSig, false));
335: } catch (CheckedAnalysisException e) {
336: return Collections.emptySet();
337: }
338:
339: AnalysisContext analysisContext = AnalysisContext
340: .currentAnalysisContext();
341:
342: // Get the receiver class.
343: String receiverClassName = ((ObjectType) receiverType)
344: .getClassName();
345: ClassDescriptor receiverDesc = DescriptorFactory
346: .createClassDescriptorFromDottedClassName(receiverClassName);
347: XClass xClass;
348: try {
349: xClass = getXClass(receiverDesc);
350: } catch (CheckedAnalysisException e) {
351: return Collections.emptySet();
352: }
353: // Figure out the upper bound for the method.
354: // This is what will be called if this is not a virtual call site.
355: XMethod upperBound = findMethod(receiverDesc, methodName,
356: methodSig, false);
357: if (upperBound == null) {
358: upperBound = findInvocationLeastUpperBound(xClass,
359: methodName, methodSig, false, false);
360: }
361: if (upperBound != null) {
362: if (DEBUG_METHOD_LOOKUP) {
363: System.out.println("Adding upper bound: " + upperBound);
364: }
365: result.add(upperBound);
366: }
367:
368: // Is this a virtual call site?
369: boolean virtualCall = (invokeInstruction.getOpcode() == Constants.INVOKEVIRTUAL || invokeInstruction
370: .getOpcode() == Constants.INVOKEINTERFACE)
371: && (upperBound == null || !upperBound.isFinal())
372: && !receiverTypeIsExact;
373:
374: if (virtualCall) {
375: if (!receiverClassName.equals("java.lang.Object")) {
376:
377: // This is a true virtual call: assume that any concrete
378: // subtype method may be called.
379: Set<ClassDescriptor> subTypeSet = analysisContext
380: .getSubtypes2().getSubtypes(receiverDesc);
381: for (ClassDescriptor subtype : subTypeSet) {
382: XMethod concreteSubtypeMethod = findMethod(subtype,
383: methodName, methodSig, false);
384: if (concreteSubtypeMethod != null
385: && (concreteSubtypeMethod.getAccessFlags() & Constants.ACC_ABSTRACT) == 0) {
386: result.add(concreteSubtypeMethod);
387: }
388: }
389: if (false && subTypeSet.size() > 500)
390: new RuntimeException(receiverClassName + " has "
391: + subTypeSet.size() + " subclasses, "
392: + result.size() + " of which implement "
393: + methodName + methodSig + " "
394: + invokeInstruction)
395: .printStackTrace(System.out);
396:
397: }
398: }
399: return result;
400: }
401:
402: /**
403: * Find the declared exceptions for the method called
404: * by given instruction.
405: *
406: * @param inv the InvokeInstruction
407: * @param cpg the ConstantPoolGen used by the class the InvokeInstruction belongs to
408: * @return array of ObjectTypes of thrown exceptions, or null
409: * if we can't find the method implementation
410: */
411: public static @CheckForNull
412: ObjectType[] findDeclaredExceptions(InvokeInstruction inv,
413: ConstantPoolGen cpg) throws ClassNotFoundException {
414: XMethod method = findInvocationLeastUpperBound(inv, cpg,
415: inv instanceof INVOKESTATIC ? Hierarchy.STATIC_METHOD
416: : Hierarchy.INSTANCE_METHOD);
417:
418: if (method == null)
419: return null;
420: String[] exceptions = method.getThrownExceptions();
421:
422: if (exceptions == null)
423: return new ObjectType[0];
424:
425: ObjectType[] result = new ObjectType[exceptions.length];
426: for (int i = 0; i < exceptions.length; ++i) {
427: result[i] = ObjectTypeFactory.getInstance(ClassName
428: .toDottedClassName(exceptions[i]));
429: }
430: return result;
431: }
432:
433: }
434:
435: // vim:ts=4
|