001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.bcel.classfile;
020
021import java.io.BufferedInputStream;
022import java.io.DataInput;
023import java.io.DataInputStream;
024import java.io.FileInputStream;
025import java.io.IOException;
026import java.io.InputStream;
027import java.util.zip.ZipEntry;
028import java.util.zip.ZipFile;
029
030import org.apache.bcel.Const;
031import org.apache.commons.io.IOUtils;
032
033/**
034 * Wrapper class that parses a given Java .class file. The method <a href ="#parse">parse</a> returns a
035 * <a href ="JavaClass.html"> JavaClass</a> object on success. When an I/O error or an inconsistency occurs an
036 * appropriate exception is propagated back to the caller.
037 *
038 * The structure and the names comply, except for a few conveniences, exactly with the
039 * <a href="https://docs.oracle.com/javase/specs/"> JVM specification 1.0</a>. See this paper for further details about
040 * the structure of a bytecode file.
041 */
042public final class ClassParser {
043
044    private static final int BUFSIZE = 8192;
045
046    static int[] readU2U2Table(final DataInput dataInput) throws IOException {
047        final int count = dataInput.readUnsignedShort();
048        final int[] table = new int[count];
049        for (int i = 0; i < count; i++) {
050            table[i] = dataInput.readUnsignedShort();
051        }
052        return table;
053    }
054
055    private DataInputStream dataInputStream;
056    private final boolean fileOwned;
057    private final String fileName;
058    private String zipFile;
059    private int classNameIndex;
060    private int superclassNameIndex;
061    private int major; // Compiler version
062    private int minor; // Compiler version
063    private int accessFlags; // Access rights of parsed class
064    private int[] interfaces; // Names of implemented interfaces
065    private ConstantPool constantPool; // collection of constants
066    private Field[] fields; // class fields, that is, its variables
067    private Method[] methods; // methods defined in the class
068    private Attribute[] attributes; // attributes defined in the class
069
070    private final boolean isZip; // Loaded from ZIP file
071
072    /**
073     * Parses class from the given stream.
074     *
075     * @param inputStream Input stream.
076     * @param fileName File name.
077     */
078    public ClassParser(final InputStream inputStream, final String fileName) {
079        this.fileName = fileName;
080        this.fileOwned = false;
081        final String clazz = inputStream.getClass().getName(); // Not a very clean solution ...
082        this.isZip = clazz.startsWith("java.util.zip.") || clazz.startsWith("java.util.jar.");
083        if (inputStream instanceof DataInputStream) {
084            this.dataInputStream = (DataInputStream) inputStream;
085        } else {
086            this.dataInputStream = new DataInputStream(new BufferedInputStream(inputStream, BUFSIZE));
087        }
088    }
089
090    /**
091     * Parses class from given .class file.
092     *
093     * @param fileName file name.
094     */
095    public ClassParser(final String fileName) {
096        this.isZip = false;
097        this.fileName = fileName;
098        this.fileOwned = true;
099    }
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}