001: /*
002: * FindBugs - Find Bugs in Java programs
003: * Copyright (C) 2006, 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.classfile.engine;
021:
022: import java.io.DataInputStream;
023: import java.io.IOException;
024: import java.util.Collection;
025: import java.util.HashSet;
026: import java.util.Set;
027:
028: import edu.umd.cs.findbugs.annotations.CheckForNull;
029: import edu.umd.cs.findbugs.classfile.ClassDescriptor;
030: import edu.umd.cs.findbugs.classfile.DescriptorFactory;
031: import edu.umd.cs.findbugs.classfile.FieldDescriptor;
032: import edu.umd.cs.findbugs.classfile.IClassConstants;
033: import edu.umd.cs.findbugs.classfile.ICodeBaseEntry;
034: import edu.umd.cs.findbugs.classfile.InvalidClassFileFormatException;
035: import edu.umd.cs.findbugs.classfile.MethodDescriptor;
036: import edu.umd.cs.findbugs.classfile.analysis.ClassInfo;
037: import edu.umd.cs.findbugs.classfile.analysis.ClassNameAndSuperclassInfo;
038: import edu.umd.cs.findbugs.internalAnnotations.SlashedClassName;
039: import edu.umd.cs.findbugs.io.IO;
040: import edu.umd.cs.findbugs.util.ClassName;
041:
042: /**
043: * Parse a class to extract symbolic information.
044: * see <a href=http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html">
045: http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html </a>
046: *
047: * @author David Hovemeyer
048: */
049: public class ClassParser implements ClassParserInterface {
050:
051: static class Constant {
052: int tag;
053: Object[] data;
054:
055: Constant(int tag, Object[] data) {
056: this .tag = tag;
057: this .data = data;
058: }
059: }
060:
061: private DataInputStream in;
062: private ClassDescriptor expectedClassDescriptor;
063: private ICodeBaseEntry codeBaseEntry;
064: private Constant[] constantPool;
065: private ClassDescriptor immediateEnclosingClass;
066:
067: /**
068: * Constructor.
069: *
070: * @param in the DataInputStream to read class data from
071: * @param expectedClassDescriptor ClassDescriptor expected: null if unknown
072: * @param codeBaseEntry codebase entry class is loaded from
073: */
074: public ClassParser(DataInputStream in, @CheckForNull
075: ClassDescriptor expectedClassDescriptor,
076: ICodeBaseEntry codeBaseEntry) {
077: this .in = in;
078: this .expectedClassDescriptor = expectedClassDescriptor;
079: this .codeBaseEntry = codeBaseEntry;
080: }
081:
082: /* (non-Javadoc)
083: * @see edu.umd.cs.findbugs.classfile.engine.ClassParserInterface#parse(edu.umd.cs.findbugs.classfile.analysis.ClassNameAndSuperclassInfo.Builder)
084: */
085: public void parse(ClassNameAndSuperclassInfo.Builder builder)
086: throws InvalidClassFileFormatException {
087: try {
088: int magic = in.readInt();
089: if (magic != 0xcafebabe)
090: throw new InvalidClassFileFormatException(
091: "Classfile header isn't 0xCAFEBABE",
092: expectedClassDescriptor, codeBaseEntry);
093: int major_version = in.readUnsignedShort();
094: int minor_version = in.readUnsignedShort();
095: int constant_pool_count = in.readUnsignedShort();
096:
097: constantPool = new Constant[constant_pool_count];
098: for (int i = 1; i < constantPool.length; i++) {
099: constantPool[i] = readConstant();
100: if (constantPool[i].tag == IClassConstants.CONSTANT_Double
101: || constantPool[i].tag == IClassConstants.CONSTANT_Long) {
102: // Double and Long constants take up two constant pool entries
103: ++i;
104: }
105: }
106:
107: int access_flags = in.readUnsignedShort();
108:
109: int this _class = in.readUnsignedShort();
110: ClassDescriptor this ClassDescriptor = getClassDescriptor(this _class);
111:
112: int super _class = in.readUnsignedShort();
113: ClassDescriptor super ClassDescriptor = getClassDescriptor(super _class);
114:
115: int interfaces_count = in.readUnsignedShort();
116: if (interfaces_count < 0) {
117: throw new InvalidClassFileFormatException(
118: expectedClassDescriptor, codeBaseEntry);
119: }
120: ClassDescriptor[] interfaceDescriptorList = new ClassDescriptor[interfaces_count];
121: for (int i = 0; i < interfaceDescriptorList.length; i++) {
122: interfaceDescriptorList[i] = getClassDescriptor(in
123: .readUnsignedShort());
124: }
125: // Extract all references to other classes,
126: // both CONSTANT_Class entries and also referenced method
127: // signatures.
128: Collection<ClassDescriptor> referencedClassDescriptorList = extractReferencedClasses();
129:
130: builder.setClassDescriptor(this ClassDescriptor);
131: builder.setSuperclassDescriptor(super ClassDescriptor);
132: builder.setInterfaceDescriptorList(interfaceDescriptorList);
133: builder.setCodeBaseEntry(codeBaseEntry);
134: builder.setAccessFlags(access_flags);
135: builder
136: .setReferencedClassDescriptors(referencedClassDescriptorList);
137: builder.setClassfileVersion(major_version, minor_version);
138: } catch (IOException e) {
139: throw new InvalidClassFileFormatException(
140: expectedClassDescriptor, codeBaseEntry, e);
141: }
142: }
143:
144: /* (non-Javadoc)
145: * @see edu.umd.cs.findbugs.classfile.engine.ClassParserInterface#parse(edu.umd.cs.findbugs.classfile.analysis.ClassInfo.Builder)
146: */
147: public void parse(ClassInfo.Builder builder)
148: throws InvalidClassFileFormatException {
149: throw new UnsupportedOperationException(
150: "Need to use a ClassParserUsingASM to build ClassInfo");
151: }
152:
153: /**
154: * Extract references to other classes.
155: *
156: * @return array of ClassDescriptors of referenced classes
157: * @throws InvalidClassFileFormatException
158: */
159: private Collection<ClassDescriptor> extractReferencedClasses()
160: throws InvalidClassFileFormatException {
161: Set<ClassDescriptor> referencedClassSet = new HashSet<ClassDescriptor>();
162: for (Constant constant : constantPool) {
163: if (constant == null) {
164: continue;
165: }
166: if (constant.tag == IClassConstants.CONSTANT_Class) {
167: @SlashedClassName
168: String className = getUtf8String((Integer) constant.data[0]);
169: if (className.indexOf('[') >= 0) {
170: extractReferencedClassesFromSignature(
171: referencedClassSet, className);
172: } else if (ClassName.isValidClassName(className)) {
173: referencedClassSet.add(DescriptorFactory.instance()
174: .getClassDescriptor(className));
175: }
176: } else if (constant.tag == IClassConstants.CONSTANT_Methodref
177: || constant.tag == IClassConstants.CONSTANT_Fieldref
178: || constant.tag == IClassConstants.CONSTANT_InterfaceMethodref) {
179: // Get the target class name
180: String className = getClassName((Integer) constant.data[0]);
181: extractReferencedClassesFromSignature(
182: referencedClassSet, className);
183:
184: // Parse signature to extract class names
185: String signature = getSignatureFromNameAndType((Integer) constant.data[1]);
186: extractReferencedClassesFromSignature(
187: referencedClassSet, signature);
188: }
189: }
190: return referencedClassSet;
191: }
192:
193: /**
194: * @param referencedClassSet
195: * @param signature
196: */
197: public static void extractReferencedClassesFromSignature(
198: Set<ClassDescriptor> referencedClassSet, String signature) {
199: while (signature.length() > 0) {
200: int start = signature.indexOf('L');
201: if (start < 0) {
202: break;
203: }
204: int end = signature.indexOf(';', start);
205: if (end < 0) {
206: break;
207: }
208: @SlashedClassName
209: String className = signature.substring(start + 1, end);
210: if (ClassName.isValidClassName(className)) {
211: referencedClassSet.add(DescriptorFactory.instance()
212: .getClassDescriptor(className));
213: }
214: signature = signature.substring(end + 1);
215: }
216: }
217:
218: // 8: UTF-8 string
219: // I: int
220: // F: float
221: // L: long
222: // D: double
223: // i: 2-byte constant pool index
224: private static final String[] CONSTANT_FORMAT_MAP = { null, "8", // 1: CONSTANT_Utf8
225: null, "I", // 3: CONSTANT_Integer
226: "F", // 4: CONSTANT_Float
227: "L", // 5: CONSTANT_Long
228: "D", // 6: CONSTANT_Double
229: "i", // 7: CONSTANT_Class
230: "i", // 8: CONSTANT_String
231: "ii", // 9: CONSTANT_Fieldref
232: "ii", // 10: CONSTANT_Methodref
233: "ii", // 11: CONSTANT_InterfaceMethodref
234: "ii", // 12: CONSTANT_NameAndType
235: };
236:
237: /**
238: * Read a constant from the constant pool.
239: *
240: * @return a Constant
241: * @throws InvalidClassFileFormatException
242: * @throws IOException
243: */
244: private Constant readConstant()
245: throws InvalidClassFileFormatException, IOException {
246: int tag = in.readUnsignedByte();
247: if (tag < 0 || tag >= CONSTANT_FORMAT_MAP.length
248: || CONSTANT_FORMAT_MAP[tag] == null) {
249: throw new InvalidClassFileFormatException(
250: expectedClassDescriptor, codeBaseEntry);
251: }
252: String format = CONSTANT_FORMAT_MAP[tag];
253: Object[] data = new Object[format.length()];
254: for (int i = 0; i < format.length(); i++) {
255: char spec = format.charAt(i);
256: switch (spec) {
257: case '8':
258: data[i] = in.readUTF();
259: break;
260: case 'I':
261: data[i] = (Integer) in.readInt();
262: break;
263: case 'F':
264: data[i] = new Float(in.readFloat());
265: break;
266: case 'L':
267: data[i] = (Long) in.readLong();
268: break;
269: case 'D':
270: data[i] = new Double(in.readDouble());
271: break;
272: case 'i':
273: data[i] = (Integer) in.readUnsignedShort();
274: break;
275: default:
276: throw new IllegalStateException();
277: }
278: }
279:
280: return new Constant(tag, data);
281: }
282:
283: /**
284: * Get a class name from a CONSTANT_Class.
285: * Note that this may be an array (e.g., "[Ljava/lang/String;").
286: *
287: * @param index index of the constant
288: * @return the class name
289: * @throws InvalidClassFileFormatException
290: */
291: private @SlashedClassName
292: String getClassName(int index)
293: throws InvalidClassFileFormatException {
294: if (index == 0) {
295: return null;
296: }
297:
298: checkConstantPoolIndex(index);
299: Constant constant = constantPool[index];
300: checkConstantTag(constant, IClassConstants.CONSTANT_Class);
301:
302: int refIndex = ((Integer) constant.data[0]).intValue();
303: String stringValue = getUtf8String(refIndex);
304:
305: return stringValue;
306: }
307:
308: /**
309: * Get the ClassDescriptor of a class referenced in the constant pool.
310: *
311: * @param index index of the referenced class in the constant pool
312: * @return the ClassDescriptor of the referenced class
313: * @throws InvalidClassFileFormatException
314: */
315: private ClassDescriptor getClassDescriptor(int index)
316: throws InvalidClassFileFormatException {
317: @SlashedClassName
318: String className = getClassName(index);
319: return className != null ? DescriptorFactory.instance()
320: .getClassDescriptor(className) : null;
321: }
322:
323: /**
324: * Get the UTF-8 string constant at given constant pool index.
325: *
326: * @param refIndex the constant pool index
327: * @return the String at that index
328: * @throws InvalidClassFileFormatException
329: */
330: private String getUtf8String(int refIndex)
331: throws InvalidClassFileFormatException {
332: checkConstantPoolIndex(refIndex);
333: Constant refConstant = constantPool[refIndex];
334: checkConstantTag(refConstant, IClassConstants.CONSTANT_Utf8);
335: return (String) refConstant.data[0];
336: }
337:
338: /**
339: * Check that a constant pool index is valid.
340: *
341: * @param expectedClassDescriptor class descriptor
342: * @param constantPool the constant pool
343: * @param index the index to check
344: * @throws InvalidClassFileFormatException if the index is not valid
345: */
346: private void checkConstantPoolIndex(int index)
347: throws InvalidClassFileFormatException {
348: if (index < 0 || index >= constantPool.length
349: || constantPool[index] == null) {
350: throw new InvalidClassFileFormatException(
351: expectedClassDescriptor, codeBaseEntry);
352: }
353: }
354:
355: /**
356: * Check that a constant has the expected tag.
357: *
358: * @param constant the constant to check
359: * @param expectedTag the expected constant tag
360: * @throws InvalidClassFileFormatException if the constant's tag does not match the expected tag
361: */
362: private void checkConstantTag(Constant constant, int expectedTag)
363: throws InvalidClassFileFormatException {
364: if (constant.tag != expectedTag) {
365: throw new InvalidClassFileFormatException(
366: expectedClassDescriptor, codeBaseEntry);
367: }
368: }
369:
370: interface FieldOrMethodDescriptorCreator<E> {
371: public E create(String className, String name,
372: String signature, int accessFlags);
373: }
374:
375: /**
376: * Read field_info, return FieldDescriptor.
377: *
378: * @param thisClassDescriptor the ClassDescriptor of this class (being parsed)
379: * @return the FieldDescriptor
380: * @throws IOException
381: * @throws InvalidClassFileFormatException
382: */
383: private FieldDescriptor readField(
384: ClassDescriptor this ClassDescriptor) throws IOException,
385: InvalidClassFileFormatException {
386: return readFieldOrMethod(this ClassDescriptor,
387: new FieldOrMethodDescriptorCreator<FieldDescriptor>() {
388: /* (non-Javadoc)
389: * @see edu.umd.cs.findbugs.classfile.engine.ClassParser.FieldOrMethodDescriptorCreator#create(java.lang.String, java.lang.String, java.lang.String, int)
390: */
391: public FieldDescriptor create(String className,
392: String name, String signature,
393: int accessFlags) {
394: return DescriptorFactory
395: .instance()
396: .getFieldDescriptor(
397: className,
398: name,
399: signature,
400: (accessFlags & IClassConstants.ACC_STATIC) != 0);
401: }
402: });
403: }
404:
405: /**
406: * Read method_info, read method descriptor.
407: *
408: * @param thisClassDescriptor
409: * @return
410: * @throws IOException
411: * @throws InvalidClassFileFormatException
412: */
413: private MethodDescriptor readMethod(
414: ClassDescriptor this ClassDescriptor)
415: throws InvalidClassFileFormatException, IOException {
416: return readFieldOrMethod(this ClassDescriptor,
417: new FieldOrMethodDescriptorCreator<MethodDescriptor>() {
418: /* (non-Javadoc)
419: * @see edu.umd.cs.findbugs.classfile.engine.ClassParser.FieldOrMethodDescriptorCreator#create(java.lang.String, java.lang.String, java.lang.String, int)
420: */
421: public MethodDescriptor create(String className,
422: String name, String signature,
423: int accessFlags) {
424: return DescriptorFactory
425: .instance()
426: .getMethodDescriptor(
427: className,
428: name,
429: signature,
430: (accessFlags & IClassConstants.ACC_STATIC) != 0);
431: }
432: });
433: }
434:
435: /**
436: * Read field_info or method_info.
437: * They have the same format.
438: *
439: * @param <E> descriptor type to return
440: * @param thisClassDescriptor class descriptor of class being parsed
441: * @param creator callback to create the FieldDescriptor or MethodDescriptor
442: * @return the parsed descriptor
443: * @throws IOException
444: * @throws InvalidClassFileFormatException
445: */
446: private <E> E readFieldOrMethod(
447: ClassDescriptor this ClassDescriptor,
448: FieldOrMethodDescriptorCreator<E> creator)
449: throws IOException, InvalidClassFileFormatException {
450: int access_flags = in.readUnsignedShort();
451: int name_index = in.readUnsignedShort();
452: int descriptor_index = in.readUnsignedShort();
453: int attributes_count = in.readUnsignedShort();
454:
455: String name = getUtf8String(name_index);
456: String signature = getUtf8String(descriptor_index);
457: if (attributes_count < 0) {
458: throw new InvalidClassFileFormatException(
459: expectedClassDescriptor, codeBaseEntry);
460: }
461: for (int i = 0; i < attributes_count; i++) {
462: readAttribute();
463: }
464:
465: return creator.create(this ClassDescriptor.getClassName(), name,
466: signature, access_flags);
467: }
468:
469: /**
470: * Read an attribute.
471: *
472: * @throws IOException
473: * @throws InvalidClassFileFormatException
474: */
475: private void readAttribute() throws IOException,
476: InvalidClassFileFormatException {
477: int attribute_name_index = in.readUnsignedShort();
478: String attrName = getUtf8String(attribute_name_index);
479:
480: int attribute_length = in.readInt();
481: if (attribute_length < 0) {
482: throw new InvalidClassFileFormatException(
483: expectedClassDescriptor, codeBaseEntry);
484: }
485:
486: if (attrName.equals("InnerClasses")) {
487: readInnerClassesAttribute(attribute_length);
488: } else {
489: IO.skipFully(in, attribute_length);
490: }
491: }
492:
493: /**
494: * Read an InnerClasses attribute.
495: *
496: * @param attribute_length length of attribute (excluding first 6 bytes)
497: * @throws InvalidClassFileFormatException
498: * @throws IOException
499: */
500: private void readInnerClassesAttribute(int attribute_length)
501: throws InvalidClassFileFormatException, IOException {
502: int number_of_classes = in.readUnsignedShort();
503: if (attribute_length != number_of_classes * 8) {
504: throw new InvalidClassFileFormatException(
505: expectedClassDescriptor, codeBaseEntry);
506: }
507:
508: for (int i = 0; i < number_of_classes; i++) {
509: int inner_class_info_index = in.readUnsignedShort();
510: int outer_class_info_index = in.readUnsignedShort();
511: int inner_name_index = in.readUnsignedShort();
512: int inner_class_access_flags = in.readUnsignedShort();
513:
514: if (outer_class_info_index != 0) {
515: // Record which class this class is a member of.
516: this .immediateEnclosingClass = getClassDescriptor(outer_class_info_index);
517: }
518: }
519: }
520:
521: /**
522: * Get the signature from a CONSTANT_NameAndType.
523: *
524: * @param index the index of the CONSTANT_NameAndType
525: * @return the signature
526: * @throws InvalidClassFileFormatException
527: */
528: private String getSignatureFromNameAndType(int index)
529: throws InvalidClassFileFormatException {
530: checkConstantPoolIndex(index);
531: Constant constant = constantPool[index];
532: checkConstantTag(constant, IClassConstants.CONSTANT_NameAndType);
533: return getUtf8String((Integer) constant.data[1]);
534: }
535: }
|