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