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 }