    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}