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}