View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.bcel.classfile;
20  
21  import java.io.BufferedInputStream;
22  import java.io.DataInput;
23  import java.io.DataInputStream;
24  import java.io.FileInputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.util.zip.ZipEntry;
28  import java.util.zip.ZipFile;
29  
30  import org.apache.bcel.Const;
31  import org.apache.commons.io.IOUtils;
32  
33  /**
34   * Wrapper class that parses a given Java .class file. The method <a href ="#parse">parse</a> returns a
35   * <a href ="JavaClass.html"> JavaClass</a> object on success. When an I/O error or an inconsistency occurs an
36   * appropriate exception is propagated back to the caller.
37   *
38   * The structure and the names comply, except for a few conveniences, exactly with the
39   * <a href="https://docs.oracle.com/javase/specs/"> JVM specification 1.0</a>. See this paper for further details about
40   * the structure of a bytecode file.
41   */
42  public final class ClassParser {
43  
44      private static final int BUFSIZE = 8192;
45  
46      static int[] readU2U2Table(final DataInput dataInput) throws IOException {
47          final int count = dataInput.readUnsignedShort();
48          final int[] table = new int[count];
49          for (int i = 0; i < count; i++) {
50              table[i] = dataInput.readUnsignedShort();
51          }
52          return table;
53      }
54  
55      private DataInputStream dataInputStream;
56      private final boolean fileOwned;
57      private final String fileName;
58      private String zipFile;
59      private int classNameIndex;
60      private int superclassNameIndex;
61      private int major; // Compiler version
62      private int minor; // Compiler version
63      private int accessFlags; // Access rights of parsed class
64      private int[] interfaces; // Names of implemented interfaces
65      private ConstantPool constantPool; // collection of constants
66      private Field[] fields; // class fields, that is, its variables
67      private Method[] methods; // methods defined in the class
68      private Attribute[] attributes; // attributes defined in the class
69  
70      private final boolean isZip; // Loaded from ZIP file
71  
72      /**
73       * Parses class from the given stream.
74       *
75       * @param inputStream Input stream.
76       * @param fileName File name.
77       */
78      public ClassParser(final InputStream inputStream, final String fileName) {
79          this.fileName = fileName;
80          this.fileOwned = false;
81          final String clazz = inputStream.getClass().getName(); // Not a very clean solution ...
82          this.isZip = clazz.startsWith("java.util.zip.") || clazz.startsWith("java.util.jar.");
83          if (inputStream instanceof DataInputStream) {
84              this.dataInputStream = (DataInputStream) inputStream;
85          } else {
86              this.dataInputStream = new DataInputStream(new BufferedInputStream(inputStream, BUFSIZE));
87          }
88      }
89  
90      /**
91       * Parses class from given .class file.
92       *
93       * @param fileName file name.
94       */
95      public ClassParser(final String fileName) {
96          this.isZip = false;
97          this.fileName = fileName;
98          this.fileOwned = true;
99      }
100 
101     /**
102      * Parses class from given .class file in a ZIP-archive
103      *
104      * @param zipFile ZIP file name.
105      * @param fileName file name.
106      */
107     public ClassParser(final String zipFile, final String fileName) {
108         this.isZip = true;
109         this.fileOwned = true;
110         this.zipFile = zipFile;
111         this.fileName = fileName;
112     }
113 
114     /**
115      * Parses the given Java class file and return an object that represents the contained data, that is, constants, methods,
116      * fields and commands. A <em>ClassFormatException</em> is raised, if the file is not a valid .class file. (This does
117      * not include verification of the byte code as it is performed by the Java interpreter).
118      *
119      * @return Class object representing the parsed class file.
120      * @throws IOException if an I/O error occurs.
121      * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file.
122      */
123     public JavaClass parse() throws IOException, ClassFormatException {
124         ZipFile zip = null;
125         try {
126             if (fileOwned) {
127                 if (isZip) {
128                     zip = new ZipFile(zipFile);
129                     final ZipEntry entry = zip.getEntry(fileName);
130 
131                     if (entry == null) {
132                         throw new IOException("File " + fileName + " not found");
133                     }
134 
135                     dataInputStream = new DataInputStream(new BufferedInputStream(zip.getInputStream(entry), BUFSIZE));
136                 } else {
137                     dataInputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(fileName), BUFSIZE));
138                 }
139             }
140             // -- Read headers --
141             // Check magic tag of class file
142             readID();
143             // Get compiler version
144             readVersion();
145             // -- Read constant pool and related **************/
146             // Read constant pool entries
147             readConstantPool();
148             // Get class information
149             readClassInfo();
150             // Get interface information, that is, implemented interfaces
151             readInterfaces();
152             // -- Read class fields and methods --
153             // Read class fields, that is, the variables of the class
154             readFields();
155             // Read class methods, that is, the functions in the class
156             readMethods();
157             // Read class attributes
158             readAttributes();
159             // Check for unknown variables
160             // Unknown[] u = Unknown.getUnknownAttributes();
161             // for (int i=0; i < u.length; i++)
162             // System.err.println("WARNING: " + u[i]);
163             // Everything should have been read now
164             // if (file.available() > 0) {
165             // int bytes = file.available();
166             // byte[] buf = new byte[bytes];
167             // file.read(buf);
168             // if (!(isZip && (buf.length == 1))) {
169             // System.err.println("WARNING: Trailing garbage at end of " + fileName);
170             // System.err.println(bytes + " extra bytes: " + Utility.toHexString(buf));
171             // }
172             // }
173         } finally {
174             // Read everything of interest, so close the file
175             if (fileOwned) {
176                 IOUtils.closeQuietly(dataInputStream);
177             }
178             IOUtils.closeQuietly(zip);
179         }
180         // Return the information we have gathered in a new object
181         return new JavaClass(classNameIndex, superclassNameIndex, fileName, major, minor, accessFlags, constantPool, interfaces, fields, methods, attributes,
182             isZip ? JavaClass.ZIP : JavaClass.FILE);
183     }
184 
185     /**
186      * Reads information about the attributes of the class.
187      *
188      * @throws IOException if an I/O error occurs.
189      * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file.
190      */
191     private void readAttributes() throws IOException, ClassFormatException {
192         final int attributesCount = dataInputStream.readUnsignedShort();
193         attributes = new Attribute[attributesCount];
194         for (int i = 0; i < attributesCount; i++) {
195             attributes[i] = Attribute.readAttribute(dataInputStream, constantPool);
196         }
197     }
198 
199     /**
200      * Reads information about the class and its super class.
201      *
202      * @throws IOException if an I/O error occurs.
203      * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file.
204      */
205     private void readClassInfo() throws IOException, ClassFormatException {
206         accessFlags = dataInputStream.readUnsignedShort();
207         /*
208          * Interfaces are implicitly abstract, the flag should be set according to the JVM specification.
209          */
210         if ((accessFlags & Const.ACC_INTERFACE) != 0) {
211             accessFlags |= Const.ACC_ABSTRACT;
212         }
213         if ((accessFlags & Const.ACC_ABSTRACT) != 0 && (accessFlags & Const.ACC_FINAL) != 0) {
214             throw new ClassFormatException("Class " + fileName + " can't be both final and abstract");
215         }
216         classNameIndex = dataInputStream.readUnsignedShort();
217         superclassNameIndex = dataInputStream.readUnsignedShort();
218     }
219 
220     /**
221      * Reads constant pool entries.
222      *
223      * @throws IOException if an I/O error occurs.
224      * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file.
225      */
226     private void readConstantPool() throws IOException, ClassFormatException {
227         constantPool = new ConstantPool(dataInputStream);
228     }
229 
230     /**
231      * Reads information about the fields of the class, that is, its variables.
232      *
233      * @throws IOException if an I/O error occurs.
234      * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file.
235      */
236     private void readFields() throws IOException, ClassFormatException {
237         final int fieldsCount = dataInputStream.readUnsignedShort();
238         fields = new Field[fieldsCount];
239         for (int i = 0; i < fieldsCount; i++) {
240             fields[i] = new Field(dataInputStream, constantPool);
241         }
242     }
243 
244     /**
245      * Checks whether the header of the file is ok. Of course, this has to be the first action on successive file reads.
246      *
247      * @throws IOException if an I/O error occurs.
248      * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file.
249      */
250     private void readID() throws IOException, ClassFormatException {
251         if (dataInputStream.readInt() != Const.JVM_CLASSFILE_MAGIC) {
252             throw new ClassFormatException(fileName + " is not a Java .class file");
253         }
254     }
255 
256     /**
257      * Reads information about the interfaces implemented by this class.
258      *
259      * @throws IOException if an I/O error occurs.
260      * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file.
261      */
262     private void readInterfaces() throws IOException, ClassFormatException {
263         interfaces = readU2U2Table(dataInputStream);
264     }
265 
266     /**
267      * Reads information about the methods of the class.
268      *
269      * @throws IOException if an I/O error occurs.
270      * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file.
271      */
272     private void readMethods() throws IOException {
273         final int methodsCount = dataInputStream.readUnsignedShort();
274         methods = new Method[methodsCount];
275         for (int i = 0; i < methodsCount; i++) {
276             methods[i] = new Method(dataInputStream, constantPool);
277         }
278     }
279 
280     /**
281      * Reads major and minor version of compiler which created the file.
282      *
283      * @throws IOException if an I/O error occurs.
284      * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file.
285      */
286     private void readVersion() throws IOException, ClassFormatException {
287         minor = dataInputStream.readUnsignedShort();
288         major = dataInputStream.readUnsignedShort();
289     }
290 }