Diff

Created Diff never expires
46 removals
536 lines
50 additions
538 lines
// ASM: a very small and fast Java bytecode manipulation framework
// ASM: a very small and fast Java bytecode manipulation framework
// Copyright (c) 2000-2011 INRIA, France Telecom
// Copyright (c) 2000-2011 INRIA, France Telecom
// All rights reserved.
// All rights reserved.
//
//
// Redistribution and use in source and binary forms, with or without
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// modification, are permitted provided that the following conditions
// are met:
// are met:
// 1. Redistributions of source code must retain the above copyright
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holders nor the names of its
// 3. Neither the name of the copyright holders nor the names of its
// contributors may be used to endorse or promote products derived from
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
// this software without specific prior written permission.
//
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
// THE POSSIBILITY OF SUCH DAMAGE.
package org.objectweb.asm;
package org.objectweb.asm;


import java.io.ByteArrayOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStream;


/**
/**
* A parser to make a {@link ClassVisitor} visit a ClassFile structure, as defined in the Java
* A parser to make a {@link ClassVisitor} visit a ClassFile structure, as defined in the Java
* Virtual Machine Specification (JVMS). This class parses the ClassFile content and calls the
* Virtual Machine Specification (JVMS). This class parses the ClassFile content and calls the
* appropriate visit methods of a given {@link ClassVisitor} for each field, method and bytecode
* appropriate visit methods of a given {@link ClassVisitor} for each field, method and bytecode
* instruction encountered.
* instruction encountered.
*
*
* @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html">JVMS 4</a>
* @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html">JVMS 4</a>
* @author Eric Bruneton
* @author Eric Bruneton
* @author Eugene Kuleshov
* @author Eugene Kuleshov
*/
*/
public class ClassReader {
public class ClassReader {


/**
/**
* A flag to skip the Code attributes. If this flag is set the Code attributes are neither parsed
* A flag to skip the Code attributes. If this flag is set the Code attributes are neither parsed
* nor visited.
* nor visited.
*/
*/
public static final int SKIP_CODE = 1;
public static final int SKIP_CODE = 1;


/**
/**
* A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable, LocalVariableTypeTable
* A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable,
* and LineNumberTable attributes. If this flag is set these attributes are neither parsed nor
* LocalVariableTypeTable, LineNumberTable and MethodParameters attributes. If this flag is set
* visited (i.e. {@link ClassVisitor#visitSource}, {@link MethodVisitor#visitLocalVariable} and
* these attributes are neither parsed nor visited (i.e. {@link ClassVisitor#visitSource}, {@link
* {@link MethodVisitor#visitLineNumber} are not called).
* MethodVisitor#visitLocalVariable}, {@link MethodVisitor#visitLineNumber} and {@link
* MethodVisitor#visitParameter} are not called).
*/
*/
public static final int SKIP_DEBUG = 2;
public static final int SKIP_DEBUG = 2;


/**
/**
* A flag to skip the StackMap and StackMapTable attributes. If this flag is set these attributes
* A flag to skip the StackMap and StackMapTable attributes. If this flag is set these attributes
* are neither parsed nor visited (i.e. {@link MethodVisitor#visitFrame} is not called). This flag
* are neither parsed nor visited (i.e. {@link MethodVisitor#visitFrame} is not called). This flag
* is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is used: it avoids visiting frames
* is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is used: it avoids visiting frames
* that will be ignored and recomputed from scratch.
* that will be ignored and recomputed from scratch.
*/
*/
public static final int SKIP_FRAMES = 4;
public static final int SKIP_FRAMES = 4;


/**
/**
* A flag to expand the stack map frames. By default stack map frames are visited in their
* A flag to expand the stack map frames. By default stack map frames are visited in their
* original format (i.e. "expanded" for classes whose version is less than V1_6, and "compressed"
* original format (i.e. "expanded" for classes whose version is less than V1_6, and "compressed"
* for the other classes). If this flag is set, stack map frames are always visited in expanded
* for the other classes). If this flag is set, stack map frames are always visited in expanded
* format (this option adds a decompression/compression step in ClassReader and ClassWriter which
* format (this option adds a decompression/compression step in ClassReader and ClassWriter which
* degrades performance quite a lot).
* degrades performance quite a lot).
*/
*/
public static final int EXPAND_FRAMES = 8;
public static final int EXPAND_FRAMES = 8;


/**
/**
* A flag to expand the ASM specific instructions into an equivalent sequence of standard bytecode
* A flag to expand the ASM specific instructions into an equivalent sequence of standard bytecode
* instructions. When resolving a forward jump it may happen that the signed 2 bytes offset
* instructions. When resolving a forward jump it may happen that the signed 2 bytes offset
* reserved for it is not sufficient to store the bytecode offset. In this case the jump
* reserved for it is not sufficient to store the bytecode offset. In this case the jump
* instruction is replaced with a temporary ASM specific instruction using an unsigned 2 bytes
* instruction is replaced with a temporary ASM specific instruction using an unsigned 2 bytes
* offset (see {@link Label#resolve}). This internal flag is used to re-read classes containing
* offset (see {@link Label#resolve}). This internal flag is used to re-read classes containing
* such instructions, in order to replace them with standard instructions. In addition, when this
* such instructions, in order to replace them with standard instructions. In addition, when this
* flag is used, goto_w and jsr_w are <i>not</i> converted into goto and jsr, to make sure that
* flag is used, goto_w and jsr_w are <i>not</i> converted into goto and jsr, to make sure that
* infinite loops where a goto_w is replaced with a goto in ClassReader and converted back to a
* infinite loops where a goto_w is replaced with a goto in ClassReader and converted back to a
* goto_w in ClassWriter cannot occur.
* goto_w in ClassWriter cannot occur.
*/
*/
static final int EXPAND_ASM_INSNS = 256;
static final int EXPAND_ASM_INSNS = 256;


/** The size of the temporary byte array used to read class input streams chunk by chunk. */
/** The size of the temporary byte array used to read class input streams chunk by chunk. */
private static final int INPUT_STREAM_DATA_CHUNK_SIZE = 4096;
private static final int INPUT_STREAM_DATA_CHUNK_SIZE = 4096;


/**
/**
* A byte array containing the JVMS ClassFile structure to be parsed.
*
* @deprecated Use {@link #readByte(int)} and the other read methods instead. This field will
* eventually be deleted.
*/
@Deprecated
// DontCheck(MemberName): can't be renamed (for backward binary compatibility).
public final byte[] b;

/**
* A byte array containing the JVMS ClassFile structure to be parsed. <i>The content of this array
* A byte array containing the JVMS ClassFile structure to be parsed. <i>The content of this array
* must not be modified. This field is intended for {@link Attribute} sub classes, and is normally
* must not be modified. This field is intended for {@link Attribute} sub classes, and is normally
* not needed by class visitors.</i>
* not needed by class visitors.</i>
*
*
* <p>NOTE: the ClassFile structure can start at any offset within this array, i.e. it does not
* <p>NOTE: the ClassFile structure can start at any offset within this array, i.e. it does not
* necessarily start at offset 0. Use {@link #getItem} and {@link #header} to get correct
* necessarily start at offset 0. Use {@link #getItem} and {@link #header} to get correct
* ClassFile element offsets within this byte array.
* ClassFile element offsets within this byte array.
*/
*/
// DontCheck(MemberName): can't be renamed (for backward binary compatibility).
final byte[] classFileBuffer;
public final byte[] b;


/**
/**
* The offset in bytes, in {@link #b}, of each cp_info entry of the ClassFile's constant_pool
* The offset in bytes, in {@link #classFileBuffer}, of each cp_info entry of the ClassFile's
* array, <i>plus one</i>. In other words, the offset of constant pool entry i is given by
* constant_pool array, <i>plus one</i>. In other words, the offset of constant pool entry i is
* cpInfoOffsets[i] - 1, i.e. its cp_info's tag field is given by b[cpInfoOffsets[i] - 1].
* given by cpInfoOffsets[i] - 1, i.e. its cp_info's tag field is given by b[cpInfoOffsets[i] -
* 1].
*/
*/
private final int[] cpInfoOffsets;
private final int[] cpInfoOffsets;


/**
/**
* The String objects corresponding to the CONSTANT_Utf8 constant pool items. This cache avoids
* The String objects corresponding to the CONSTANT_Utf8 constant pool items. This cache avoids
* multiple parsing of a given CONSTANT_Utf8 constant pool item.
* multiple parsing of a given CONSTANT_Utf8 constant pool item.
*/
*/
private final String[] constantUtf8Values;
private final String[] constantUtf8Values;


/**
/**
* The ConstantDynamic objects corresponding to the CONSTANT_Dynamic constant pool items. This
* The ConstantDynamic objects corresponding to the CONSTANT_Dynamic constant pool items. This
* cache avoids multiple parsing of a given CONSTANT_Dynamic constant pool item.
* cache avoids multiple parsing of a given CONSTANT_Dynamic constant pool item.
*/
*/
private final ConstantDynamic[] constantDynamicValues;
private final ConstantDynamic[] constantDynamicValues;


/**
/**
* The start offsets in {@link #b} of each element of the bootstrap_methods array (in the
* The start offsets in {@link #classFileBuffer} of each element of the bootstrap_methods array
* BootstrapMethods attribute).
* (in the BootstrapMethods attribute).
*
*
* @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.23">JVMS
* @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.23">JVMS
* 4.7.23</a>
* 4.7.23</a>
*/
*/
private final int[] bootstrapMethodOffsets;
private final int[] bootstrapMethodOffsets;


/**
/**
* A conservative estimate of the maximum length of the strings contained in the constant pool of
* A conservative estimate of the maximum length of the strings contained in the constant pool of
* the class.
* the class.
*/
*/
private final int maxStringLength;
private final int maxStringLength;


/** The offset in bytes, in {@link #b}, of the ClassFile's access_flags field. */
/** The offset in bytes of the ClassFile's access_flags field. */
public final int header;
public final int header;


// -----------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------
// Constructors
// Constructors
// -----------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------


/**
/**
* Constructs a new {@link ClassReader} object.
* Constructs a new {@link ClassReader} object.
*
*
* @param classFile the JVMS ClassFile structure to be read.
* @param classFile the JVMS ClassFile structure to be read.
*/
*/
public ClassReader(final byte[] classFile) {
public ClassReader(final byte[] classFile) {
this(classFile, 0, classFile.length);
this(classFile, 0, classFile.length);
}
}


/**
/**
* Constructs a new {@link ClassReader} object.
* Constructs a new {@link ClassReader} object.
*
*
* @param classFileBuffer a byte array containing the JVMS ClassFile structure to be read.
* @param classFileBuffer a byte array containing the JVMS ClassFile structure to be read.
* @param classFileOffset the offset in byteBuffer of the first byte of the ClassFile to be read.
* @param classFileOffset the offset in byteBuffer of the first byte of the ClassFile to be read.
* @param classFileLength the length in bytes of the ClassFile to be read.
* @param classFileLength the length in bytes of the ClassFile to be read.
*/
*/
public ClassReader(
public ClassReader(
final byte[] classFileBuffer,
final byte[] classFileBuffer,
final int classFileOffset,
final int classFileOffset,
final int classFileLength) { // NOPMD(UnusedFormalParameter) used for backward compatibility.
final int classFileLength) { // NOPMD(UnusedFormalParameter) used for backward compatibility.
this(classFileBuffer, classFileOffset, /* checkClassVersion = */ true);
this(classFileBuffer, classFileOffset, /* checkClassVersion = */ true);
}
}


/**
/**
* Constructs a new {@link ClassReader} object. <i>This internal constructor must not be exposed
* Constructs a new {@link ClassReader} object. <i>This internal constructor must not be exposed
* as a public API</i>.
* as a public API</i>.
*
*
* @param classFileBuffer a byte array containing the JVMS ClassFile structure to be read.
* @param classFileBuffer a byte array containing the JVMS ClassFile structure to be read.
* @param classFileOffset the offset in byteBuffer of the first byte of the ClassFile to be read.
* @param classFileOffset the offset in byteBuffer of the first byte of the ClassFile to be read.
* @param checkClassVersion whether to check the class version or not.
* @param checkClassVersion whether to check the class version or not.
*/
*/
ClassReader(
ClassReader(
final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) {
final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) {
b = classFileBuffer;
this.classFileBuffer = classFileBuffer;
this.b = classFileBuffer;
// Check the class' major_version. This field is after the magic and minor_version fields, which
// Check the class' major_version. This field is after the magic and minor_version fields, which
// use 4 and 2 bytes respectively.
// use 4 and 2 bytes respectively.
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V12) {
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V15) {
throw new IllegalArgumentException(
throw new IllegalArgumentException(
"Unsupported class file major version " + readShort(classFileOffset + 6));
"Unsupported class file major version " + readShort(classFileOffset + 6));
}
}
// Create the constant pool arrays. The constant_pool_count field is after the magic,
// Create the constant pool arrays. The constant_pool_count field is after the magic,
// minor_version and major_version fields, which use 4, 2 and 2 bytes respectively.
// minor_version and major_version fields, which use 4, 2 and 2 bytes respectively.
int constantPoolCount = readUnsignedShort(classFileOffset + 8);
int constantPoolCount = readUnsignedShort(classFileOffset + 8);
cpInfoOffsets = new int[constantPoolCount];
cpInfoOffsets = new int[constantPoolCount];
constantUtf8Values = new String[constantPoolCount];
constantUtf8Values = new String[constantPoolCount];
// Compute the offset of each constant pool entry, as well as a conservative estimate of the
// Compute the offset of each constant pool entry, as well as a conservative estimate of the
// maximum length of the constant pool strings. The first constant pool entry is after the
// maximum length of the constant pool strings. The first constant pool entry is after the
// magic, minor_version, major_version and constant_pool_count fields, which use 4, 2, 2 and 2
// magic, minor_version, major_version and constant_pool_count fields, which use 4, 2, 2 and 2
// bytes respectively.
// bytes respectively.
int currentCpInfoIndex = 1;
int currentCpInfoIndex = 1;
int currentCpInfoOffset = classFileOffset + 10;
int currentCpInfoOffset = classFileOffset + 10;
int currentMaxStringLength = 0;
int currentMaxStringLength = 0;
boolean hasBootstrapMethods = false;
boolean hasConstantDynamic = false;
boolean hasConstantDynamic = false;
boolean hasConstantInvokeDynamic = false;
// The offset of the other entries depend on the total size of all the previous entries.
// The offset of the other entries depend on the total size of all the previous entries.
while (currentCpInfoIndex < constantPoolCount) {
while (currentCpInfoIndex < constantPoolCount) {
cpInfoOffsets[currentCpInfoIndex++] = currentCpInfoOffset + 1;
cpInfoOffsets[currentCpInfoIndex++] = currentCpInfoOffset + 1;
int cpInfoSize;
int cpInfoSize;
switch (classFileBuffer[currentCpInfoOffset]) {
switch (classFileBuffer[currentCpInfoOffset]) {
case Symbol.CONSTANT_FIELDREF_TAG:
case Symbol.CONSTANT_FIELDREF_TAG:
case Symbol.CONSTANT_METHODREF_TAG:
case Symbol.CONSTANT_METHODREF_TAG:
case Symbol.CONSTANT_INTERFACE_METHODREF_TAG:
case Symbol.CONSTANT_INTERFACE_METHODREF_TAG:
case Symbol.CONSTANT_INTEGER_TAG:
case Symbol.CONSTANT_INTEGER_TAG:
case Symbol.CONSTANT_FLOAT_TAG:
case Symbol.CONSTANT_FLOAT_TAG:
case Symbol.CONSTANT_NAME_AND_TYPE_TAG:
case Symbol.CONSTANT_NAME_AND_TYPE_TAG:
cpInfoSize = 5;
cpInfoSize = 5;
break;
break;
case Symbol.CONSTANT_DYNAMIC_TAG:
case Symbol.CONSTANT_DYNAMIC_TAG:
cpInfoSize = 5;
cpInfoSize = 5;
hasBootstrapMethods = true;
hasConstantDynamic = true;
hasConstantDynamic = true;
break;
break;
case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG:
case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG:
cpInfoSize = 5;
cpInfoSize = 5;
hasConstantInvokeDynamic = true;
hasBootstrapMethods = true;
break;
break;
case Symbol.CONSTANT_LONG_TAG:
case Symbol.CONSTANT_LONG_TAG:
case Symbol.CONSTANT_DOUBLE_TAG:
case Symbol.CONSTANT_DOUBLE_TAG:
cpInfoSize = 9;
cpInfoSize = 9;
currentCpInfoIndex++;
currentCpInfoIndex++;
break;
break;
case Symbol.CONSTANT_UTF8_TAG:
case Symbol.CONSTANT_UTF8_TAG:
cpInfoSize = 3 + readUnsignedShort(currentCpInfoOffset + 1);
cpInfoSize = 3 + readUnsignedShort(currentCpInfoOffset + 1);
if (cpInfoSize > currentMaxStringLength) {
if (cpInfoSize > currentMaxStringLength) {
// The size in bytes of this CONSTANT_Utf8 structure provides a conservative estimate
// The size in bytes of this CONSTANT_Utf8 structure provides a conservative estimate
// of the length in characters of the corresponding string, and is much cheaper to
// of the length in characters of the corresponding string, and is much cheaper to
// compute than this exact length.
// compute than this exact length.
currentMaxStringLength = cpInfoSize;
currentMaxStringLength = cpInfoSize;
}
}
break;
break;
case Symbol.CONSTANT_METHOD_HANDLE_TAG:
case Symbol.CONSTANT_METHOD_HANDLE_TAG:
cpInfoSize = 4;
cpInfoSize = 4;
break;
break;
case Symbol.CONSTANT_CLASS_TAG:
case Symbol.CONSTANT_CLASS_TAG:
case Symbol.CONSTANT_STRING_TAG:
case Symbol.CONSTANT_STRING_TAG:
case Symbol.CONSTANT_METHOD_TYPE_TAG:
case Symbol.CONSTANT_METHOD_TYPE_TAG:
case Symbol.CONSTANT_PACKAGE_TAG:
case Symbol.CONSTANT_PACKAGE_TAG:
case Symbol.CONSTANT_MODULE_TAG:
case Symbol.CONSTANT_MODULE_TAG:
cpInfoSize = 3;
cpInfoSize = 3;
break;
break;
default:
default:
throw new IllegalArgumentException();
throw new IllegalArgumentException();
}
}
currentCpInfoOffset += cpInfoSize;
currentCpInfoOffset += cpInfoSize;
}
}
maxStringLength = currentMaxStringLength;
maxStringLength = currentMaxStringLength;
// The Classfile's access_flags field is just after the last constant pool entry.
// The Classfile's access_flags field is just after the last constant pool entry.
header = currentCpInfoOffset;
header = currentCpInfoOffset;


// Allocate the cache of ConstantDynamic values, if there is at least one.
// Allocate the cache of ConstantDynamic values, if there is at least one.
constantDynamicValues = hasConstantDynamic ? new ConstantDynamic[constantPoolCount] : null;
constantDynamicValues = hasConstantDynamic ? new ConstantDynamic[constantPoolCount] : null;


// Read the BootstrapMethods attribute, if any (only get the offset of each method).
// Read the BootstrapMethods attribute, if any (only get the offset of each method).
bootstrapMethodOffsets =
bootstrapMethodOffsets =
(hasConstantDynamic | hasConstantInvokeDynamic)
hasBootstrapMethods ? readBootstrapMethodsAttribute(currentMaxStringLength) : null;
? readBootstrapMethodsAttribute(currentMaxStringLength)
: null;
}
}


/**
/**
* Constructs a new {@link ClassReader} object.
* Constructs a new {@link ClassReader} object.
*
*
* @param inputStream an input stream of the JVMS ClassFile structure to be read. This input
* @param inputStream an input stream of the JVMS ClassFile structure to be read. This input
* stream must contain nothing more than the ClassFile structure itself. It is read from its
* stream must contain nothing more than the ClassFile structure itself. It is read from its
* current position to its end.
* current position to its end.
* @throws IOException if a problem occurs during reading.
* @throws IOException if a problem occurs during reading.
*/
*/
public ClassReader(final InputStream inputStream) throws IOException {
public ClassReader(final InputStream inputStream) throws IOException {
this(readStream(inputStream, false));
this(readStream(inputStream, false));
}
}


/**
/**
* Constructs a new {@link ClassReader} object.
* Constructs a new {@link ClassReader} object.
*
*
* @param className the fully qualified name of the class to be read. The ClassFile structure is
* @param className the fully qualified name of the class to be read. The ClassFile structure is
* retrieved with the current class loader's {@link ClassLoader#getSystemResourceAsStream}.
* retrieved with the current class loader's {@link ClassLoader#getSystemResourceAsStream}.
* @throws IOException if an exception occurs during reading.
* @throws IOException if an exception occurs during reading.
*/
*/
public ClassReader(final String className) throws IOException {
public ClassReader(final String className) throws IOException {
this(
this(
readStream(
readStream(
ClassLoader.getSystemResourceAsStream(className.replace('.', '/') + ".class"), true));
ClassLoader.getSystemResourceAsStream(className.replace('.', '/') + ".class"), true));
}
}


/**
/**
* Reads the given input stream and returns its content as a byte array.
* Reads the given input stream and returns its content as a byte array.
*
*
* @param inputStream an input stream.
* @param inputStream an input stream.
* @param close true to close the input stream after reading.
* @param close true to close the input stream after reading.
* @return the content of the given input stream.
* @return the content of the given input stream.
* @throws IOException if a problem occurs during reading.
* @throws IOException if a problem occurs during reading.
*/
*/
private static byte[] readStream(final InputStream inputStream, final boolean close)
private static byte[] readStream(final InputStream inputStream, final boolean close)
throws IOException {
throws IOException {
if (inputStream == null) {
if (inputStream == null) {
throw new IOException("Class not found");
throw new IOException("Class not found");
}
}
try {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] data = new byte[INPUT_STREAM_DATA_CHUNK_SIZE];
byte[] data = new byte[INPUT_STREAM_DATA_CHUNK_SIZE];
int bytesRead;
int bytesRead;
while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
outputStream.write(data, 0, bytesRead);
outputStream.write(data, 0, bytesRead);
}
}
outputStream.flush();
outputStream.flush();
return outputStream.toByteArray();
return outputStream.toByteArray();
} finally {
} finally {
if (close) {
if (close) {
inputStream.close();
inputStream.close();
}
}
}
}
}
}


// -----------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------
// Accessors
// Accessors
// -----------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------


/**
/**
* Returns the class's access flags (see {@link Opcodes}). This value may not reflect Deprecated
* Returns the class's access flags (see {@link Opcodes}). This value may not reflect Deprecated
* and Synthetic flags when bytecode is before 1.5 and those flags are represented by attributes.
* and Synthetic flags when bytecode is before 1.5 and those flags are represented by attributes.
*
*
* @return the class access flags.
* @return the class access flags.
* @see ClassVisitor#visit(int, int, String, String, String, String[])
* @see ClassVisitor#visit(int, int, String, String, String, String[])
*/
*/
public int getAccess() {
public int getAccess() {
return readUnsignedShort(header);
return readUnsignedShort(header);
}
}


/**
/**
* Returns the internal name of the class (see {@link Type#getInternalName()}).
* Returns the internal name of the class (see {@link Type#getInternalName()}).
*
*
* @return the internal class name.
* @return the internal class name.
* @see ClassVisitor#visit(int, int, String, String, String, String[])
* @see ClassVisitor#visit(int, int, String, String, String, String[])
*/
*/
public String getClassName() {
public String getClassName() {
// this_class is just after the access_flags field (using 2 bytes).
// this_class is just after the access_flags field (using 2 bytes).
return readClass(header + 2, new char[maxStringLength]);
return readClass(header + 2, new char[maxStringLength]);
}
}


/**
/**
* Returns the internal of name of the super class (see {@link Type#getInternalName()}). For
* Returns the internal of name of the super class (see {@link Type#getInternalName()}). For
* interfaces, the super class is {@link Object}.
* interfaces, the super class is {@link Object}.
*
*
* @return the internal name of the super class, or {@literal null} for {@link Object} class.
* @return the internal name of the super class, or {@literal null} for {@link Object} class.
* @see ClassVisitor#visit(int, int, String, String, String, String[])
* @see ClassVisitor#visit(int, int, String, String, String, String[])
*/
*/
public String getSuperName() {
public String getSuperName() {
// super_class is after the access_flags and this_class fields (2 bytes each).
// super_class is after the access_flags and this_class fields (2 bytes each).
return readClass(header + 4, new char[maxStringLength]);
return readClass(header + 4, new char[maxStringLength]);
}
}


/**
/**
* Returns the internal names of the implemented interfaces (see {@link Type#getInternalName()}).
* Returns the internal names of the implemented interfaces (see {@link Type#getInternalName()}).
*
*
* @return the internal names of the directly implemented interfaces. Inherited implemented
* @return the internal names of the directly implemented interfaces. Inherited implemented
* interfaces are not returned.
* interfaces are not returned.
* @see ClassVisitor#visit(int, int, String, String, String, String[])
* @see ClassVisitor#visit(int, int, String, String, String, String[])
*/
*/
public String[] getInterfaces() {
public String[] getInterfaces() {
// interfaces_count is after the access_flags, this_class and super_class fields (2 bytes each).
// interfaces_count is after the access_flags, this_class and super_class fields (2 bytes each).
int currentOffset = header + 6;
int currentOffset = header + 6;
int interfacesCount = readUnsignedShort(currentOffset);
int interfacesCount = readUnsignedShort(currentOffset);
String[] interfaces = new String[interfacesCount];
String[] interfaces = new String[interfacesCount];
if (interfacesCount > 0) {
if (interfacesCount > 0) {
char[] charBuffer = new char[maxStringLength];
char[] charBuffer = new char[maxStringLength];
for (int i = 0; i < interfacesCount; ++i) {
for (int i = 0; i < interfacesCount; ++i) {
currentOffset += 2;
currentOffset += 2;
interfaces[i] = readClass(currentOffset, charBuffer);
interfaces[i] = readClass(currentOffset, charBuffer);
}
}
}
}
return interfaces;
return interfaces;
}
}


// -----------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------
// Public methods
// Public methods
// -----------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------


/**
/**
* Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this
* Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this
* {@link ClassReader}.
* {@link ClassReader}.
*
*
* @param classVisitor the visitor that must visit this class.
* @param classVisitor the visitor that must visit this class.
* @param parsingOptions the options to use to parse this class. One or more of {@link
* @param parsingOptions the options to use to parse this class. One or more of {@link
* #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}.
* #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}.
*/
*/
public void accept(final ClassVisitor classVisitor, final int parsingOptions) {
public void accept(final ClassVisitor classVisitor, final int parsingOptions) {
accept(classVisitor, new Attribute[0], parsingOptions);
accept(classVisitor, new Attribute[0], parsingOptions);
}
}


/**
/**
* Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this
* Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this
* {@link ClassReader}.
* {@link ClassReader}.
*
*
* @param classVisitor the visitor that must visit this class.
* @param classVisitor the visitor that must visit this class.
* @param attributePrototypes prototypes of the attributes that must be parsed during the visit of
* @param attributePrototypes prototypes of the attributes that must be parsed during the visit of
* the class. Any attribute whose type is not equal to the type of one the prototypes will not
* the class. Any attribute whose type is not equal to the type of one the prototypes will not
* be parsed: its byte array value will be passed unchanged to the ClassWriter. <i>This may
* be parsed: its byte array value will be passed unchanged to the ClassWriter. <i>This may
* corrupt it if this value contains references to the constant pool, or has syntactic or
* corrupt it if this value contains references to the constant pool, or has syntactic or
* semantic links with a class element that has been transformed by a class adapter between
* semantic links with a class element that has been transformed by a class adapter between
* the reader and the writer</i>.
* the reader and the writer</i>.
* @param parsingOptions the options to use to parse this class. One or more of {@link
* @param parsingOptions the options to use to parse this class. One or more of {@link
* #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}.
* #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}.
*/
*/
public void accept(
public void accept(
final ClassVisitor classVisitor,
final ClassVisitor classVisitor,
final Attribute[] attributePrototypes,
final Attribute[] attributePrototypes,
final int parsingOptions) {
final int parsingOptions) {
Context context = new Context();
Context context = new Context();
context.attributePrototypes = attributePrototypes;
context.attributePrototypes = attributePrototypes;
context.parsingOptions = parsingOptions;
context.parsingOptions = parsingOptions;
context.charBuffer = new char[maxStringLength];
context.charBuffer = new char[maxStringLength];


// Read the access_flags, this_class, super_class, interface_count and interfaces fields.
// Read the access_flags, this_class, super_class, interface_count and interfaces fields.
char[] charBuffer = context.charBuffer;
char[] charBuffer = context.charBuffer;
int currentOffset = header;
int currentOffset = header;
int accessFlags = readUnsignedShort(currentOffset);
int accessFlags = readUnsignedShort(currentOffset);
String thisClass = readClass(currentOffset + 2, charBuffer);
String thisClass = readClass(currentOffset + 2, charBuffer);
String superClass = readClass(currentOffset + 4, charBuffer);
String superClass = readClass(currentOffset + 4, charBuffer);
String[] interfaces = new String[readUnsignedShort(currentOffset + 6)];
String[] interfaces = new String[readUnsignedShort(currentOffset + 6)];
currentOffset += 8;
currentOffset += 8;
for (int i = 0; i < interfaces.length; ++i) {
for (int i = 0; i < interfaces.length; ++i) {
interfaces[i] = readClass(currentOffset, charBuffer);
interfaces[i] = readClass(currentOffset, charBuffer);
currentOffset += 2;
currentOffset += 2;
}
}


// Read the class attributes (the variables are ordered as in Section 4.7 of the JVMS).
// Read the class attributes (the variables are ordered as in Section 4.7 of the JVMS).
// Attribute offsets exclude the attribute_name_index and attribute_length fields.
// Attribute offsets exclude the attribute_name_index and attribute_length fields.
// - The offset of the InnerClasses attribute, or 0.
// - The offset of the InnerClasses attribute, or 0.
int innerClassesOffset = 0;
int innerClassesOffset = 0;
// - The offset of the EnclosingMethod attribute, or 0.
// - The offset of the EnclosingMethod attribute, or 0.
int enclosingMethodOffset = 0;
int enclosingMethodOffset = 0;
// - The string corresponding to the Signature attribute, or null.
// - The string corresponding to the Signature attribute, or null.
String signature = null;
String signature = null;
// - The string corresponding to the SourceFile attribute, or null.
// - The string corresponding to the SourceFile attribute, or null.
String sourceFile = null;
String sourceFile = null;
// - The string corresponding to the SourceDebugExtension attribute, or null.
// - The string corresponding to the SourceDebugExtension attribute, or null.
String sourceDebugExtension = null;
String sourceDebugExtension = null;
// - The offset of the RuntimeVisibleAnnotations attribute, or 0.
// - The offset of the RuntimeVisibleAnnotations attribute, or 0.
int runtimeVisibleAnnotationsOffset = 0;
int runtimeVisibleAnnotationsOffset = 0;
// - The offset of the RuntimeInvisibleAnnotations attribute, or 0.
// - The offset of the RuntimeInvisibleAnnotations attribute, or 0.
int runtimeInvisibleAnnotationsOffset = 0;
int runtimeInvisibleAnnotationsOffset = 0;
// - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0.
// - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0.
int runtimeVisibleTypeAnnotationsOffset = 0;
int runtimeVisibleTypeAnnotationsOffset = 0;
// - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0.
// - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0.
int runtimeInvisibleTypeAnnotationsOffset = 0;
int runtimeInvisibleTypeAnnotationsOffset = 0;
// - The offset of the Module attribute, or 0.
// - The offset of the Module attribute, or 0.
int moduleOffset = 0;
int moduleOffset = 0;
// - The offset of the ModulePackages attribute, or 0.
// - The offset of the ModulePackages attribute, or 0.
int modulePackagesOffset = 0;
int modulePackagesOffset = 0;
// - The string corresponding to the ModuleMainClass attribute, or null.
// - The string corresponding to the ModuleMainClass attribute, or null.
String moduleMainClass = null;
String moduleMainClass = null;
// - The string corresponding to the NestHost attribute, or null.
// - The string corresponding to the NestHost attribute, or null.
String nestHostClass = null;
String nestHostClass = null;
// - The offset of the NestMembers attribute, or 0.
// - The offset of the NestMembers attribute, or 0.
int nestMembersOffset = 0;
int nestMembersOffset = 0;
// - The offset of the PermittedSubtypes attribute, or 0
int permittedSubtypesOffset = 0;
// - The offset of the Record attribute, or 0.
int recordOffset = 0;
// - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
// - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
// This list in the <i>reverse order</i> or their order in the ClassFile structure.
// This list in the <i>reverse order</i> or their order in the ClassFile structure.
Attribute attributes = null;
Attribute attributes = null;


int currentAttributeOffset = getFirstAttributeOffset();
int currentAttributeOffset = getFirstAttributeOffset();
for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) {
for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) {
// Read the attribute_info's attribute_name and attribute_length fields.
// Read the attribute_info's attribute_name and attribute_length fields.
String attributeName = readUTF8(currentAttributeOffset, charBuffer);
String attributeName = readUTF8(currentAttributeOffset, charBuffer);
int attributeLength = readInt(currentAttributeOffset + 2);
int attributeLength = readInt(currentAttributeOffset + 2);
currentAttributeOffset += 6;
currentAttributeOffset += 6;
// The tests are sorted in decreasing frequency order (based on frequencies observed on
// The tests are sorted in decreasing frequency order (based on frequencies observed on
// typical classes).
// typical classes).
if (Constants.SOURCE_FILE.equals(attributeName)) {
if (Constants.SOURCE_FILE.equals(attributeName)) {
sourceFile = readUTF8(currentAttributeOffset, charBuffer);
sourceFile = readUTF8(currentAttributeOffset, charBuffer);
} else if (Constants.INNER_CLASSES.equals(attributeName)) {
} else if (Constants.INNER_CLASSES.equals(attributeName)) {
innerClassesOffset = currentAttributeOffset;
innerClassesOffset = currentAttributeOffset;
} else if (Constants.ENCLOSING_METHOD.equals(attributeName)) {
} else if (Constants.ENCLOSING_METHOD.equals(attributeName)) {
enclosingMethodOffset = currentAttributeOffset;
enclosingMethodOffset = currentAttributeOffset;
} else if (Constants.NEST_HOST.equals(attributeName)) {
} else if (Constants.NEST_HOST.equals(attributeName)) {
nestHostClass = readClass(currentAttributeOffset, charBuffer);
nestHostClass = readClass(currentAttributeOffset, charBuffer);
} else if (Constants.NEST_MEMBERS.equals(attributeName)) {
} else if (Constants.NEST_MEMBERS.equals(attributeName)) {
nestMembersOffset = currentAttributeOffset;
nestMembersOffset = currentAttributeOffset;
} else if (Constants.PERMITTED_SUBTYPES.equals(attributeName)) {
permittedSubtypesOffset = currentAttributeOffset;
} else if (Constants.SIGNATURE.equals(attributeName)) {
} else if (Constants.SIGNATURE.equals(attributeName)) {
signature = readUTF8(currentAttributeOffset, charBuffer);
signature = readUTF8(currentAttributeOffset, charBuffer);
} else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
} else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
runtimeVisibleAnnotationsOffset = currentAttributeOffset;
runtimeVisibleAnnotationsOffset = currentAttributeOffset;
} else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
} else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
runtimeVisibleTypeAnnotationsOffset = currentAttributeOffset;
runtimeVisibleTypeAnnotationsOffset = currentAttributeOffset;
} else if (Constants.DEPRECATED.equals(attributeName)) {
} else if (Constants.DEPRECATED.equals(attributeName)) {
accessFlags |= Opcodes.ACC_DEPRECATED;
accessFlags |= Opcodes.ACC_DEPRECATED;
} else if (Constants.SYNTHETIC.equals(attributeName)) {
} else if (Constants.SYNTHETIC.equals(attributeName)) {
accessFlags |= Opcodes.ACC_SYNTHETIC;
accessFlags |= Opcodes.ACC_SYNTHETIC;
} else if (Constants.SOURCE_DEBUG_EXTENSION.equals(attributeName)) {
} else if (Constants.SOURCE_DEBUG_EXTENSION.equals(attributeName)) {
sourceDebugExtension =
sourceDebugExtension =
readUtf(currentAttributeOffset, attributeLength, new char[attributeLength]);
readUtf(currentAttributeOffset, attributeLength, new char[attributeLength]);
} else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
} else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
runtimeInvisibleAnnotationsOffset = currentAttributeOffset;
runtimeInvisibleAnnotationsOffset = currentAttributeOffset;
} else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
} else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
runtimeInvisibleTypeAnnotationsOffset = currentAttributeOffset;
runtimeInvisibleTypeAnnotationsOffset = currentAttributeOffset;
} else if (Constants.RECORD.equals(attributeName)) {
recordOffset = currentAttributeOffset;
accessFlags |= Opcodes.ACC_RECORD;
} else if (Constants.MODULE.equals(attributeName)) {
} else if (Constants.MODULE.equals(attributeName)) {
moduleOffset = currentAttributeOffset;
moduleOffset = currentAttributeOffset;
} else if (Constants.MODULE_MAIN_CLASS.equals(attributeName)) {
} else if (Constants.MODULE_MAIN_CLASS.equals(attributeName)) {
moduleMainClass = readClass(currentAttributeOffset, charBuffer);
moduleMainClass = readClass(currentAttributeOffset, charBuffer);
} else if (Constants.MODULE_PACKAGES.equals(attributeName)) {
} else if (Constants.MODULE_PACKAGES.equals(attributeName)) {
modulePackagesOffset = currentAttributeOffset;
modulePackagesOffset = currentAttributeOffset;
} else if (!Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
} else if (!Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
// The BootstrapMethods attribute is read in the constructor.
// The BootstrapMethods attribute is read in the constructor.
Attribute attribute =
Attribute attribute =
readAttribute(
readAttribute(
attributePrototypes,
attributePrototypes,
attributeName,
attributeName,
currentAttributeOffset,
currentAttributeOffset,
attributeLength,
attributeLength,
charBuffer,
charBuffer,
-1,
-1,
null);
null);
attribute.nextAttribute = attributes;
attribute.nextAttribute = attributes;
attributes = attribute;
attributes = attribute;
}
}
currentAttributeOffset += attributeLength;
currentAttributeOffs
}

// Visit the class declaration. The minor_version and major_version fields start 6 bytes before
// the first constant pool entry, which itself starts at cpInfoOffsets[1] - 1 (by definition).
classVisitor.visit(
readInt(cpInfoOffsets[1] - 7), accessFlags, thisClass, signature, superClass, interfaces);

// Visit the SourceFile and SourceDebugExtenstion attributes.
if ((parsingOptions & SKIP_DEBUG) == 0
&& (sourceFile != null || sourceDebugExtension != null)) {
classVisitor.visitSource(sourceFile, sourceDebugExtension);
}

// Visit the Module, ModulePackages and ModuleMainClass attributes.
if (moduleOffset != 0) {
readModuleAttributes(
classVisitor, context, moduleOffset, modulePackagesOffset, moduleMainClass