KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > edu > umd > cs > findbugs > ba > InnerClassAccessMap


1 /*
2  * Bytecode Analysis Framework
3  * Copyright (C) 2003,2004 University of Maryland
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  */

19
20 package edu.umd.cs.findbugs.ba;
21
22 import java.util.HashMap JavaDoc;
23 import java.util.Map JavaDoc;
24
25 import org.apache.bcel.Constants;
26 import org.apache.bcel.Repository;
27 import org.apache.bcel.classfile.Code;
28 import org.apache.bcel.classfile.ConstantClass;
29 import org.apache.bcel.classfile.ConstantFieldref;
30 import org.apache.bcel.classfile.ConstantNameAndType;
31 import org.apache.bcel.classfile.ConstantPool;
32 import org.apache.bcel.classfile.JavaClass;
33 import org.apache.bcel.classfile.Method;
34 import org.apache.bcel.generic.ConstantPoolGen;
35 import org.apache.bcel.generic.INVOKESTATIC;
36
37 import edu.umd.cs.findbugs.SystemProperties;
38
39 /**
40  * Determine which methods are accessors used
41  * by inner classes to access fields in their enclosing classes.
42  * This has been tested with javac from the Sun JDK 1.4.x,
43  * but will probably not work with other source to bytecode compilers.
44  *
45  * <p>
46  * The instance of InnerClassAccessMap should be retrieved
47  * from the AnalysisContext.
48  * </p>
49  *
50  * @author David Hovemeyer
51  * @see InnerClassAccess
52  */

53 public class InnerClassAccessMap {
54     private static final boolean DEBUG = SystemProperties.getBoolean("icam.debug");
55
56     /* ----------------------------------------------------------------------
57      * Fields
58      * ---------------------------------------------------------------------- */

59
60     /**
61      * Map of class names to maps of method names to InnerClassAccess objects
62      * representing access methods.
63      */

64     private Map JavaDoc<String JavaDoc, Map JavaDoc<String JavaDoc, InnerClassAccess>> classToAccessMap;
65
66     /* ----------------------------------------------------------------------
67      * Public interface
68      * ---------------------------------------------------------------------- */

69     
70     /**
71      * Create an instance.
72      *
73      * @return a new instance of InnerClassAccessMap
74      */

75     public static InnerClassAccessMap create() {
76         return new InnerClassAccessMap();
77     }
78
79     /**
80      * Get the InnerClassAccess in given class with the given method name.
81      *
82      * @param className the name of the class
83      * @param methodName the name of the access method
84      * @return the InnerClassAccess object for the method, or null if
85      * the method doesn't seem to be an inner class access
86      */

87     public InnerClassAccess getInnerClassAccess(String JavaDoc className, String JavaDoc methodName) throws ClassNotFoundException JavaDoc {
88         Map JavaDoc<String JavaDoc, InnerClassAccess> map = getAccessMapForClass(className);
89         return map.get(methodName);
90     }
91
92     /**
93      * Get the inner class access object for given invokestatic instruction.
94      * Returns null if the called method is not an inner class access.
95      *
96      * @param inv the invokestatic instruction
97      * @param cpg the ConstantPoolGen for the method
98      * @return the InnerClassAccess, or null if the call is not an inner class access
99      */

100     public InnerClassAccess getInnerClassAccess(INVOKESTATIC inv, ConstantPoolGen cpg) throws ClassNotFoundException JavaDoc {
101         String JavaDoc methodName = inv.getMethodName(cpg);
102         if (methodName.startsWith("access$")) {
103             String JavaDoc className = inv.getClassName(cpg);
104
105             return getInnerClassAccess(className, methodName);
106         }
107         return null;
108     }
109
110     /**
111      * Clear the cache.
112      */

113     public void clearCache() {
114         classToAccessMap.clear();
115     }
116
117     /* ----------------------------------------------------------------------
118      * Implementation
119      * ---------------------------------------------------------------------- */

120
121     /**
122      * Constructor.
123      */

124     private InnerClassAccessMap() {
125         this.classToAccessMap = new HashMap JavaDoc<String JavaDoc, Map JavaDoc<String JavaDoc, InnerClassAccess>>();
126     }
127
128     /**
129      * Convert byte to unsigned int.
130      */

131     private static int toInt(byte b) {
132         int value = b & 0x7F;
133         if ((b & 0x80) != 0)
134             value |= 0x80;
135         return value;
136     }
137
138     /**
139      * Get an unsigned 16 bit constant pool index from a byte array.
140      */

141     private static int getIndex(byte[] instructionList, int index) {
142         return (toInt(instructionList[index + 1]) << 8) | toInt(instructionList[index + 2]);
143     }
144
145     private static class LookupFailure extends RuntimeException JavaDoc {
146         /**
147          *
148          */

149         private static final long serialVersionUID = 1L;
150         private final ClassNotFoundException JavaDoc exception;
151
152         public LookupFailure(ClassNotFoundException JavaDoc exception) {
153             this.exception = exception;
154         }
155
156         public ClassNotFoundException JavaDoc getException() {
157             return exception;
158         }
159     }
160
161     /**
162      * Callback to scan an access method to determine what
163      * field it accesses, and whether the field is loaded or stored.
164      */

165     private static class InstructionCallback implements BytecodeScanner.Callback {
166         private JavaClass javaClass;
167         private String JavaDoc methodName;
168         private String JavaDoc methodSig;
169         private byte[] instructionList;
170         private InnerClassAccess access;
171         private int accessCount;
172
173         /**
174          * Constructor.
175          *
176          * @param javaClass the class containing the access method
177          * @param methodName the name of the access method
178          * @param methodSig the signature of the access method
179          * @param instructionList the bytecode of the method
180          */

181         public InstructionCallback(JavaClass javaClass, String JavaDoc methodName, String JavaDoc methodSig, byte[] instructionList) {
182             this.javaClass = javaClass;
183             this.methodName = methodName;
184             this.methodSig = methodSig;
185             this.instructionList = instructionList;
186             this.access = null;
187             this.accessCount = 0;
188         }
189
190         public void handleInstruction(int opcode, int index) {
191             switch (opcode) {
192             case Constants.GETFIELD:
193             case Constants.PUTFIELD:
194                 setField(getIndex(instructionList, index), false, opcode == Constants.GETFIELD);
195                 break;
196             case Constants.GETSTATIC:
197             case Constants.PUTSTATIC:
198                 setField(getIndex(instructionList, index), true, opcode == Constants.GETSTATIC);
199                 break;
200             }
201         }
202
203         /**
204          * Get the InnerClassAccess object representing the method.
205          *
206          * @return the InnerClassAccess, or null if the method
207          * was not found to be a simple load or store in the
208          * expected form
209          */

210         public InnerClassAccess getAccess() {
211             return access;
212         }
213
214         /**
215          * Called to indicate that a field load or store was encountered.
216          *
217          * @param cpIndex the constant pool index of the fieldref
218          * @param isStatic true if it is a static field access
219          * @param isLoad true if the access is a load
220          */

221         private void setField(int cpIndex, boolean isStatic, boolean isLoad) {
222             // We only allow one field access for an accessor method.
223
accessCount++;
224             if (accessCount != 1) {
225                 access = null;
226                 return;
227             }
228
229             ConstantPool cp = javaClass.getConstantPool();
230             ConstantFieldref fieldref = (ConstantFieldref) cp.getConstant(cpIndex);
231
232             ConstantClass cls = (ConstantClass) cp.getConstant(fieldref.getClassIndex());
233             String JavaDoc className = cls.getBytes(cp).replace('/', '.');
234
235             ConstantNameAndType nameAndType = (ConstantNameAndType) cp.getConstant(fieldref.getNameAndTypeIndex());
236             String JavaDoc fieldName = nameAndType.getName(cp);
237             String JavaDoc fieldSig = nameAndType.getSignature(cp);
238
239             try {
240                 XField xfield = Hierarchy.findXField(className, fieldName, fieldSig);
241                 if (xfield != null && xfield.isStatic() == isStatic && isValidAccessMethod(methodSig, xfield, isLoad)) {
242                     access = new InnerClassAccess(methodName, methodSig, xfield, isLoad);
243                 }
244             } catch (ClassNotFoundException JavaDoc e) {
245                 throw new LookupFailure(e);
246             }
247         }
248
249         /**
250          * Determine if the method appears to be an accessor of the expected form.
251          * This has only been tested with the Sun JDK 1.4 javac (definitely)
252          * and jikes 1.18 (I think).
253          *
254          * @param methodSig the method's signature
255          * @param field the field accessed by the method
256          * @param isLoad true if the access is a load
257          */

258         private boolean isValidAccessMethod(String JavaDoc methodSig, XField field, boolean isLoad) {
259
260             // Get the method parameters and return type
261
// (as they appear in the method signature).
262
int paramsEnd = methodSig.indexOf(')');
263             if (paramsEnd < 0)
264                 return false;
265             String JavaDoc methodParams = methodSig.substring(0, paramsEnd + 1);
266             String JavaDoc methodReturnType = methodSig.substring(paramsEnd + 1);
267
268             // Figure out what the expected method parameters should be
269
String JavaDoc classSig = "L" + javaClass.getClassName().replace('.', '/') + ";";
270             StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
271             buf.append('(');
272             if (!field.isStatic())
273                 buf.append(classSig); // the OuterClass.this reference
274
if (!isLoad)
275                 buf.append(field.getSignature()); // the value being stored
276
buf.append(')');
277             String JavaDoc expectedMethodParams = buf.toString();
278
279             // See if params match
280
if (!methodParams.equals(expectedMethodParams)) {
281                 if (DEBUG) {
282                     System.out.println("In " + javaClass.getClassName() + "." + methodName +
283                         " expected params " +
284                         expectedMethodParams + ", saw " + methodParams);
285                     System.out.println(isLoad ? "LOAD" : "STORE");
286                 }
287                 return false;
288             }
289
290             // Return type can be either the type of the field, or void.
291
if (!methodReturnType.equals("V") && !methodReturnType.equals(field.getSignature())) {
292                 if (DEBUG) {
293                     System.out.println("In " + javaClass.getClassName() + "." + methodName +
294                         " expected return type V or " + field.getSignature() +
295                         ", saw " + methodReturnType);
296                     System.out.println(isLoad ? "LOAD" : "STORE");
297                 }
298                 return false;
299             }
300
301             return true;
302         }
303     }
304
305     private static final Map JavaDoc<String JavaDoc, InnerClassAccess> emptyMap = new HashMap JavaDoc<String JavaDoc, InnerClassAccess>();
306
307     /**
308      * Return a map of inner-class member access method names to
309      * the fields that they access for given class name.
310      *
311      * @param className the name of the class
312      * @return map of access method names to the fields they access
313      */

314     private Map JavaDoc<String JavaDoc, InnerClassAccess> getAccessMapForClass(String JavaDoc className)
315             throws ClassNotFoundException JavaDoc {
316
317         Map JavaDoc<String JavaDoc, InnerClassAccess> map = classToAccessMap.get(className);
318         if (map == null) {
319             map = new HashMap JavaDoc<String JavaDoc, InnerClassAccess>();
320
321             if (!className.startsWith("[")) {
322                 JavaClass javaClass = Repository.lookupClass(className);
323
324                 Method[] methodList = javaClass.getMethods();
325                 for (Method method : methodList) {
326                     String JavaDoc methodName = method.getName();
327                     if (!methodName.startsWith("access$"))
328                         continue;
329
330                     Code code = method.getCode();
331                     if (code == null)
332                         continue;
333
334                     if (DEBUG) System.out.println("Analyzing " + className + "." +
335                             method.getName() + " as an inner-class access method...");
336
337                     byte[] instructionList = code.getCode();
338                     String JavaDoc methodSig = method.getSignature();
339                     InstructionCallback callback = new InstructionCallback(javaClass, methodName, methodSig, instructionList);
340                     try {
341                         new BytecodeScanner().scan(instructionList, callback);
342                     } catch (LookupFailure lf) {
343                         throw lf.getException();
344                     }
345                     InnerClassAccess access = callback.getAccess();
346                     if (DEBUG) System.out.println((access != null ? "IS" : "IS NOT") + " an inner-class access method");
347                     if (access != null)
348                         map.put(methodName, access);
349                 }
350             }
351
352             if (map.size() == 0)
353                 map = emptyMap;
354
355             classToAccessMap.put(className, map);
356         }
357
358         return map;
359     }
360
361 }
362
363 // vim:ts=4
364
Popular Tags