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 // Actual Code layout selected after checking both the explicit option and the class file version. 064 private boolean compactCodeHeader; 065 // Versions 45.0 through 45.2 can also use standard Code headers, so the narrower layout requires an explicit option. 066 private boolean compactCodeHeaderEnabled; 067 private int accessFlags; // Access rights of parsed class 068 private int[] interfaces; // Names of implemented interfaces 069 private ConstantPool constantPool; // collection of constants 070 private Field[] fields; // class fields, that is, its variables 071 private Method[] methods; // methods defined in the class 072 private Attribute[] attributes; // attributes defined in the class 073 074 private final boolean isZip; // Loaded from ZIP file 075 076 /** 077 * Parses class from the given stream. 078 * 079 * @param inputStream Input stream. 080 * @param fileName File name. 081 */ 082 public ClassParser(final InputStream inputStream, final String fileName) { 083 this.fileName = fileName; 084 this.fileOwned = false; 085 final String clazz = inputStream.getClass().getName(); // Not a very clean solution ... 086 this.isZip = clazz.startsWith("java.util.zip.") || clazz.startsWith("java.util.jar."); 087 if (inputStream instanceof DataInputStream) { 088 this.dataInputStream = (DataInputStream) inputStream; 089 } else { 090 this.dataInputStream = new DataInputStream(new BufferedInputStream(inputStream, BUFSIZE)); 091 } 092 } 093 094 /** 095 * Parses class from given .class file. 096 * 097 * @param fileName file name. 098 */ 099 public ClassParser(final String fileName) { 100 this.isZip = false; 101 this.fileName = fileName; 102 this.fileOwned = true; 103 } 104 105 /** 106 * Parses class from given .class file in a ZIP-archive 107 * 108 * @param zipFile ZIP file name. 109 * @param fileName file name. 110 */ 111 public ClassParser(final String zipFile, final String fileName) { 112 this.isZip = true; 113 this.fileOwned = true; 114 this.zipFile = zipFile; 115 this.fileName = fileName; 116 } 117 118 /** 119 * Gets whether compact Code header parsing is enabled for class file versions 45.0 through 45.2. 120 * 121 * @return Whether compact Code header parsing is enabled. 122 * @since 6.14.0 123 * @see #setCompactCodeHeaderEnabled(boolean) 124 */ 125 public boolean isCompactCodeHeaderEnabled() { 126 return compactCodeHeaderEnabled; 127 } 128 129 /** 130 * Parses the given Java class file and return an object that represents the contained data, that is, constants, methods, 131 * fields and commands. A <em>ClassFormatException</em> is raised, if the file is not a valid .class file. (This does 132 * not include verification of the byte code as it is performed by the Java interpreter). 133 * 134 * <p> 135 * Code attributes use the standard eight-byte header by default, including in class files with version 45.0 through 45.2. 136 * Use {@link #setCompactCodeHeaderEnabled(boolean)} to read the compact four-byte header used by historical files with those versions. 137 * </p> 138 * 139 * @return Class object representing the parsed class file. 140 * @throws IOException Thrown if an I/O error occurs. 141 * @throws ClassFormatException Thrown if a class is malformed or cannot be interpreted as a class file. 142 */ 143 public JavaClass parse() throws IOException, ClassFormatException { 144 ZipFile zip = null; 145 try { 146 if (fileOwned) { 147 if (isZip) { 148 zip = new ZipFile(zipFile); 149 final ZipEntry entry = zip.getEntry(fileName); 150 151 if (entry == null) { 152 throw new IOException("File " + fileName + " not found"); 153 } 154 155 dataInputStream = new DataInputStream(new BufferedInputStream(zip.getInputStream(entry), BUFSIZE)); 156 } else { 157 dataInputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(fileName), BUFSIZE)); 158 } 159 } 160 // -- Read headers -- 161 // Check magic tag of class file 162 readID(); 163 // Get compiler version 164 readVersion(); 165 // -- Read constant pool and related **************/ 166 // Read constant pool entries 167 readConstantPool(); 168 // Get class information 169 readClassInfo(); 170 // Get interface information, that is, implemented interfaces 171 readInterfaces(); 172 // -- Read class fields and methods -- 173 // Read class fields, that is, the variables of the class 174 readFields(); 175 // Read class methods, that is, the functions in the class 176 readMethods(); 177 // Read class attributes 178 readAttributes(); 179 // Check for unknown variables 180 // Unknown[] u = Unknown.getUnknownAttributes(); 181 // for (int i=0; i < u.length; i++) 182 // System.err.println("WARNING: " + u[i]); 183 // Everything should have been read now 184 // if (file.available() > 0) { 185 // int bytes = file.available(); 186 // byte[] buf = new byte[bytes]; 187 // file.read(buf); 188 // if (!(isZip && (buf.length == 1))) { 189 // System.err.println("WARNING: Trailing garbage at end of " + fileName); 190 // System.err.println(bytes + " extra bytes: " + Utility.toHexString(buf)); 191 // } 192 // } 193 } finally { 194 // Read everything of interest, so close the file 195 if (fileOwned) { 196 IOUtils.closeQuietly(dataInputStream); 197 } 198 IOUtils.closeQuietly(zip); 199 } 200 // Return the information we have gathered in a new object 201 return new JavaClass(classNameIndex, superclassNameIndex, fileName, major, minor, accessFlags, constantPool, interfaces, fields, methods, attributes, 202 isZip ? JavaClass.ZIP : JavaClass.FILE); 203 } 204 205 /** 206 * Reads information about the attributes of the class. 207 * 208 * @throws IOException Thrown if an I/O error occurs. 209 * @throws ClassFormatException Thrown if a class is malformed or cannot be interpreted as a class file. 210 */ 211 private void readAttributes() throws IOException, ClassFormatException { 212 final int attributesCount = dataInputStream.readUnsignedShort(); 213 attributes = new Attribute[attributesCount]; 214 for (int i = 0; i < attributesCount; i++) { 215 attributes[i] = Attribute.readAttribute(dataInputStream, constantPool, compactCodeHeader); 216 } 217 } 218 219 /** 220 * Reads information about the class and its superclass. 221 * 222 * @throws IOException Thrown if an I/O error occurs. 223 * @throws ClassFormatException Thrown if a class is malformed or cannot be interpreted as a class file. 224 */ 225 private void readClassInfo() throws IOException, ClassFormatException { 226 accessFlags = dataInputStream.readUnsignedShort(); 227 /* 228 * Interfaces are implicitly abstract, the flag should be set according to the JVM specification. 229 */ 230 if ((accessFlags & Const.ACC_INTERFACE) != 0) { 231 accessFlags |= Const.ACC_ABSTRACT; 232 } 233 if ((accessFlags & Const.ACC_ABSTRACT) != 0 && (accessFlags & Const.ACC_FINAL) != 0) { 234 throw new ClassFormatException("Class %s can't be both final and abstract", fileName); 235 } 236 classNameIndex = dataInputStream.readUnsignedShort(); 237 superclassNameIndex = dataInputStream.readUnsignedShort(); 238 } 239 240 /** 241 * Reads constant pool entries. 242 * 243 * @throws IOException Thrown if an I/O error occurs. 244 * @throws ClassFormatException Thrown if a class is malformed or cannot be interpreted as a class file. 245 */ 246 private void readConstantPool() throws IOException, ClassFormatException { 247 constantPool = new ConstantPool(dataInputStream); 248 } 249 250 /** 251 * Reads information about the fields of the class, that is, its variables. 252 * 253 * @throws IOException Thrown if an I/O error occurs. 254 * @throws ClassFormatException Thrown if a class is malformed or cannot be interpreted as a class file. 255 */ 256 private void readFields() throws IOException, ClassFormatException { 257 final int fieldsCount = dataInputStream.readUnsignedShort(); 258 fields = new Field[fieldsCount]; 259 for (int i = 0; i < fieldsCount; i++) { 260 fields[i] = new Field(dataInputStream, constantPool, compactCodeHeader); 261 } 262 } 263 264 /** 265 * Checks whether the header of the file is ok. Of course, this has to be the first action on successive file reads. 266 * 267 * @throws IOException Thrown if an I/O error occurs. 268 * @throws ClassFormatException Thrown if a class is malformed or cannot be interpreted as a class file. 269 */ 270 private void readID() throws IOException, ClassFormatException { 271 if (dataInputStream.readInt() != Const.JVM_CLASSFILE_MAGIC) { 272 throw new ClassFormatException("%s is not a Java .class file", fileName); 273 } 274 } 275 276 /** 277 * Reads information about the interfaces implemented by this class. 278 * 279 * @throws IOException Thrown if an I/O error occurs. 280 * @throws ClassFormatException Thrown if a class is malformed or cannot be interpreted as a class file. 281 */ 282 private void readInterfaces() throws IOException, ClassFormatException { 283 interfaces = readU2U2Table(dataInputStream); 284 } 285 286 /** 287 * Reads information about the methods of the class. 288 * 289 * @throws IOException Thrown if an I/O error occurs. 290 * @throws ClassFormatException Thrown if a class is malformed or cannot be interpreted as a class file. 291 */ 292 private void readMethods() throws IOException { 293 final int methodsCount = dataInputStream.readUnsignedShort(); 294 methods = new Method[methodsCount]; 295 for (int i = 0; i < methodsCount; i++) { 296 methods[i] = new Method(dataInputStream, constantPool, compactCodeHeader); 297 } 298 } 299 300 /** 301 * Reads major and minor version of compiler which created the file. 302 * 303 * @throws IOException Thrown if an I/O error occurs. 304 * @throws ClassFormatException Thrown if a class is malformed or cannot be interpreted as a class file. 305 */ 306 private void readVersion() throws IOException, ClassFormatException { 307 minor = dataInputStream.readUnsignedShort(); 308 major = dataInputStream.readUnsignedShort(); 309 // The option resolves the ambiguous layout for 45.0 through 45.2; versions 45.3 and later always use standard headers. 310 compactCodeHeader = compactCodeHeaderEnabled && major == Const.MAJOR_1_1 && minor < Const.MINOR_1_1; 311 } 312 313 /** 314 * Sets whether to read compact Code headers in class file versions 45.0 through 45.2. 315 * 316 * <p> 317 * "Compact" refers to the widths of {@code max_stack}, {@code max_locals}, and {@code code_length}: unsigned one-, one-, and two-byte fields, 318 * respectively, totaling 4 bytes. Standard headers use unsigned two-, two-, and four-byte fields, totaling 8 bytes. These sizes exclude the common 319 * six-byte attribute prefix. The layout of that prefix and the remaining Code fields is unchanged. 320 * </p> 321 * 322 * <p> 323 * This option is disabled by default because modern JVMs also accept versions 45.0 through 45.2 with standard headers. The version therefore 324 * cannot determine the layout by itself; enabling this option selects the historical compact layout. Other class file versions always use standard 325 * headers, regardless of this option. Set this option before calling {@link #parse()}. 326 * </p> 327 * 328 * @param enabled Whether to enable compact Code header parsing. 329 * @since 6.14.0 330 * @see Code#isCompactCodeHeader() 331 */ 332 public void setCompactCodeHeaderEnabled(final boolean enabled) { 333 compactCodeHeaderEnabled = enabled; 334 } 335}