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 }