    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 *
    017 */
    018package org.apache.bcel.classfile;
    019
    020import java.io.BufferedInputStream;
    021import java.io.DataInputStream;
    022import java.io.FileInputStream;
    023import java.io.IOException;
    024import java.io.InputStream;
    025import java.util.zip.ZipEntry;
    026import java.util.zip.ZipFile;
    027
    028import org.apache.bcel.Const;
    029
    030/**
    031 * Wrapper class that parses a given Java .class file. The method <a href ="#parse">parse</a> returns a
    032 * <a href ="JavaClass.html"> JavaClass</a> object on success. When an I/O error or an inconsistency occurs an
    033 * appropriate exception is propagated back to the caller.
    034 *
    035 * The structure and the names comply, except for a few conveniences, exactly with the
    036 * <a href="http://docs.oracle.com/javase/specs/"> JVM specification 1.0</a>. See this paper for further details about
    037 * the structure of a bytecode file.
    038 *
    039 */
    040public final class ClassParser {
    041
    042    private static final int BUFSIZE = 8192;
    043    private DataInputStream dataInputStream;
    044    private final boolean fileOwned;
    045    private final String fileName;
    046    private String zipFile;
    047    private int classNameIndex;
    048    private int superclassNameIndex;
    049    private int major; // Compiler version
    050    private int minor; // Compiler version
    051    private int accessFlags; // Access rights of parsed class
    052    private int[] interfaces; // Names of implemented interfaces
    053    private ConstantPool constantPool; // collection of constants
    054    private Field[] fields; // class fields, i.e., its variables
    055    private Method[] methods; // methods defined in the class
    056    private Attribute[] attributes; // attributes defined in the class
    057    private final boolean isZip; // Loaded from zip file
    058
    059    /**
    060     * Parses class from the given stream.
    061     *
    062     * @param inputStream Input stream
    063     * @param fileName File name
    064     */
    065    public ClassParser(final InputStream inputStream, final String fileName) {
    066        this.fileName = fileName;
    067        this.fileOwned = false;
    068        final String clazz = inputStream.getClass().getName(); // Not a very clean solution ...
    069        this.isZip = clazz.startsWith("java.util.zip.") || clazz.startsWith("java.util.jar.");
    070        if (inputStream instanceof DataInputStream) {
    071            this.dataInputStream = (DataInputStream) inputStream;
    072        } else {
    073            this.dataInputStream = new DataInputStream(new BufferedInputStream(inputStream, BUFSIZE));
    074        }
    075    }
    076
    077    /**
    078     * Parses class from given .class file.
    079     *
    080     * @param fileName file name
    081     */
    082    public ClassParser(final String fileName) {
    083        this.isZip = false;
    084        this.fileName = fileName;
    085        this.fileOwned = true;
    086    }
    087
    088    /**
    089     * Parses class from given .class file in a ZIP-archive
    090     *
    091     * @param zipFile zip file name
    092     * @param fileName file name
    093     */
    094    public ClassParser(final String zipFile, final String fileName) {
    095        this.isZip = true;
    096        this.fileOwned = true;
    097        this.zipFile = zipFile;
    098        this.fileName = fileName;
    099    }
    100
    101    /**
    102     * Parses the given Java class file and return an object that represents the contained data, i.e., constants, methods,
    103     * fields and commands. A <em>ClassFormatException</em> is raised, if the file is not a valid .class file. (This does
    104     * not include verification of the byte code as it is performed by the java interpreter).
    105     *
    106     * @return Class object representing the parsed class file
    107     * @throws IOException if an I/O error occurs.
    108     * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
    109     */
    110    public JavaClass parse() throws IOException, ClassFormatException {
    111        ZipFile zip = null;
    112        try {
    113            if (fileOwned) {
    114                if (isZip) {
    115                    zip = new ZipFile(zipFile);
    116                    final ZipEntry entry = zip.getEntry(fileName);
    117
    118                    if (entry == null) {
    119                        throw new IOException("File " + fileName + " not found");
    120                    }
    121
    122                    dataInputStream = new DataInputStream(new BufferedInputStream(zip.getInputStream(entry), BUFSIZE));
    123                } else {
    124                    dataInputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(fileName), BUFSIZE));
    125                }
    126            }
    127            /****************** Read headers ********************************/
    128            // Check magic tag of class file
    129            readID();
    130            // Get compiler version
    131            readVersion();
    132            /****************** Read constant pool and related **************/
    133            // Read constant pool entries
    134            readConstantPool();
    135            // Get class information
    136            readClassInfo();
    137            // Get interface information, i.e., implemented interfaces
    138            readInterfaces();
    139            /****************** Read class fields and methods ***************/
    140            // Read class fields, i.e., the variables of the class
    141            readFields();
    142            // Read class methods, i.e., the functions in the class
    143            readMethods();
    144            // Read class attributes
    145            readAttributes();
    146            // Check for unknown variables
    147            // Unknown[] u = Unknown.getUnknownAttributes();
    148            // for (int i=0; i < u.length; i++)
    149            // System.err.println("WARNING: " + u[i]);
    150            // Everything should have been read now
    151            // if(file.available() > 0) {
    152            // int bytes = file.available();
    153            // byte[] buf = new byte[bytes];
    154            // file.read(buf);
    155            // if(!(isZip && (buf.length == 1))) {
    156            // System.err.println("WARNING: Trailing garbage at end of " + fileName);
    157            // System.err.println(bytes + " extra bytes: " + Utility.toHexString(buf));
    158            // }
    159            // }
    160        } finally {
    161            // Read everything of interest, so close the file
    162            if (fileOwned) {
    163                try {
    164                    if (dataInputStream != null) {
    165                        dataInputStream.close();
    166                    }
    167                } catch (final IOException ioe) {
    168                    // ignore close exceptions
    169                }
    170            }
    171            try {
    172                if (zip != null) {
    173                    zip.close();
    174                }
    175            } catch (final IOException ioe) {
    176                // ignore close exceptions
    177            }
    178        }
    179        // Return the information we have gathered in a new object
    180        return new JavaClass(classNameIndex, superclassNameIndex, fileName, major, minor, accessFlags, constantPool, interfaces, fields, methods, attributes,
    181            isZip ? JavaClass.ZIP : JavaClass.FILE);
    182    }
    183
    184    /**
    185     * Reads information about the attributes of the class.
    186     *
    187     * @throws IOException if an I/O error occurs.
    188     * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
    189     */
    190    private void readAttributes() throws IOException, ClassFormatException {
    191        final int attributes_count = dataInputStream.readUnsignedShort();
    192        attributes = new Attribute[attributes_count];
    193        for (int i = 0; i < attributes_count; i++) {
    194            attributes[i] = Attribute.readAttribute(dataInputStream, constantPool);
    195        }
    196    }
    197
    198    /**
    199     * Reads information about the class and its super class.
    200     *
    201     * @throws IOException if an I/O error occurs.
    202     * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
    203     */
    204    private void readClassInfo() throws IOException, ClassFormatException {
    205        accessFlags = dataInputStream.readUnsignedShort();
    206        /*
    207         * Interfaces are implicitly abstract, the flag should be set according to the JVM specification.
    208         */
    209        if ((accessFlags & Const.ACC_INTERFACE) != 0) {
    210            accessFlags |= Const.ACC_ABSTRACT;
    211        }
    212        if ((accessFlags & Const.ACC_ABSTRACT) != 0 && (accessFlags & Const.ACC_FINAL) != 0) {
    213            throw new ClassFormatException("Class " + fileName + " can't be both final and abstract");
    214        }
    215        classNameIndex = dataInputStream.readUnsignedShort();
    216        superclassNameIndex = dataInputStream.readUnsignedShort();
    217    }
    218
    219    /**
    220     * Reads constant pool entries.
    221     *
    222     * @throws IOException if an I/O error occurs.
    223     * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
    224     */
    225    private void readConstantPool() throws IOException, ClassFormatException {
    226        constantPool = new ConstantPool(dataInputStream);
    227    }
    228
    229    /**
    230     * Reads information about the fields of the class, i.e., its variables.
    231     *
    232     * @throws IOException if an I/O error occurs.
    233     * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
    234     */
    235    private void readFields() throws IOException, ClassFormatException {
    236        final int fields_count = dataInputStream.readUnsignedShort();
    237        fields = new Field[fields_count];
    238        for (int i = 0; i < fields_count; i++) {
    239            fields[i] = new Field(dataInputStream, constantPool);
    240        }
    241    }
    242
    243    /******************** Private utility methods **********************/
    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        final int interfaces_count = dataInputStream.readUnsignedShort();
    264        interfaces = new int[interfaces_count];
    265        for (int i = 0; i < interfaces_count; i++) {
    266            interfaces[i] = dataInputStream.readUnsignedShort();
    267        }
    268    }
    269
    270    /**
    271     * Reads information about the methods of the class.
    272     *
    273     * @throws IOException if an I/O error occurs.
    274     * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
    275     */
    276    private void readMethods() throws IOException {
    277        final int methods_count = dataInputStream.readUnsignedShort();
    278        methods = new Method[methods_count];
    279        for (int i = 0; i < methods_count; i++) {
    280            methods[i] = new Method(dataInputStream, constantPool);
    281        }
    282    }
    283
    284    /**
    285     * Reads major and minor version of compiler which created the file.
    286     *
    287     * @throws IOException if an I/O error occurs.
    288     * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
    289     */
    290    private void readVersion() throws IOException, ClassFormatException {
    291        minor = dataInputStream.readUnsignedShort();
    292        major = dataInputStream.readUnsignedShort();
    293    }
    294}