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