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