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}