    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}