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 java.util.HashMap;
023: import java.util.Map;
024:
025: import org.apache.bcel.Constants;
026: import org.apache.bcel.Repository;
027: import org.apache.bcel.classfile.Code;
028: import org.apache.bcel.classfile.ConstantClass;
029: import org.apache.bcel.classfile.ConstantFieldref;
030: import org.apache.bcel.classfile.ConstantNameAndType;
031: import org.apache.bcel.classfile.ConstantPool;
032: import org.apache.bcel.classfile.JavaClass;
033: import org.apache.bcel.classfile.Method;
034: import org.apache.bcel.generic.ConstantPoolGen;
035: import org.apache.bcel.generic.INVOKESTATIC;
036:
037: import edu.umd.cs.findbugs.SystemProperties;
038:
039: /**
040: * Determine which methods are accessors used
041: * by inner classes to access fields in their enclosing classes.
042: * This has been tested with javac from the Sun JDK 1.4.x,
043: * but will probably not work with other source to bytecode compilers.
044: *
045: * <p>
046: * The instance of InnerClassAccessMap should be retrieved
047: * from the AnalysisContext.
048: * </p>
049: *
050: * @author David Hovemeyer
051: * @see InnerClassAccess
052: */
053: public class InnerClassAccessMap {
054: private static final boolean DEBUG = SystemProperties
055: .getBoolean("icam.debug");
056:
057: /* ----------------------------------------------------------------------
058: * Fields
059: * ---------------------------------------------------------------------- */
060:
061: /**
062: * Map of class names to maps of method names to InnerClassAccess objects
063: * representing access methods.
064: */
065: private Map<String, Map<String, InnerClassAccess>> classToAccessMap;
066:
067: /* ----------------------------------------------------------------------
068: * Public interface
069: * ---------------------------------------------------------------------- */
070:
071: /**
072: * Create an instance.
073: *
074: * @return a new instance of InnerClassAccessMap
075: */
076: public static InnerClassAccessMap create() {
077: return new InnerClassAccessMap();
078: }
079:
080: /**
081: * Get the InnerClassAccess in given class with the given method name.
082: *
083: * @param className the name of the class
084: * @param methodName the name of the access method
085: * @return the InnerClassAccess object for the method, or null if
086: * the method doesn't seem to be an inner class access
087: */
088: public InnerClassAccess getInnerClassAccess(String className,
089: String methodName) throws ClassNotFoundException {
090: Map<String, InnerClassAccess> map = getAccessMapForClass(className);
091: return map.get(methodName);
092: }
093:
094: /**
095: * Get the inner class access object for given invokestatic instruction.
096: * Returns null if the called method is not an inner class access.
097: *
098: * @param inv the invokestatic instruction
099: * @param cpg the ConstantPoolGen for the method
100: * @return the InnerClassAccess, or null if the call is not an inner class access
101: */
102: public InnerClassAccess getInnerClassAccess(INVOKESTATIC inv,
103: ConstantPoolGen cpg) throws ClassNotFoundException {
104: String methodName = inv.getMethodName(cpg);
105: if (methodName.startsWith("access$")) {
106: String className = inv.getClassName(cpg);
107:
108: return getInnerClassAccess(className, methodName);
109: }
110: return null;
111: }
112:
113: /**
114: * Clear the cache.
115: */
116: public void clearCache() {
117: classToAccessMap.clear();
118: }
119:
120: /* ----------------------------------------------------------------------
121: * Implementation
122: * ---------------------------------------------------------------------- */
123:
124: /**
125: * Constructor.
126: */
127: private InnerClassAccessMap() {
128: this .classToAccessMap = new HashMap<String, Map<String, InnerClassAccess>>();
129: }
130:
131: /**
132: * Convert byte to unsigned int.
133: */
134: private static int toInt(byte b) {
135: int value = b & 0x7F;
136: if ((b & 0x80) != 0)
137: value |= 0x80;
138: return value;
139: }
140:
141: /**
142: * Get an unsigned 16 bit constant pool index from a byte array.
143: */
144: private static int getIndex(byte[] instructionList, int index) {
145: return (toInt(instructionList[index + 1]) << 8)
146: | toInt(instructionList[index + 2]);
147: }
148:
149: private static class LookupFailure extends RuntimeException {
150: /**
151: *
152: */
153: private static final long serialVersionUID = 1L;
154: private final ClassNotFoundException exception;
155:
156: public LookupFailure(ClassNotFoundException exception) {
157: this .exception = exception;
158: }
159:
160: public ClassNotFoundException getException() {
161: return exception;
162: }
163: }
164:
165: /**
166: * Callback to scan an access method to determine what
167: * field it accesses, and whether the field is loaded or stored.
168: */
169: private static class InstructionCallback implements
170: BytecodeScanner.Callback {
171: private JavaClass javaClass;
172: private String methodName;
173: private String methodSig;
174: private byte[] instructionList;
175: private InnerClassAccess access;
176: private int accessCount;
177:
178: /**
179: * Constructor.
180: *
181: * @param javaClass the class containing the access method
182: * @param methodName the name of the access method
183: * @param methodSig the signature of the access method
184: * @param instructionList the bytecode of the method
185: */
186: public InstructionCallback(JavaClass javaClass,
187: String methodName, String methodSig,
188: byte[] instructionList) {
189: this .javaClass = javaClass;
190: this .methodName = methodName;
191: this .methodSig = methodSig;
192: this .instructionList = instructionList;
193: this .access = null;
194: this .accessCount = 0;
195: }
196:
197: public void handleInstruction(int opcode, int index) {
198: switch (opcode) {
199: case Constants.GETFIELD:
200: case Constants.PUTFIELD:
201: setField(getIndex(instructionList, index), false,
202: opcode == Constants.GETFIELD);
203: break;
204: case Constants.GETSTATIC:
205: case Constants.PUTSTATIC:
206: setField(getIndex(instructionList, index), true,
207: opcode == Constants.GETSTATIC);
208: break;
209: }
210: }
211:
212: /**
213: * Get the InnerClassAccess object representing the method.
214: *
215: * @return the InnerClassAccess, or null if the method
216: * was not found to be a simple load or store in the
217: * expected form
218: */
219: public InnerClassAccess getAccess() {
220: return access;
221: }
222:
223: /**
224: * Called to indicate that a field load or store was encountered.
225: *
226: * @param cpIndex the constant pool index of the fieldref
227: * @param isStatic true if it is a static field access
228: * @param isLoad true if the access is a load
229: */
230: private void setField(int cpIndex, boolean isStatic,
231: boolean isLoad) {
232: // We only allow one field access for an accessor method.
233: accessCount++;
234: if (accessCount != 1) {
235: access = null;
236: return;
237: }
238:
239: ConstantPool cp = javaClass.getConstantPool();
240: ConstantFieldref fieldref = (ConstantFieldref) cp
241: .getConstant(cpIndex);
242:
243: ConstantClass cls = (ConstantClass) cp.getConstant(fieldref
244: .getClassIndex());
245: String className = cls.getBytes(cp).replace('/', '.');
246:
247: ConstantNameAndType nameAndType = (ConstantNameAndType) cp
248: .getConstant(fieldref.getNameAndTypeIndex());
249: String fieldName = nameAndType.getName(cp);
250: String fieldSig = nameAndType.getSignature(cp);
251:
252: try {
253: XField xfield = Hierarchy.findXField(className,
254: fieldName, fieldSig, isStatic);
255: if (xfield != null
256: && xfield.isStatic() == isStatic
257: && isValidAccessMethod(methodSig, xfield,
258: isLoad)) {
259: access = new InnerClassAccess(methodName,
260: methodSig, xfield, isLoad);
261: }
262: } catch (ClassNotFoundException e) {
263: throw new LookupFailure(e);
264: }
265: }
266:
267: /**
268: * Determine if the method appears to be an accessor of the expected form.
269: * This has only been tested with the Sun JDK 1.4 javac (definitely)
270: * and jikes 1.18 (I think).
271: *
272: * @param methodSig the method's signature
273: * @param field the field accessed by the method
274: * @param isLoad true if the access is a load
275: */
276: private boolean isValidAccessMethod(String methodSig,
277: XField field, boolean isLoad) {
278:
279: // Get the method parameters and return type
280: // (as they appear in the method signature).
281: int paramsEnd = methodSig.indexOf(')');
282: if (paramsEnd < 0)
283: return false;
284: String methodParams = methodSig.substring(0, paramsEnd + 1);
285: String methodReturnType = methodSig
286: .substring(paramsEnd + 1);
287:
288: // Figure out what the expected method parameters should be
289: String classSig = "L"
290: + javaClass.getClassName().replace('.', '/') + ";";
291: StringBuffer buf = new StringBuffer();
292: buf.append('(');
293: if (!field.isStatic())
294: buf.append(classSig); // the OuterClass.this reference
295: if (!isLoad)
296: buf.append(field.getSignature()); // the value being stored
297: buf.append(')');
298: String expectedMethodParams = buf.toString();
299:
300: // See if params match
301: if (!methodParams.equals(expectedMethodParams)) {
302: if (DEBUG) {
303: System.out.println("In " + javaClass.getClassName()
304: + "." + methodName + " expected params "
305: + expectedMethodParams + ", saw "
306: + methodParams);
307: System.out.println(isLoad ? "LOAD" : "STORE");
308: }
309: return false;
310: }
311:
312: // Return type can be either the type of the field, or void.
313: if (!methodReturnType.equals("V")
314: && !methodReturnType.equals(field.getSignature())) {
315: if (DEBUG) {
316: System.out.println("In " + javaClass.getClassName()
317: + "." + methodName
318: + " expected return type V or "
319: + field.getSignature() + ", saw "
320: + methodReturnType);
321: System.out.println(isLoad ? "LOAD" : "STORE");
322: }
323: return false;
324: }
325:
326: return true;
327: }
328: }
329:
330: private static final Map<String, InnerClassAccess> emptyMap = new HashMap<String, InnerClassAccess>();
331:
332: /**
333: * Return a map of inner-class member access method names to
334: * the fields that they access for given class name.
335: *
336: * @param className the name of the class
337: * @return map of access method names to the fields they access
338: */
339: private Map<String, InnerClassAccess> getAccessMapForClass(
340: String className) throws ClassNotFoundException {
341:
342: Map<String, InnerClassAccess> map = classToAccessMap
343: .get(className);
344: if (map == null) {
345: map = new HashMap<String, InnerClassAccess>();
346:
347: if (!className.startsWith("[")) {
348: JavaClass javaClass = Repository.lookupClass(className);
349:
350: Method[] methodList = javaClass.getMethods();
351: for (Method method : methodList) {
352: String methodName = method.getName();
353: if (!methodName.startsWith("access$"))
354: continue;
355:
356: Code code = method.getCode();
357: if (code == null)
358: continue;
359:
360: if (DEBUG)
361: System.out
362: .println("Analyzing "
363: + className
364: + "."
365: + method.getName()
366: + " as an inner-class access method...");
367:
368: byte[] instructionList = code.getCode();
369: String methodSig = method.getSignature();
370: InstructionCallback callback = new InstructionCallback(
371: javaClass, methodName, methodSig,
372: instructionList);
373: try {
374: new BytecodeScanner().scan(instructionList,
375: callback);
376: } catch (LookupFailure lf) {
377: throw lf.getException();
378: }
379: InnerClassAccess access = callback.getAccess();
380: if (DEBUG)
381: System.out.println((access != null ? "IS"
382: : "IS NOT")
383: + " an inner-class access method");
384: if (access != null)
385: map.put(methodName, access);
386: }
387: }
388:
389: if (map.size() == 0)
390: map = emptyMap;
391:
392: classToAccessMap.put(className, map);
393: }
394:
395: return map;
396: }
397:
398: }
399:
400: // vim:ts=4
|