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