    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.DataInputStream;
    023import java.io.FileInputStream;
    024import java.io.IOException;
    025import java.io.InputStream;
    026import java.util.zip.ZipEntry;
    027import java.util.zip.ZipFile;
    028
    029import org.apache.bcel.Const;
    030import org.apache.commons.io.IOUtils;
    031
    032/**
    033 * Wrapper class that parses a given Java .class file. The method <a href ="#parse">parse</a> returns a
    034 * <a href ="JavaClass.html"> JavaClass</a> object on success. When an I/O error or an inconsistency occurs an
    035 * appropriate exception is propagated back to the caller.
    036 *
    037 * The structure and the names comply, except for a few conveniences, exactly with the
    038 * <a href="https://docs.oracle.com/javase/specs/"> JVM specification 1.0</a>. See this paper for further details about
    039 * the structure of a bytecode file.
    040 */
    041public final class ClassParser {
    042
    043    private static final int BUFSIZE = 8192;
    044    private DataInputStream dataInputStream;
    045    private final boolean fileOwned;
    046    private final String fileName;
    047    private String zipFile;
    048    private int classNameIndex;
    049    private int superclassNameIndex;
    050    private int major; // Compiler version
    051    private int minor; // Compiler version
    052    private int accessFlags; // Access rights of parsed class
    053    private int[] interfaces; // Names of implemented interfaces
    054    private ConstantPool constantPool; // collection of constants
    055    private Field[] fields; // class fields, i.e., its variables
    056    private Method[] methods; // methods defined in the class
    057    private Attribute[] attributes; // attributes defined in the class
    058    private final boolean isZip; // Loaded from ZIP file
    059
    060    /**
    061     * Parses class from the given stream.
    062     *
    063     * @param inputStream Input stream
    064     * @param fileName File name
    065     */
    066    public ClassParser(final InputStream inputStream, final String fileName) {
    067        this.fileName = fileName;
    068        this.fileOwned = false;
    069        final String clazz = inputStream.getClass().getName(); // Not a very clean solution ...
    070        this.isZip = clazz.startsWith("java.util.zip.") || clazz.startsWith("java.util.jar.");
    071        if (inputStream instanceof DataInputStream) {
    072            this.dataInputStream = (DataInputStream) inputStream;
    073        } else {
    074            this.dataInputStream = new DataInputStream(new BufferedInputStream(inputStream, BUFSIZE));
    075        }
    076    }
    077
    078    /**
    079     * Parses class from given .class file.
    080     *
    081     * @param fileName file name
    082     */
    083    public ClassParser(final String fileName) {
    084        this.isZip = false;
    085        this.fileName = fileName;
    086        this.fileOwned = true;
    087    }
    088
    089    /**
    090     * Parses class from given .class file in a ZIP-archive
    091     *
    092     * @param zipFile ZIP file name
    093     * @param fileName file name
    094     */
    095    public ClassParser(final String zipFile, final String fileName) {
    096        this.isZip = true;
    097        this.fileOwned = true;
    098        this.zipFile = zipFile;
    099        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}