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