blob: 41b6de2dd7b64f4219984e87724149c22d492f46 [file] [log] [blame]
// ASM: a very small and fast Java bytecode manipulation framework
// Copyright (c) 2000-2011 INRIA, France Telecom
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holders nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
package org.apache.tapestry5.internal.plastic.asm;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 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
* appropriate visit methods of a given {@link ClassVisitor} for each field, method and bytecode
* instruction encountered.
*
* @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html">JVMS 4</a>
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
public class ClassReader {
/**
* A flag to skip the Code attributes. If this flag is set the Code attributes are neither parsed
* nor visited.
*/
public static final int SKIP_CODE = 1;
/**
* A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable, LocalVariableTypeTable
* and LineNumberTable attributes. If this flag is set these attributes are neither parsed nor
* visited (i.e. {@link ClassVisitor#visitSource}, {@link MethodVisitor#visitLocalVariable} and
* {@link MethodVisitor#visitLineNumber} are not called).
*/
public static final int SKIP_DEBUG = 2;
/**
* 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
* is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is used: it avoids visiting frames
* that will be ignored and recomputed from scratch.
*/
public static final int SKIP_FRAMES = 4;
/**
* 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"
* 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
* degrades performance quite a lot).
*/
public static final int EXPAND_FRAMES = 8;
/**
* 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
* 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
* 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
* 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
* goto_w in ClassWriter cannot occur.
*/
static final int EXPAND_ASM_INSNS = 256;
/** 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;
/**
* 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
* not needed by class visitors.</i>
*
* <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
* ClassFile element offsets within this byte array.
*/
// DontCheck(MemberName): can't be renamed (for backward binary compatibility).
public final byte[] b;
/**
* The offset in bytes, in {@link #b}, of each cp_info entry of the ClassFile's constant_pool
* array, <i>plus one</i>. In other words, the offset of constant pool entry i is given by
* cpInfoOffsets[i] - 1, i.e. its cp_info's tag field is given by b[cpInfoOffsets[i] - 1].
*/
private final int[] cpInfoOffsets;
/**
* The String objects corresponding to the CONSTANT_Utf8 constant pool items. This cache avoids
* multiple parsing of a given CONSTANT_Utf8 constant pool item.
*/
private final String[] constantUtf8Values;
/**
* The ConstantDynamic objects corresponding to the CONSTANT_Dynamic constant pool items. This
* cache avoids multiple parsing of a given CONSTANT_Dynamic constant pool item.
*/
private final ConstantDynamic[] constantDynamicValues;
/**
* The start offsets in {@link #b} of each element of the bootstrap_methods array (in the
* BootstrapMethods attribute).
*
* @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.23">JVMS
* 4.7.23</a>
*/
private final int[] bootstrapMethodOffsets;
/**
* A conservative estimate of the maximum length of the strings contained in the constant pool of
* the class.
*/
private final int maxStringLength;
/** The offset in bytes, in {@link #b}, of the ClassFile's access_flags field. */
public final int header;
// -----------------------------------------------------------------------------------------------
// Constructors
// -----------------------------------------------------------------------------------------------
/**
* Constructs a new {@link ClassReader} object.
*
* @param classFile the JVMS ClassFile structure to be read.
*/
public ClassReader(final byte[] classFile) {
this(classFile, 0, classFile.length);
}
/**
* Constructs a new {@link ClassReader} object.
*
* @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 classFileLength the length in bytes of the ClassFile to be read.
*/
public ClassReader(
final byte[] classFileBuffer,
final int classFileOffset,
final int classFileLength) { // NOPMD(UnusedFormalParameter) used for backward compatibility.
this(classFileBuffer, classFileOffset, /* checkClassVersion = */ true);
}
/**
* Constructs a new {@link ClassReader} object. <i>This internal constructor must not be exposed
* as a public API</i>.
*
* @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 checkClassVersion whether to check the class version or not.
*/
ClassReader(
final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) {
b = classFileBuffer;
// Check the class' major_version. This field is after the magic and minor_version fields, which
// use 4 and 2 bytes respectively.
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V12) {
throw new IllegalArgumentException(
"Unsupported class file major version " + readShort(classFileOffset + 6));
}
// 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.
int constantPoolCount = readUnsignedShort(classFileOffset + 8);
cpInfoOffsets = new int[constantPoolCount];
constantUtf8Values = new String[constantPoolCount];
// 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
// magic, minor_version, major_version and constant_pool_count fields, which use 4, 2, 2 and 2
// bytes respectively.
int currentCpInfoIndex = 1;
int currentCpInfoOffset = classFileOffset + 10;
int currentMaxStringLength = 0;
boolean hasConstantDynamic = false;
boolean hasConstantInvokeDynamic = false;
// The offset of the other entries depend on the total size of all the previous entries.
while (currentCpInfoIndex < constantPoolCount) {
cpInfoOffsets[currentCpInfoIndex++] = currentCpInfoOffset + 1;
int cpInfoSize;
switch (classFileBuffer[currentCpInfoOffset]) {
case Symbol.CONSTANT_FIELDREF_TAG:
case Symbol.CONSTANT_METHODREF_TAG:
case Symbol.CONSTANT_INTERFACE_METHODREF_TAG:
case Symbol.CONSTANT_INTEGER_TAG:
case Symbol.CONSTANT_FLOAT_TAG:
case Symbol.CONSTANT_NAME_AND_TYPE_TAG:
cpInfoSize = 5;
break;
case Symbol.CONSTANT_DYNAMIC_TAG:
cpInfoSize = 5;
hasConstantDynamic = true;
break;
case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG:
cpInfoSize = 5;
hasConstantInvokeDynamic = true;
break;
case Symbol.CONSTANT_LONG_TAG:
case Symbol.CONSTANT_DOUBLE_TAG:
cpInfoSize = 9;
currentCpInfoIndex++;
break;
case Symbol.CONSTANT_UTF8_TAG:
cpInfoSize = 3 + readUnsignedShort(currentCpInfoOffset + 1);
if (cpInfoSize > currentMaxStringLength) {
// 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
// compute than this exact length.
currentMaxStringLength = cpInfoSize;
}
break;
case Symbol.CONSTANT_METHOD_HANDLE_TAG:
cpInfoSize = 4;
break;
case Symbol.CONSTANT_CLASS_TAG:
case Symbol.CONSTANT_STRING_TAG:
case Symbol.CONSTANT_METHOD_TYPE_TAG:
case Symbol.CONSTANT_PACKAGE_TAG:
case Symbol.CONSTANT_MODULE_TAG:
cpInfoSize = 3;
break;
default:
throw new IllegalArgumentException();
}
currentCpInfoOffset += cpInfoSize;
}
maxStringLength = currentMaxStringLength;
// The Classfile's access_flags field is just after the last constant pool entry.
header = currentCpInfoOffset;
// Allocate the cache of ConstantDynamic values, if there is at least one.
constantDynamicValues = hasConstantDynamic ? new ConstantDynamic[constantPoolCount] : null;
// Read the BootstrapMethods attribute, if any (only get the offset of each method).
bootstrapMethodOffsets =
(hasConstantDynamic | hasConstantInvokeDynamic)
? readBootstrapMethodsAttribute(currentMaxStringLength)
: null;
}
/**
* Constructs a new {@link ClassReader} object.
*
* @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
* current position to its end.
* @throws IOException if a problem occurs during reading.
*/
public ClassReader(final InputStream inputStream) throws IOException {
this(readStream(inputStream, false));
}
/**
* Constructs a new {@link ClassReader} object.
*
* @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}.
* @throws IOException if an exception occurs during reading.
*/
public ClassReader(final String className) throws IOException {
this(
readStream(
ClassLoader.getSystemResourceAsStream(className.replace('.', '/') + ".class"), true));
}
/**
* Reads the given input stream and returns its content as a byte array.
*
* @param inputStream an input stream.
* @param close true to close the input stream after reading.
* @return the content of the given input stream.
* @throws IOException if a problem occurs during reading.
*/
private static byte[] readStream(final InputStream inputStream, final boolean close)
throws IOException {
if (inputStream == null) {
throw new IOException("Class not found");
}
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] data = new byte[INPUT_STREAM_DATA_CHUNK_SIZE];
int bytesRead;
while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
outputStream.write(data, 0, bytesRead);
}
outputStream.flush();
return outputStream.toByteArray();
} finally {
if (close) {
inputStream.close();
}
}
}
// -----------------------------------------------------------------------------------------------
// Accessors
// -----------------------------------------------------------------------------------------------
/**
* 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.
*
* @return the class access flags.
* @see ClassVisitor#visit(int, int, String, String, String, String[])
*/
public int getAccess() {
return readUnsignedShort(header);
}
/**
* Returns the internal name of the class (see {@link Type#getInternalName()}).
*
* @return the internal class name.
* @see ClassVisitor#visit(int, int, String, String, String, String[])
*/
public String getClassName() {
// this_class is just after the access_flags field (using 2 bytes).
return readClass(header + 2, new char[maxStringLength]);
}
/**
* Returns the internal of name of the super class (see {@link Type#getInternalName()}). For
* interfaces, the super class is {@link Object}.
*
* @return the internal name of the super class, or {@literal null} for {@link Object} class.
* @see ClassVisitor#visit(int, int, String, String, String, String[])
*/
public String getSuperName() {
// super_class is after the access_flags and this_class fields (2 bytes each).
return readClass(header + 4, new char[maxStringLength]);
}
/**
* Returns the internal names of the implemented interfaces (see {@link Type#getInternalName()}).
*
* @return the internal names of the directly implemented interfaces. Inherited implemented
* interfaces are not returned.
* @see ClassVisitor#visit(int, int, String, String, String, String[])
*/
public String[] getInterfaces() {
// interfaces_count is after the access_flags, this_class and super_class fields (2 bytes each).
int currentOffset = header + 6;
int interfacesCount = readUnsignedShort(currentOffset);
String[] interfaces = new String[interfacesCount];
if (interfacesCount > 0) {
char[] charBuffer = new char[maxStringLength];
for (int i = 0; i < interfacesCount; ++i) {
currentOffset += 2;
interfaces[i] = readClass(currentOffset, charBuffer);
}
}
return interfaces;
}
// -----------------------------------------------------------------------------------------------
// Public methods
// -----------------------------------------------------------------------------------------------
/**
* Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this
* {@link ClassReader}.
*
* @param classVisitor the visitor that must visit this class.
* @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}.
*/
public void accept(final ClassVisitor classVisitor, final int parsingOptions) {
accept(classVisitor, new Attribute[0], parsingOptions);
}
/**
* Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this
* {@link ClassReader}.
*
* @param classVisitor the visitor that must visit this class.
* @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
* 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
* semantic links with a class element that has been transformed by a class adapter between
* the reader and the writer</i>.
* @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}.
*/
public void accept(
final ClassVisitor classVisitor,
final Attribute[] attributePrototypes,
final int parsingOptions) {
Context context = new Context();
context.attributePrototypes = attributePrototypes;
context.parsingOptions = parsingOptions;
context.charBuffer = new char[maxStringLength];
// Read the access_flags, this_class, super_class, interface_count and interfaces fields.
char[] charBuffer = context.charBuffer;
int currentOffset = header;
int accessFlags = readUnsignedShort(currentOffset);
String thisClass = readClass(currentOffset + 2, charBuffer);
String superClass = readClass(currentOffset + 4, charBuffer);
String[] interfaces = new String[readUnsignedShort(currentOffset + 6)];
currentOffset += 8;
for (int i = 0; i < interfaces.length; ++i) {
interfaces[i] = readClass(currentOffset, charBuffer);
currentOffset += 2;
}
// 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.
// - The offset of the InnerClasses attribute, or 0.
int innerClassesOffset = 0;
// - The offset of the EnclosingMethod attribute, or 0.
int enclosingMethodOffset = 0;
// - The string corresponding to the Signature attribute, or null.
String signature = null;
// - The string corresponding to the SourceFile attribute, or null.
String sourceFile = null;
// - The string corresponding to the SourceDebugExtension attribute, or null.
String sourceDebugExtension = null;
// - The offset of the RuntimeVisibleAnnotations attribute, or 0.
int runtimeVisibleAnnotationsOffset = 0;
// - The offset of the RuntimeInvisibleAnnotations attribute, or 0.
int runtimeInvisibleAnnotationsOffset = 0;
// - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0.
int runtimeVisibleTypeAnnotationsOffset = 0;
// - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0.
int runtimeInvisibleTypeAnnotationsOffset = 0;
// - The offset of the Module attribute, or 0.
int moduleOffset = 0;
// - The offset of the ModulePackages attribute, or 0.
int modulePackagesOffset = 0;
// - The string corresponding to the ModuleMainClass attribute, or null.
String moduleMainClass = null;
// - The string corresponding to the NestHost attribute, or null.
String nestHostClass = null;
// - The offset of the NestMembers attribute, or 0.
int nestMembersOffset = 0;
// - 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.
Attribute attributes = null;
int currentAttributeOffset = getFirstAttributeOffset();
for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) {
// Read the attribute_info's attribute_name and attribute_length fields.
String attributeName = readUTF8(currentAttributeOffset, charBuffer);
int attributeLength = readInt(currentAttributeOffset + 2);
currentAttributeOffset += 6;
// The tests are sorted in decreasing frequency order (based on frequencies observed on
// typical classes).
if (Constants.SOURCE_FILE.equals(attributeName)) {
sourceFile = readUTF8(currentAttributeOffset, charBuffer);
} else if (Constants.INNER_CLASSES.equals(attributeName)) {
innerClassesOffset = currentAttributeOffset;
} else if (Constants.ENCLOSING_METHOD.equals(attributeName)) {
enclosingMethodOffset = currentAttributeOffset;
} else if (Constants.NEST_HOST.equals(attributeName)) {
nestHostClass = readClass(currentAttributeOffset, charBuffer);
} else if (Constants.NEST_MEMBERS.equals(attributeName)) {
nestMembersOffset = currentAttributeOffset;
} else if (Constants.SIGNATURE.equals(attributeName)) {
signature = readUTF8(currentAttributeOffset, charBuffer);
} else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
runtimeVisibleAnnotationsOffset = currentAttributeOffset;
} else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
runtimeVisibleTypeAnnotationsOffset = currentAttributeOffset;
} else if (Constants.DEPRECATED.equals(attributeName)) {
accessFlags |= Opcodes.ACC_DEPRECATED;
} else if (Constants.SYNTHETIC.equals(attributeName)) {
accessFlags |= Opcodes.ACC_SYNTHETIC;
} else if (Constants.SOURCE_DEBUG_EXTENSION.equals(attributeName)) {
sourceDebugExtension =
readUtf(currentAttributeOffset, attributeLength, new char[attributeLength]);
} else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
runtimeInvisibleAnnotationsOffset = currentAttributeOffset;
} else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
runtimeInvisibleTypeAnnotationsOffset = currentAttributeOffset;
} else if (Constants.MODULE.equals(attributeName)) {
moduleOffset = currentAttributeOffset;
} else if (Constants.MODULE_MAIN_CLASS.equals(attributeName)) {
moduleMainClass = readClass(currentAttributeOffset, charBuffer);
} else if (Constants.MODULE_PACKAGES.equals(attributeName)) {
modulePackagesOffset = currentAttributeOffset;
} else if (!Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
// The BootstrapMethods attribute is read in the constructor.
Attribute attribute =
readAttribute(
attributePrototypes,
attributeName,
currentAttributeOffset,
attributeLength,
charBuffer,
-1,
null);
attribute.nextAttribute = attributes;
attributes = attribute;
}
currentAttributeOffset += attributeLength;
}
// 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);
}
// Visit the NestHost attribute.
if (nestHostClass != null) {
classVisitor.visitNestHost(nestHostClass);
}
// Visit the EnclosingMethod attribute.
if (enclosingMethodOffset != 0) {
String className = readClass(enclosingMethodOffset, charBuffer);
int methodIndex = readUnsignedShort(enclosingMethodOffset + 2);
String name = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex], charBuffer);
String type = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex] + 2, charBuffer);
classVisitor.visitOuterClass(className, name, type);
}
// Visit the RuntimeVisibleAnnotations attribute.
if (runtimeVisibleAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset);
int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the RuntimeInvisibleAnnotations attribute.
if (runtimeInvisibleAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset);
int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the RuntimeVisibleTypeAnnotations attribute.
if (runtimeVisibleTypeAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset);
int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the target_type, target_info and target_path fields.
currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
classVisitor.visitTypeAnnotation(
context.currentTypeAnnotationTarget,
context.currentTypeAnnotationTargetPath,
annotationDescriptor,
/* visible = */ true),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the RuntimeInvisibleTypeAnnotations attribute.
if (runtimeInvisibleTypeAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset);
int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the target_type, target_info and target_path fields.
currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
classVisitor.visitTypeAnnotation(
context.currentTypeAnnotationTarget,
context.currentTypeAnnotationTargetPath,
annotationDescriptor,
/* visible = */ false),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the non standard attributes.
while (attributes != null) {
// Copy and reset the nextAttribute field so that it can also be used in ClassWriter.
Attribute nextAttribute = attributes.nextAttribute;
attributes.nextAttribute = null;
classVisitor.visitAttribute(attributes);
attributes = nextAttribute;
}
// Visit the NestedMembers attribute.
if (nestMembersOffset != 0) {
int numberOfNestMembers = readUnsignedShort(nestMembersOffset);
int currentNestMemberOffset = nestMembersOffset + 2;
while (numberOfNestMembers-- > 0) {
classVisitor.visitNestMember(readClass(currentNestMemberOffset, charBuffer));
currentNestMemberOffset += 2;
}
}
// Visit the InnerClasses attribute.
if (innerClassesOffset != 0) {
int numberOfClasses = readUnsignedShort(innerClassesOffset);
int currentClassesOffset = innerClassesOffset + 2;
while (numberOfClasses-- > 0) {
classVisitor.visitInnerClass(
readClass(currentClassesOffset, charBuffer),
readClass(currentClassesOffset + 2, charBuffer),
readUTF8(currentClassesOffset + 4, charBuffer),
readUnsignedShort(currentClassesOffset + 6));
currentClassesOffset += 8;
}
}
// Visit the fields and methods.
int fieldsCount = readUnsignedShort(currentOffset);
currentOffset += 2;
while (fieldsCount-- > 0) {
currentOffset = readField(classVisitor, context, currentOffset);
}
int methodsCount = readUnsignedShort(currentOffset);
currentOffset += 2;
while (methodsCount-- > 0) {
currentOffset = readMethod(classVisitor, context, currentOffset);
}
// Visit the end of the class.
classVisitor.visitEnd();
}
// ----------------------------------------------------------------------------------------------
// Methods to parse modules, fields and methods
// ----------------------------------------------------------------------------------------------
/**
* Reads the Module, ModulePackages and ModuleMainClass attributes and visit them.
*
* @param classVisitor the current class visitor
* @param context information about the class being parsed.
* @param moduleOffset the offset of the Module attribute (excluding the attribute_info's
* attribute_name_index and attribute_length fields).
* @param modulePackagesOffset the offset of the ModulePackages attribute (excluding the
* attribute_info's attribute_name_index and attribute_length fields), or 0.
* @param moduleMainClass the string corresponding to the ModuleMainClass attribute, or null.
*/
private void readModuleAttributes(
final ClassVisitor classVisitor,
final Context context,
final int moduleOffset,
final int modulePackagesOffset,
final String moduleMainClass) {
char[] buffer = context.charBuffer;
// Read the module_name_index, module_flags and module_version_index fields and visit them.
int currentOffset = moduleOffset;
String moduleName = readModule(currentOffset, buffer);
int moduleFlags = readUnsignedShort(currentOffset + 2);
String moduleVersion = readUTF8(currentOffset + 4, buffer);
currentOffset += 6;
ModuleVisitor moduleVisitor = classVisitor.visitModule(moduleName, moduleFlags, moduleVersion);
if (moduleVisitor == null) {
return;
}
// Visit the ModuleMainClass attribute.
if (moduleMainClass != null) {
moduleVisitor.visitMainClass(moduleMainClass);
}
// Visit the ModulePackages attribute.
if (modulePackagesOffset != 0) {
int packageCount = readUnsignedShort(modulePackagesOffset);
int currentPackageOffset = modulePackagesOffset + 2;
while (packageCount-- > 0) {
moduleVisitor.visitPackage(readPackage(currentPackageOffset, buffer));
currentPackageOffset += 2;
}
}
// Read the 'requires_count' and 'requires' fields.
int requiresCount = readUnsignedShort(currentOffset);
currentOffset += 2;
while (requiresCount-- > 0) {
// Read the requires_index, requires_flags and requires_version fields and visit them.
String requires = readModule(currentOffset, buffer);
int requiresFlags = readUnsignedShort(currentOffset + 2);
String requiresVersion = readUTF8(currentOffset + 4, buffer);
currentOffset += 6;
moduleVisitor.visitRequire(requires, requiresFlags, requiresVersion);
}
// Read the 'exports_count' and 'exports' fields.
int exportsCount = readUnsignedShort(currentOffset);
currentOffset += 2;
while (exportsCount-- > 0) {
// Read the exports_index, exports_flags, exports_to_count and exports_to_index fields
// and visit them.
String exports = readPackage(currentOffset, buffer);
int exportsFlags = readUnsignedShort(currentOffset + 2);
int exportsToCount = readUnsignedShort(currentOffset + 4);
currentOffset += 6;
String[] exportsTo = null;
if (exportsToCount != 0) {
exportsTo = new String[exportsToCount];
for (int i = 0; i < exportsToCount; ++i) {
exportsTo[i] = readModule(currentOffset, buffer);
currentOffset += 2;
}
}
moduleVisitor.visitExport(exports, exportsFlags, exportsTo);
}
// Reads the 'opens_count' and 'opens' fields.
int opensCount = readUnsignedShort(currentOffset);
currentOffset += 2;
while (opensCount-- > 0) {
// Read the opens_index, opens_flags, opens_to_count and opens_to_index fields and visit them.
String opens = readPackage(currentOffset, buffer);
int opensFlags = readUnsignedShort(currentOffset + 2);
int opensToCount = readUnsignedShort(currentOffset + 4);
currentOffset += 6;
String[] opensTo = null;
if (opensToCount != 0) {
opensTo = new String[opensToCount];
for (int i = 0; i < opensToCount; ++i) {
opensTo[i] = readModule(currentOffset, buffer);
currentOffset += 2;
}
}
moduleVisitor.visitOpen(opens, opensFlags, opensTo);
}
// Read the 'uses_count' and 'uses' fields.
int usesCount = readUnsignedShort(currentOffset);
currentOffset += 2;
while (usesCount-- > 0) {
moduleVisitor.visitUse(readClass(currentOffset, buffer));
currentOffset += 2;
}
// Read the 'provides_count' and 'provides' fields.
int providesCount = readUnsignedShort(currentOffset);
currentOffset += 2;
while (providesCount-- > 0) {
// Read the provides_index, provides_with_count and provides_with_index fields and visit them.
String provides = readClass(currentOffset, buffer);
int providesWithCount = readUnsignedShort(currentOffset + 2);
currentOffset += 4;
String[] providesWith = new String[providesWithCount];
for (int i = 0; i < providesWithCount; ++i) {
providesWith[i] = readClass(currentOffset, buffer);
currentOffset += 2;
}
moduleVisitor.visitProvide(provides, providesWith);
}
// Visit the end of the module attributes.
moduleVisitor.visitEnd();
}
/**
* Reads a JVMS field_info structure and makes the given visitor visit it.
*
* @param classVisitor the visitor that must visit the field.
* @param context information about the class being parsed.
* @param fieldInfoOffset the start offset of the field_info structure.
* @return the offset of the first byte following the field_info structure.
*/
private int readField(
final ClassVisitor classVisitor, final Context context, final int fieldInfoOffset) {
char[] charBuffer = context.charBuffer;
// Read the access_flags, name_index and descriptor_index fields.
int currentOffset = fieldInfoOffset;
int accessFlags = readUnsignedShort(currentOffset);
String name = readUTF8(currentOffset + 2, charBuffer);
String descriptor = readUTF8(currentOffset + 4, charBuffer);
currentOffset += 6;
// Read the field attributes (the variables are ordered as in Section 4.7 of the JVMS).
// Attribute offsets exclude the attribute_name_index and attribute_length fields.
// - The value corresponding to the ConstantValue attribute, or null.
Object constantValue = null;
// - The string corresponding to the Signature attribute, or null.
String signature = null;
// - The offset of the RuntimeVisibleAnnotations attribute, or 0.
int runtimeVisibleAnnotationsOffset = 0;
// - The offset of the RuntimeInvisibleAnnotations attribute, or 0.
int runtimeInvisibleAnnotationsOffset = 0;
// - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0.
int runtimeVisibleTypeAnnotationsOffset = 0;
// - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0.
int runtimeInvisibleTypeAnnotationsOffset = 0;
// - 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.
Attribute attributes = null;
int attributesCount = readUnsignedShort(currentOffset);
currentOffset += 2;
while (attributesCount-- > 0) {
// Read the attribute_info's attribute_name and attribute_length fields.
String attributeName = readUTF8(currentOffset, charBuffer);
int attributeLength = readInt(currentOffset + 2);
currentOffset += 6;
// The tests are sorted in decreasing frequency order (based on frequencies observed on
// typical classes).
if (Constants.CONSTANT_VALUE.equals(attributeName)) {
int constantvalueIndex = readUnsignedShort(currentOffset);
constantValue = constantvalueIndex == 0 ? null : readConst(constantvalueIndex, charBuffer);
} else if (Constants.SIGNATURE.equals(attributeName)) {
signature = readUTF8(currentOffset, charBuffer);
} else if (Constants.DEPRECATED.equals(attributeName)) {
accessFlags |= Opcodes.ACC_DEPRECATED;
} else if (Constants.SYNTHETIC.equals(attributeName)) {
accessFlags |= Opcodes.ACC_SYNTHETIC;
} else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
runtimeVisibleAnnotationsOffset = currentOffset;
} else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
runtimeVisibleTypeAnnotationsOffset = currentOffset;
} else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
runtimeInvisibleAnnotationsOffset = currentOffset;
} else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
runtimeInvisibleTypeAnnotationsOffset = currentOffset;
} else {
Attribute attribute =
readAttribute(
context.attributePrototypes,
attributeName,
currentOffset,
attributeLength,
charBuffer,
-1,
null);
attribute.nextAttribute = attributes;
attributes = attribute;
}
currentOffset += attributeLength;
}
// Visit the field declaration.
FieldVisitor fieldVisitor =
classVisitor.visitField(accessFlags, name, descriptor, signature, constantValue);
if (fieldVisitor == null) {
return currentOffset;
}
// Visit the RuntimeVisibleAnnotations attribute.
if (runtimeVisibleAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset);
int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
fieldVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the RuntimeInvisibleAnnotations attribute.
if (runtimeInvisibleAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset);
int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
fieldVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the RuntimeVisibleTypeAnnotations attribute.
if (runtimeVisibleTypeAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset);
int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the target_type, target_info and target_path fields.
currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
fieldVisitor.visitTypeAnnotation(
context.currentTypeAnnotationTarget,
context.currentTypeAnnotationTargetPath,
annotationDescriptor,
/* visible = */ true),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the RuntimeInvisibleTypeAnnotations attribute.
if (runtimeInvisibleTypeAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset);
int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the target_type, target_info and target_path fields.
currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
fieldVisitor.visitTypeAnnotation(
context.currentTypeAnnotationTarget,
context.currentTypeAnnotationTargetPath,
annotationDescriptor,
/* visible = */ false),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the non standard attributes.
while (attributes != null) {
// Copy and reset the nextAttribute field so that it can also be used in FieldWriter.
Attribute nextAttribute = attributes.nextAttribute;
attributes.nextAttribute = null;
fieldVisitor.visitAttribute(attributes);
attributes = nextAttribute;
}
// Visit the end of the field.
fieldVisitor.visitEnd();
return currentOffset;
}
/**
* Reads a JVMS method_info structure and makes the given visitor visit it.
*
* @param classVisitor the visitor that must visit the method.
* @param context information about the class being parsed.
* @param methodInfoOffset the start offset of the method_info structure.
* @return the offset of the first byte following the method_info structure.
*/
private int readMethod(
final ClassVisitor classVisitor, final Context context, final int methodInfoOffset) {
char[] charBuffer = context.charBuffer;
// Read the access_flags, name_index and descriptor_index fields.
int currentOffset = methodInfoOffset;
context.currentMethodAccessFlags = readUnsignedShort(currentOffset);
context.currentMethodName = readUTF8(currentOffset + 2, charBuffer);
context.currentMethodDescriptor = readUTF8(currentOffset + 4, charBuffer);
currentOffset += 6;
// Read the method attributes (the variables are ordered as in Section 4.7 of the JVMS).
// Attribute offsets exclude the attribute_name_index and attribute_length fields.
// - The offset of the Code attribute, or 0.
int codeOffset = 0;
// - The offset of the Exceptions attribute, or 0.
int exceptionsOffset = 0;
// - The strings corresponding to the Exceptions attribute, or null.
String[] exceptions = null;
// - Whether the method has a Synthetic attribute.
boolean synthetic = false;
// - The constant pool index contained in the Signature attribute, or 0.
int signatureIndex = 0;
// - The offset of the RuntimeVisibleAnnotations attribute, or 0.
int runtimeVisibleAnnotationsOffset = 0;
// - The offset of the RuntimeInvisibleAnnotations attribute, or 0.
int runtimeInvisibleAnnotationsOffset = 0;
// - The offset of the RuntimeVisibleParameterAnnotations attribute, or 0.
int runtimeVisibleParameterAnnotationsOffset = 0;
// - The offset of the RuntimeInvisibleParameterAnnotations attribute, or 0.
int runtimeInvisibleParameterAnnotationsOffset = 0;
// - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0.
int runtimeVisibleTypeAnnotationsOffset = 0;
// - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0.
int runtimeInvisibleTypeAnnotationsOffset = 0;
// - The offset of the AnnotationDefault attribute, or 0.
int annotationDefaultOffset = 0;
// - The offset of the MethodParameters attribute, or 0.
int methodParametersOffset = 0;
// - 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.
Attribute attributes = null;
int attributesCount = readUnsignedShort(currentOffset);
currentOffset += 2;
while (attributesCount-- > 0) {
// Read the attribute_info's attribute_name and attribute_length fields.
String attributeName = readUTF8(currentOffset, charBuffer);
int attributeLength = readInt(currentOffset + 2);
currentOffset += 6;
// The tests are sorted in decreasing frequency order (based on frequencies observed on
// typical classes).
if (Constants.CODE.equals(attributeName)) {
if ((context.parsingOptions & SKIP_CODE) == 0) {
codeOffset = currentOffset;
}
} else if (Constants.EXCEPTIONS.equals(attributeName)) {
exceptionsOffset = currentOffset;
exceptions = new String[readUnsignedShort(exceptionsOffset)];
int currentExceptionOffset = exceptionsOffset + 2;
for (int i = 0; i < exceptions.length; ++i) {
exceptions[i] = readClass(currentExceptionOffset, charBuffer);
currentExceptionOffset += 2;
}
} else if (Constants.SIGNATURE.equals(attributeName)) {
signatureIndex = readUnsignedShort(currentOffset);
} else if (Constants.DEPRECATED.equals(attributeName)) {
context.currentMethodAccessFlags |= Opcodes.ACC_DEPRECATED;
} else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
runtimeVisibleAnnotationsOffset = currentOffset;
} else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
runtimeVisibleTypeAnnotationsOffset = currentOffset;
} else if (Constants.ANNOTATION_DEFAULT.equals(attributeName)) {
annotationDefaultOffset = currentOffset;
} else if (Constants.SYNTHETIC.equals(attributeName)) {
synthetic = true;
context.currentMethodAccessFlags |= Opcodes.ACC_SYNTHETIC;
} else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
runtimeInvisibleAnnotationsOffset = currentOffset;
} else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
runtimeInvisibleTypeAnnotationsOffset = currentOffset;
} else if (Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS.equals(attributeName)) {
runtimeVisibleParameterAnnotationsOffset = currentOffset;
} else if (Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS.equals(attributeName)) {
runtimeInvisibleParameterAnnotationsOffset = currentOffset;
} else if (Constants.METHOD_PARAMETERS.equals(attributeName)) {
methodParametersOffset = currentOffset;
} else {
Attribute attribute =
readAttribute(
context.attributePrototypes,
attributeName,
currentOffset,
attributeLength,
charBuffer,
-1,
null);
attribute.nextAttribute = attributes;
attributes = attribute;
}
currentOffset += attributeLength;
}
// Visit the method declaration.
MethodVisitor methodVisitor =
classVisitor.visitMethod(
context.currentMethodAccessFlags,
context.currentMethodName,
context.currentMethodDescriptor,
signatureIndex == 0 ? null : readUtf(signatureIndex, charBuffer),
exceptions);
if (methodVisitor == null) {
return currentOffset;
}
// If the returned MethodVisitor is in fact a MethodWriter, it means there is no method
// adapter between the reader and the writer. In this case, it might be possible to copy
// the method attributes directly into the writer. If so, return early without visiting
// the content of these attributes.
if (methodVisitor instanceof MethodWriter) {
MethodWriter methodWriter = (MethodWriter) methodVisitor;
if (methodWriter.canCopyMethodAttributes(
this,
methodInfoOffset,
currentOffset - methodInfoOffset,
synthetic,
(context.currentMethodAccessFlags & Opcodes.ACC_DEPRECATED) != 0,
readUnsignedShort(methodInfoOffset + 4),
signatureIndex,
exceptionsOffset)) {
return currentOffset;
}
}
// Visit the MethodParameters attribute.
if (methodParametersOffset != 0) {
int parametersCount = readByte(methodParametersOffset);
int currentParameterOffset = methodParametersOffset + 1;
while (parametersCount-- > 0) {
// Read the name_index and access_flags fields and visit them.
methodVisitor.visitParameter(
readUTF8(currentParameterOffset, charBuffer),
readUnsignedShort(currentParameterOffset + 2));
currentParameterOffset += 4;
}
}
// Visit the AnnotationDefault attribute.
if (annotationDefaultOffset != 0) {
AnnotationVisitor annotationVisitor = methodVisitor.visitAnnotationDefault();
readElementValue(annotationVisitor, annotationDefaultOffset, null, charBuffer);
if (annotationVisitor != null) {
annotationVisitor.visitEnd();
}
}
// Visit the RuntimeVisibleAnnotations attribute.
if (runtimeVisibleAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset);
int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the RuntimeInvisibleAnnotations attribute.
if (runtimeInvisibleAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset);
int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the RuntimeVisibleTypeAnnotations attribute.
if (runtimeVisibleTypeAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset);
int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the target_type, target_info and target_path fields.
currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
methodVisitor.visitTypeAnnotation(
context.currentTypeAnnotationTarget,
context.currentTypeAnnotationTargetPath,
annotationDescriptor,
/* visible = */ true),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the RuntimeInvisibleTypeAnnotations attribute.
if (runtimeInvisibleTypeAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset);
int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the target_type, target_info and target_path fields.
currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
methodVisitor.visitTypeAnnotation(
context.currentTypeAnnotationTarget,
context.currentTypeAnnotationTargetPath,
annotationDescriptor,
/* visible = */ false),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the RuntimeVisibleParameterAnnotations attribute.
if (runtimeVisibleParameterAnnotationsOffset != 0) {
readParameterAnnotations(
methodVisitor, context, runtimeVisibleParameterAnnotationsOffset, /* visible = */ true);
}
// Visit the RuntimeInvisibleParameterAnnotations attribute.
if (runtimeInvisibleParameterAnnotationsOffset != 0) {
readParameterAnnotations(
methodVisitor,
context,
runtimeInvisibleParameterAnnotationsOffset,
/* visible = */ false);
}
// Visit the non standard attributes.
while (attributes != null) {
// Copy and reset the nextAttribute field so that it can also be used in MethodWriter.
Attribute nextAttribute = attributes.nextAttribute;
attributes.nextAttribute = null;
methodVisitor.visitAttribute(attributes);
attributes = nextAttribute;
}
// Visit the Code attribute.
if (codeOffset != 0) {
methodVisitor.visitCode();
readCode(methodVisitor, context, codeOffset);
}
// Visit the end of the method.
methodVisitor.visitEnd();
return currentOffset;
}
// ----------------------------------------------------------------------------------------------
// Methods to parse a Code attribute
// ----------------------------------------------------------------------------------------------
/**
* Reads a JVMS 'Code' attribute and makes the given visitor visit it.
*
* @param methodVisitor the visitor that must visit the Code attribute.
* @param context information about the class being parsed.
* @param codeOffset the start offset in {@link #b} of the Code attribute, excluding its
* attribute_name_index and attribute_length fields.
*/
private void readCode(
final MethodVisitor methodVisitor, final Context context, final int codeOffset) {
int currentOffset = codeOffset;
// Read the max_stack, max_locals and code_length fields.
final byte[] classFileBuffer = b;
final char[] charBuffer = context.charBuffer;
final int maxStack = readUnsignedShort(currentOffset);
final int maxLocals = readUnsignedShort(currentOffset + 2);
final int codeLength = readInt(currentOffset + 4);
currentOffset += 8;
// Read the bytecode 'code' array to create a label for each referenced instruction.
final int bytecodeStartOffset = currentOffset;
final int bytecodeEndOffset = currentOffset + codeLength;
final Label[] labels = context.currentMethodLabels = new Label[codeLength + 1];
while (currentOffset < bytecodeEndOffset) {
final int bytecodeOffset = currentOffset - bytecodeStartOffset;
final int opcode = classFileBuffer[currentOffset] & 0xFF;
switch (opcode) {
case Constants.NOP:
case Constants.ACONST_NULL:
case Constants.ICONST_M1:
case Constants.ICONST_0:
case Constants.ICONST_1:
case Constants.ICONST_2:
case Constants.ICONST_3:
case Constants.ICONST_4:
case Constants.ICONST_5:
case Constants.LCONST_0:
case Constants.LCONST_1:
case Constants.FCONST_0:
case Constants.FCONST_1:
case Constants.FCONST_2:
case Constants.DCONST_0:
case Constants.DCONST_1:
case Constants.IALOAD:
case Constants.LALOAD:
case Constants.FALOAD:
case Constants.DALOAD:
case Constants.AALOAD:
case Constants.BALOAD:
case Constants.CALOAD:
case Constants.SALOAD:
case Constants.IASTORE:
case Constants.LASTORE:
case Constants.FASTORE:
case Constants.DASTORE:
case Constants.AASTORE:
case Constants.BASTORE:
case Constants.CASTORE:
case Constants.SASTORE:
case Constants.POP:
case Constants.POP2:
case Constants.DUP:
case Constants.DUP_X1:
case Constants.DUP_X2:
case Constants.DUP2:
case Constants.DUP2_X1:
case Constants.DUP2_X2:
case Constants.SWAP:
case Constants.IADD:
case Constants.LADD:
case Constants.FADD:
case Constants.DADD:
case Constants.ISUB:
case Constants.LSUB:
case Constants.FSUB:
case Constants.DSUB:
case Constants.IMUL:
case Constants.LMUL:
case Constants.FMUL:
case Constants.DMUL:
case Constants.IDIV:
case Constants.LDIV:
case Constants.FDIV:
case Constants.DDIV:
case Constants.IREM:
case Constants.LREM:
case Constants.FREM:
case Constants.DREM:
case Constants.INEG:
case Constants.LNEG:
case Constants.FNEG:
case Constants.DNEG:
case Constants.ISHL:
case Constants.LSHL:
case Constants.ISHR:
case Constants.LSHR:
case Constants.IUSHR:
case Constants.LUSHR:
case Constants.IAND:
case Constants.LAND:
case Constants.IOR:
case Constants.LOR:
case Constants.IXOR:
case Constants.LXOR:
case Constants.I2L:
case Constants.I2F:
case Constants.I2D:
case Constants.L2I:
case Constants.L2F:
case Constants.L2D:
case Constants.F2I:
case Constants.F2L:
case Constants.F2D:
case Constants.D2I:
case Constants.D2L:
case Constants.D2F:
case Constants.I2B:
case Constants.I2C:
case Constants.I2S:
case Constants.LCMP:
case Constants.FCMPL:
case Constants.FCMPG:
case Constants.DCMPL:
case Constants.DCMPG:
case Constants.IRETURN:
case Constants.LRETURN:
case Constants.FRETURN:
case Constants.DRETURN:
case Constants.ARETURN:
case Constants.RETURN:
case Constants.ARRAYLENGTH:
case Constants.ATHROW:
case Constants.MONITORENTER:
case Constants.MONITOREXIT:
case Constants.ILOAD_0:
case Constants.ILOAD_1:
case Constants.ILOAD_2:
case Constants.ILOAD_3:
case Constants.LLOAD_0:
case Constants.LLOAD_1:
case Constants.LLOAD_2:
case Constants.LLOAD_3:
case Constants.FLOAD_0:
case Constants.FLOAD_1:
case Constants.FLOAD_2:
case Constants.FLOAD_3:
case Constants.DLOAD_0:
case Constants.DLOAD_1:
case Constants.DLOAD_2:
case Constants.DLOAD_3:
case Constants.ALOAD_0:
case Constants.ALOAD_1:
case Constants.ALOAD_2:
case Constants.ALOAD_3:
case Constants.ISTORE_0:
case Constants.ISTORE_1:
case Constants.ISTORE_2:
case Constants.ISTORE_3:
case Constants.LSTORE_0:
case Constants.LSTORE_1:
case Constants.LSTORE_2:
case Constants.LSTORE_3:
case Constants.FSTORE_0:
case Constants.FSTORE_1:
case Constants.FSTORE_2:
case Constants.FSTORE_3:
case Constants.DSTORE_0:
case Constants.DSTORE_1:
case Constants.DSTORE_2:
case Constants.DSTORE_3:
case Constants.ASTORE_0:
case Constants.ASTORE_1:
case Constants.ASTORE_2:
case Constants.ASTORE_3:
currentOffset += 1;
break;
case Constants.IFEQ:
case Constants.IFNE:
case Constants.IFLT:
case Constants.IFGE:
case Constants.IFGT:
case Constants.IFLE:
case Constants.IF_ICMPEQ:
case Constants.IF_ICMPNE:
case Constants.IF_ICMPLT:
case Constants.IF_ICMPGE:
case Constants.IF_ICMPGT:
case Constants.IF_ICMPLE:
case Constants.IF_ACMPEQ:
case Constants.IF_ACMPNE:
case Constants.GOTO:
case Constants.JSR:
case Constants.IFNULL:
case Constants.IFNONNULL:
createLabel(bytecodeOffset + readShort(currentOffset + 1), labels);
currentOffset += 3;
break;
case Constants.ASM_IFEQ:
case Constants.ASM_IFNE:
case Constants.ASM_IFLT:
case Constants.ASM_IFGE:
case Constants.ASM_IFGT:
case Constants.ASM_IFLE:
case Constants.ASM_IF_ICMPEQ:
case Constants.ASM_IF_ICMPNE:
case Constants.ASM_IF_ICMPLT:
case Constants.ASM_IF_ICMPGE:
case Constants.ASM_IF_ICMPGT:
case Constants.ASM_IF_ICMPLE:
case Constants.ASM_IF_ACMPEQ:
case Constants.ASM_IF_ACMPNE:
case Constants.ASM_GOTO:
case Constants.ASM_JSR:
case Constants.ASM_IFNULL:
case Constants.ASM_IFNONNULL:
createLabel(bytecodeOffset + readUnsignedShort(currentOffset + 1), labels);
currentOffset += 3;
break;
case Constants.GOTO_W:
case Constants.JSR_W:
case Constants.ASM_GOTO_W:
createLabel(bytecodeOffset + readInt(currentOffset + 1), labels);
currentOffset += 5;
break;
case Constants.WIDE:
switch (classFileBuffer[currentOffset + 1] & 0xFF) {
case Constants.ILOAD:
case Constants.FLOAD:
case Constants.ALOAD:
case Constants.LLOAD:
case Constants.DLOAD:
case Constants.ISTORE:
case Constants.FSTORE:
case Constants.ASTORE:
case Constants.LSTORE:
case Constants.DSTORE:
case Constants.RET:
currentOffset += 4;
break;
case Constants.IINC:
currentOffset += 6;
break;
default:
throw new IllegalArgumentException();
}
break;
case Constants.TABLESWITCH:
// Skip 0 to 3 padding bytes.
currentOffset += 4 - (bytecodeOffset & 3);
// Read the default label and the number of table entries.
createLabel(bytecodeOffset + readInt(currentOffset), labels);
int numTableEntries = readInt(currentOffset + 8) - readInt(currentOffset + 4) + 1;
currentOffset += 12;
// Read the table labels.
while (numTableEntries-- > 0) {
createLabel(bytecodeOffset + readInt(currentOffset), labels);
currentOffset += 4;
}
break;
case Constants.LOOKUPSWITCH:
// Skip 0 to 3 padding bytes.
currentOffset += 4 - (bytecodeOffset & 3);
// Read the default label and the number of switch cases.
createLabel(bytecodeOffset + readInt(currentOffset), labels);
int numSwitchCases = readInt(currentOffset + 4);
currentOffset += 8;
// Read the switch labels.
while (numSwitchCases-- > 0) {
createLabel(bytecodeOffset + readInt(currentOffset + 4), labels);
currentOffset += 8;
}
break;
case Constants.ILOAD:
case Constants.LLOAD:
case Constants.FLOAD:
case Constants.DLOAD:
case Constants.ALOAD:
case Constants.ISTORE:
case Constants.LSTORE:
case Constants.FSTORE:
case Constants.DSTORE:
case Constants.ASTORE:
case Constants.RET:
case Constants.BIPUSH:
case Constants.NEWARRAY:
case Constants.LDC:
currentOffset += 2;
break;
case Constants.SIPUSH:
case Constants.LDC_W:
case Constants.LDC2_W:
case Constants.GETSTATIC:
case Constants.PUTSTATIC:
case Constants.GETFIELD:
case Constants.PUTFIELD:
case Constants.INVOKEVIRTUAL:
case Constants.INVOKESPECIAL:
case Constants.INVOKESTATIC:
case Constants.NEW:
case Constants.ANEWARRAY:
case Constants.CHECKCAST:
case Constants.INSTANCEOF:
case Constants.IINC:
currentOffset += 3;
break;
case Constants.INVOKEINTERFACE:
case Constants.INVOKEDYNAMIC:
currentOffset += 5;
break;
case Constants.MULTIANEWARRAY:
currentOffset += 4;
break;
default:
throw new IllegalArgumentException();
}
}
// Read the 'exception_table_length' and 'exception_table' field to create a label for each
// referenced instruction, and to make methodVisitor visit the corresponding try catch blocks.
int exceptionTableLength = readUnsignedShort(currentOffset);
currentOffset += 2;
while (exceptionTableLength-- > 0) {
Label start = createLabel(readUnsignedShort(currentOffset), labels);
Label end = createLabel(readUnsignedShort(currentOffset + 2), labels);
Label handler = createLabel(readUnsignedShort(currentOffset + 4), labels);
String catchType = readUTF8(cpInfoOffsets[readUnsignedShort(currentOffset + 6)], charBuffer);
currentOffset += 8;
methodVisitor.visitTryCatchBlock(start, end, handler, catchType);
}
// Read the Code attributes to create a label for each referenced instruction (the variables
// are ordered as in Section 4.7 of the JVMS). Attribute offsets exclude the
// attribute_name_index and attribute_length fields.
// - The offset of the current 'stack_map_frame' in the StackMap[Table] attribute, or 0.
// Initially, this is the offset of the first 'stack_map_frame' entry. Then this offset is
// updated after each stack_map_frame is read.
int stackMapFrameOffset = 0;
// - The end offset of the StackMap[Table] attribute, or 0.
int stackMapTableEndOffset = 0;
// - Whether the stack map frames are compressed (i.e. in a StackMapTable) or not.
boolean compressedFrames = true;
// - The offset of the LocalVariableTable attribute, or 0.
int localVariableTableOffset = 0;
// - The offset of the LocalVariableTypeTable attribute, or 0.
int localVariableTypeTableOffset = 0;
// - The offset of each 'type_annotation' entry in the RuntimeVisibleTypeAnnotations
// attribute, or null.
int[] visibleTypeAnnotationOffsets = null;
// - The offset of each 'type_annotation' entry in the RuntimeInvisibleTypeAnnotations
// attribute, or null.
int[] invisibleTypeAnnotationOffsets = null;
// - 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.
Attribute attributes = null;
int attributesCount = readUnsignedShort(currentOffset);
currentOffset += 2;
while (attributesCount-- > 0) {
// Read the attribute_info's attribute_name and attribute_length fields.
String attributeName = readUTF8(currentOffset, charBuffer);
int attributeLength = readInt(currentOffset + 2);
currentOffset += 6;
if (Constants.LOCAL_VARIABLE_TABLE.equals(attributeName)) {
if ((context.parsingOptions & SKIP_DEBUG) == 0) {
localVariableTableOffset = currentOffset;
// Parse the attribute to find the corresponding (debug only) labels.
int currentLocalVariableTableOffset = currentOffset;
int localVariableTableLength = readUnsignedShort(currentLocalVariableTableOffset);
currentLocalVariableTableOffset += 2;
while (localVariableTableLength-- > 0) {
int startPc = readUnsignedShort(currentLocalVariableTableOffset);
createDebugLabel(startPc, labels);
int length = readUnsignedShort(currentLocalVariableTableOffset + 2);
createDebugLabel(startPc + length, labels);
// Skip the name_index, descriptor_index and index fields (2 bytes each).
currentLocalVariableTableOffset += 10;
}
}
} else if (Constants.LOCAL_VARIABLE_TYPE_TABLE.equals(attributeName)) {
localVariableTypeTableOffset = currentOffset;
// Here we do not extract the labels corresponding to the attribute content. We assume they
// are the same or a subset of those of the LocalVariableTable attribute.
} else if (Constants.LINE_NUMBER_TABLE.equals(attributeName)) {
if ((context.parsingOptions & SKIP_DEBUG) == 0) {
// Parse the attribute to find the corresponding (debug only) labels.
int currentLineNumberTableOffset = currentOffset;
int lineNumberTableLength = readUnsignedShort(currentLineNumberTableOffset);
currentLineNumberTableOffset += 2;
while (lineNumberTableLength-- > 0) {
int startPc = readUnsignedShort(currentLineNumberTableOffset);
int lineNumber = readUnsignedShort(currentLineNumberTableOffset + 2);
currentLineNumberTableOffset += 4;
createDebugLabel(startPc, labels);
labels[startPc].addLineNumber(lineNumber);
}
}
} else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
visibleTypeAnnotationOffsets =
readTypeAnnotations(methodVisitor, context, currentOffset, /* visible = */ true);
// Here we do not extract the labels corresponding to the attribute content. This would
// require a full parsing of the attribute, which would need to be repeated when parsing
// the bytecode instructions (see below). Instead, the content of the attribute is read one
// type annotation at a time (i.e. after a type annotation has been visited, the next type
// annotation is read), and the labels it contains are also extracted one annotation at a
// time. This assumes that type annotations are ordered by increasing bytecode offset.
} else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
invisibleTypeAnnotationOffsets =
readTypeAnnotations(methodVisitor, context, currentOffset, /* visible = */ false);
// Same comment as above for the RuntimeVisibleTypeAnnotations attribute.
} else if (Constants.STACK_MAP_TABLE.equals(attributeName)) {
if ((context.parsingOptions & SKIP_FRAMES) == 0) {
stackMapFrameOffset = currentOffset + 2;
stackMapTableEndOffset = currentOffset + attributeLength;
}
// Here we do not extract the labels corresponding to the attribute content. This would
// require a full parsing of the attribute, which would need to be repeated when parsing
// the bytecode instructions (see below). Instead, the content of the attribute is read one
// frame at a time (i.e. after a frame has been visited, the next frame is read), and the
// labels it contains are also extracted one frame at a time. Thanks to the ordering of
// frames, having only a "one frame lookahead" is not a problem, i.e. it is not possible to
// see an offset smaller than the offset of the current instruction and for which no Label
// exist. Except for UNINITIALIZED type offsets. We solve this by parsing the stack map
// table without a full decoding (see below).
} else if ("StackMap".equals(attributeName)) {
if ((context.parsingOptions & SKIP_FRAMES) == 0) {
stackMapFrameOffset = currentOffset + 2;
stackMapTableEndOffset = currentOffset + attributeLength;
compressedFrames = false;
}
// IMPORTANT! Here we assume that the frames are ordered, as in the StackMapTable attribute,
// although this is not guaranteed by the attribute format. This allows an incremental
// extraction of the labels corresponding to this attribute (see the comment above for the
// StackMapTable attribute).
} else {
Attribute attribute =
readAttribute(
context.attributePrototypes,
attributeName,
currentOffset,
attributeLength,
charBuffer,
codeOffset,
labels);
attribute.nextAttribute = attributes;
attributes = attribute;
}
currentOffset += attributeLength;
}
// Initialize the context fields related to stack map frames, and generate the first
// (implicit) stack map frame, if needed.
final boolean expandFrames = (context.parsingOptions & EXPAND_FRAMES) != 0;
if (stackMapFrameOffset != 0) {
// The bytecode offset of the first explicit frame is not offset_delta + 1 but only
// offset_delta. Setting the implicit frame offset to -1 allows us to use of the
// "offset_delta + 1" rule in all cases.
context.currentFrameOffset = -1;
context.currentFrameType = 0;
context.currentFrameLocalCount = 0;
context.currentFrameLocalCountDelta = 0;
context.currentFrameLocalTypes = new Object[maxLocals];
context.currentFrameStackCount = 0;
context.currentFrameStackTypes = new Object[maxStack];
if (expandFrames) {
computeImplicitFrame(context);
}
// Find the labels for UNINITIALIZED frame types. Instead of decoding each element of the
// stack map table, we look for 3 consecutive bytes that "look like" an UNINITIALIZED type
// (tag ITEM_Uninitialized, offset within bytecode bounds, NEW instruction at this offset).
// We may find false positives (i.e. not real UNINITIALIZED types), but this should be rare,
// and the only consequence will be the creation of an unneeded label. This is better than
// creating a label for each NEW instruction, and faster than fully decoding the whole stack
// map table.
for (int offset = stackMapFrameOffset; offset < stackMapTableEndOffset - 2; ++offset) {
if (classFileBuffer[offset] == Frame.ITEM_UNINITIALIZED) {
int potentialBytecodeOffset = readUnsignedShort(offset + 1);
if (potentialBytecodeOffset >= 0
&& potentialBytecodeOffset < codeLength
&& (classFileBuffer[bytecodeStartOffset + potentialBytecodeOffset] & 0xFF)
== Opcodes.NEW) {
createLabel(potentialBytecodeOffset, labels);
}
}
}
}
if (expandFrames && (context.parsingOptions & EXPAND_ASM_INSNS) != 0) {
// Expanding the ASM specific instructions can introduce F_INSERT frames, even if the method
// does not currently have any frame. These inserted frames must be computed by simulating the
// effect of the bytecode instructions, one by one, starting from the implicit first frame.
// For this, MethodWriter needs to know maxLocals before the first instruction is visited. To
// ensure this, we visit the implicit first frame here (passing only maxLocals - the rest is
// computed in MethodWriter).
methodVisitor.visitFrame(Opcodes.F_NEW, maxLocals, null, 0, null);
}
// Visit the bytecode instructions. First, introduce state variables for the incremental parsing
// of the type annotations.
// Index of the next runtime visible type annotation to read (in the
// visibleTypeAnnotationOffsets array).
int currentVisibleTypeAnnotationIndex = 0;
// The bytecode offset of the next runtime visible type annotation to read, or -1.
int currentVisibleTypeAnnotationBytecodeOffset =
getTypeAnnotationBytecodeOffset(visibleTypeAnnotationOffsets, 0);
// Index of the next runtime invisible type annotation to read (in the
// invisibleTypeAnnotationOffsets array).
int currentInvisibleTypeAnnotationIndex = 0;
// The bytecode offset of the next runtime invisible type annotation to read, or -1.
int currentInvisibleTypeAnnotationBytecodeOffset =
getTypeAnnotationBytecodeOffset(invisibleTypeAnnotationOffsets, 0);
// Whether a F_INSERT stack map frame must be inserted before the current instruction.
boolean insertFrame = false;
// The delta to subtract from a goto_w or jsr_w opcode to get the corresponding goto or jsr
// opcode, or 0 if goto_w and jsr_w must be left unchanged (i.e. when expanding ASM specific
// instructions).
final int wideJumpOpcodeDelta =
(context.parsingOptions & EXPAND_ASM_INSNS) == 0 ? Constants.WIDE_JUMP_OPCODE_DELTA : 0;
currentOffset = bytecodeStartOffset;
while (currentOffset < bytecodeEndOffset) {
final int currentBytecodeOffset = currentOffset - bytecodeStartOffset;
// Visit the label and the line number(s) for this bytecode offset, if any.
Label currentLabel = labels[currentBytecodeOffset];
if (currentLabel != null) {
currentLabel.accept(methodVisitor, (context.parsingOptions & SKIP_DEBUG) == 0);
}
// Visit the stack map frame for this bytecode offset, if any.
while (stackMapFrameOffset != 0
&& (context.currentFrameOffset == currentBytecodeOffset
|| context.currentFrameOffset == -1)) {
// If there is a stack map frame for this offset, make methodVisitor visit it, and read the
// next stack map frame if there is one.
if (context.currentFrameOffset != -1) {
if (!compressedFrames || expandFrames) {
methodVisitor.visitFrame(
Opcodes.F_NEW,
context.currentFrameLocalCount,
context.currentFrameLocalTypes,
context.currentFrameStackCount,
context.currentFrameStackTypes);
} else {
methodVisitor.visitFrame(
context.currentFrameType,
context.currentFrameLocalCountDelta,
context.currentFrameLocalTypes,
context.currentFrameStackCount,
context.currentFrameStackTypes);
}
// Since there is already a stack map frame for this bytecode offset, there is no need to
// insert a new one.
insertFrame = false;
}
if (stackMapFrameOffset < stackMapTableEndOffset) {
stackMapFrameOffset =
readStackMapFrame(stackMapFrameOffset, compressedFrames, expandFrames, context);
} else {
stackMapFrameOffset = 0;
}
}
// Insert a stack map frame for this bytecode offset, if requested by setting insertFrame to
// true during the previous iteration. The actual frame content is computed in MethodWriter.
if (insertFrame) {
if ((context.parsingOptions & EXPAND_FRAMES) != 0) {
methodVisitor.visitFrame(Constants.F_INSERT, 0, null, 0, null);
}
insertFrame = false;
}
// Visit the instruction at this bytecode offset.
int opcode = classFileBuffer[currentOffset] & 0xFF;
switch (opcode) {
case Constants.NOP:
case Constants.ACONST_NULL:
case Constants.ICONST_M1:
case Constants.ICONST_0:
case Constants.ICONST_1:
case Constants.ICONST_2:
case Constants.ICONST_3:
case Constants.ICONST_4:
case Constants.ICONST_5:
case Constants.LCONST_0:
case Constants.LCONST_1:
case Constants.FCONST_0:
case Constants.FCONST_1:
case Constants.FCONST_2:
case Constants.DCONST_0:
case Constants.DCONST_1:
case Constants.IALOAD:
case Constants.LALOAD:
case Constants.FALOAD:
case Constants.DALOAD:
case Constants.AALOAD:
case Constants.BALOAD:
case Constants.CALOAD:
case Constants.SALOAD:
case Constants.IASTORE:
case Constants.LASTORE:
case Constants.FASTORE:
case Constants.DASTORE:
case Constants.AASTORE:
case Constants.BASTORE:
case Constants.CASTORE:
case Constants.SASTORE:
case Constants.POP:
case Constants.POP2:
case Constants.DUP:
case Constants.DUP_X1:
case Constants.DUP_X2:
case Constants.DUP2:
case Constants.DUP2_X1:
case Constants.DUP2_X2:
case Constants.SWAP:
case Constants.IADD:
case Constants.LADD:
case Constants.FADD:
case Constants.DADD:
case Constants.ISUB:
case Constants.LSUB:
case Constants.FSUB:
case Constants.DSUB:
case Constants.IMUL:
case Constants.LMUL:
case Constants.FMUL:
case Constants.DMUL:
case Constants.IDIV:
case Constants.LDIV:
case Constants.FDIV:
case Constants.DDIV:
case Constants.IREM:
case Constants.LREM:
case Constants.FREM:
case Constants.DREM:
case Constants.INEG:
case Constants.LNEG:
case Constants.FNEG:
case Constants.DNEG:
case Constants.ISHL:
case Constants.LSHL:
case Constants.ISHR:
case Constants.LSHR:
case Constants.IUSHR:
case Constants.LUSHR:
case Constants.IAND:
case Constants.LAND:
case Constants.IOR:
case Constants.LOR:
case Constants.IXOR:
case Constants.LXOR:
case Constants.I2L:
case Constants.I2F:
case Constants.I2D:
case Constants.L2I:
case Constants.L2F:
case Constants.L2D:
case Constants.F2I:
case Constants.F2L:
case Constants.F2D:
case Constants.D2I:
case Constants.D2L:
case Constants.D2F:
case Constants.I2B:
case Constants.I2C:
case Constants.I2S:
case Constants.LCMP:
case Constants.FCMPL:
case Constants.FCMPG:
case Constants.DCMPL:
case Constants.DCMPG:
case Constants.IRETURN:
case Constants.LRETURN:
case Constants.FRETURN:
case Constants.DRETURN:
case Constants.ARETURN:
case Constants.RETURN:
case Constants.ARRAYLENGTH:
case Constants.ATHROW:
case Constants.MONITORENTER:
case Constants.MONITOREXIT:
methodVisitor.visitInsn(opcode);
currentOffset += 1;
break;
case Constants.ILOAD_0:
case Constants.ILOAD_1:
case Constants.ILOAD_2:
case Constants.ILOAD_3:
case Constants.LLOAD_0:
case Constants.LLOAD_1:
case Constants.LLOAD_2:
case Constants.LLOAD_3:
case Constants.FLOAD_0:
case Constants.FLOAD_1:
case Constants.FLOAD_2:
case Constants.FLOAD_3:
case Constants.DLOAD_0:
case Constants.DLOAD_1:
case Constants.DLOAD_2:
case Constants.DLOAD_3:
case Constants.ALOAD_0:
case Constants.ALOAD_1:
case Constants.ALOAD_2:
case Constants.ALOAD_3:
opcode -= Constants.ILOAD_0;
methodVisitor.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), opcode & 0x3);
currentOffset += 1;
break;
case Constants.ISTORE_0:
case Constants.ISTORE_1:
case Constants.ISTORE_2:
case Constants.ISTORE_3:
case Constants.LSTORE_0:
case Constants.LSTORE_1:
case Constants.LSTORE_2:
case Constants.LSTORE_3:
case Constants.FSTORE_0:
case Constants.FSTORE_1:
case Constants.FSTORE_2:
case Constants.FSTORE_3:
case Constants.DSTORE_0:
case Constants.DSTORE_1:
case Constants.DSTORE_2:
case Constants.DSTORE_3:
case Constants.ASTORE_0:
case Constants.ASTORE_1:
case Constants.ASTORE_2:
case Constants.ASTORE_3:
opcode -= Constants.ISTORE_0;
methodVisitor.visitVarInsn(Opcodes.ISTORE + (opcode >> 2), opcode & 0x3);
currentOffset += 1;
break;
case Constants.IFEQ:
case Constants.IFNE:
case Constants.IFLT:
case Constants.IFGE:
case Constants.IFGT:
case Constants.IFLE:
case Constants.IF_ICMPEQ:
case Constants.IF_ICMPNE:
case Constants.IF_ICMPLT:
case Constants.IF_ICMPGE:
case Constants.IF_ICMPGT:
case Constants.IF_ICMPLE:
case Constants.IF_ACMPEQ:
case Constants.IF_ACMPNE:
case Constants.GOTO:
case Constants.JSR:
case Constants.IFNULL:
case Constants.IFNONNULL:
methodVisitor.visitJumpInsn(
opcode, labels[currentBytecodeOffset + readShort(currentOffset + 1)]);
currentOffset += 3;
break;
case Constants.GOTO_W:
case Constants.JSR_W:
methodVisitor.visitJumpInsn(
opcode - wideJumpOpcodeDelta,
labels[currentBytecodeOffset + readInt(currentOffset + 1)]);
currentOffset += 5;
break;
case Constants.ASM_IFEQ:
case Constants.ASM_IFNE:
case Constants.ASM_IFLT:
case Constants.ASM_IFGE:
case Constants.ASM_IFGT:
case Constants.ASM_IFLE:
case Constants.ASM_IF_ICMPEQ:
case Constants.ASM_IF_ICMPNE:
case Constants.ASM_IF_ICMPLT:
case Constants.ASM_IF_ICMPGE:
case Constants.ASM_IF_ICMPGT:
case Constants.ASM_IF_ICMPLE:
case Constants.ASM_IF_ACMPEQ:
case Constants.ASM_IF_ACMPNE:
case Constants.ASM_GOTO:
case Constants.ASM_JSR:
case Constants.ASM_IFNULL:
case Constants.ASM_IFNONNULL:
{
// A forward jump with an offset > 32767. In this case we automatically replace ASM_GOTO
// with GOTO_W, ASM_JSR with JSR_W and ASM_IFxxx <l> with IFNOTxxx <L> GOTO_W <l> L:...,
// where IFNOTxxx is the "opposite" opcode of ASMS_IFxxx (e.g. IFNE for ASM_IFEQ) and
// where <L> designates the instruction just after the GOTO_W.
// First, change the ASM specific opcodes ASM_IFEQ ... ASM_JSR, ASM_IFNULL and
// ASM_IFNONNULL to IFEQ ... JSR, IFNULL and IFNONNULL.
opcode =
opcode < Constants.ASM_IFNULL
? opcode - Constants.ASM_OPCODE_DELTA
: opcode - Constants.ASM_IFNULL_OPCODE_DELTA;
Label target = labels[currentBytecodeOffset + readUnsignedShort(currentOffset + 1)];
if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) {
// Replace GOTO with GOTO_W and JSR with JSR_W.
methodVisitor.visitJumpInsn(opcode + Constants.WIDE_JUMP_OPCODE_DELTA, target);
} else {
// Compute the "opposite" of opcode. This can be done by flipping the least
// significant bit for IFNULL and IFNONNULL, and similarly for IFEQ ... IF_ACMPEQ
// (with a pre and post offset by 1).
opcode = opcode < Opcodes.GOTO ? ((opcode + 1) ^ 1) - 1 : opcode ^ 1;
Label endif = createLabel(currentBytecodeOffset + 3, labels);
methodVisitor.visitJumpInsn(opcode, endif);
methodVisitor.visitJumpInsn(Constants.GOTO_W, target);
// endif designates the instruction just after GOTO_W, and is visited as part of the
// next instruction. Since it is a jump target, we need to insert a frame here.
insertFrame = true;
}
currentOffset += 3;
break;
}
case Constants.ASM_GOTO_W:
{
// Replace ASM_GOTO_W with GOTO_W.
methodVisitor.visitJumpInsn(
Constants.GOTO_W, labels[currentBytecodeOffset + readInt(currentOffset + 1)]);
// The instruction just after is a jump target (because ASM_GOTO_W is used in patterns
// IFNOTxxx <L> ASM_GOTO_W <l> L:..., see MethodWriter), so we need to insert a frame
// here.
insertFrame = true;
currentOffset += 5;
break;
}
case Constants.WIDE:
opcode = classFileBuffer[currentOffset + 1] & 0xFF;
if (opcode == Opcodes.IINC) {
methodVisitor.visitIincInsn(
readUnsignedShort(currentOffset + 2), readShort(currentOffset + 4));
currentOffset += 6;
} else {
methodVisitor.visitVarInsn(opcode, readUnsignedShort(currentOffset + 2));
currentOffset += 4;
}
break;
case Constants.TABLESWITCH:
{
// Skip 0 to 3 padding bytes.
currentOffset += 4 - (currentBytecodeOffset & 3);
// Read the instruction.
Label defaultLabel = labels[currentBytecodeOffset + readInt(currentOffset)];
int low = readInt(currentOffset + 4);
int high = readInt(currentOffset + 8);
currentOffset += 12;
Label[] table = new Label[high - low + 1];
for (int i = 0; i < table.length; ++i) {
table[i] = labels[currentBytecodeOffset + readInt(currentOffset)];
currentOffset += 4;
}
methodVisitor.visitTableSwitchInsn(low, high, defaultLabel, table);
break;
}
case Constants.LOOKUPSWITCH:
{
// Skip 0 to 3 padding bytes.
currentOffset += 4 - (currentBytecodeOffset & 3);
// Read the instruction.
Label defaultLabel = labels[currentBytecodeOffset + readInt(currentOffset)];
int numPairs = readInt(currentOffset + 4);
currentOffset += 8;
int[] keys = new int[numPairs];
Label[] values = new Label[numPairs];
for (int i = 0; i < numPairs; ++i) {
keys[i] = readInt(currentOffset);
values[i] = labels[currentBytecodeOffset + readInt(currentOffset + 4)];
currentOffset += 8;
}
methodVisitor.visitLookupSwitchInsn(defaultLabel, keys, values);
break;
}
case Constants.ILOAD:
case Constants.LLOAD:
case Constants.FLOAD:
case Constants.DLOAD:
case Constants.ALOAD:
case Constants.ISTORE:
case Constants.LSTORE:
case Constants.FSTORE:
case Constants.DSTORE:
case Constants.ASTORE:
case Constants.RET:
methodVisitor.visitVarInsn(opcode, classFileBuffer[currentOffset + 1] & 0xFF);
currentOffset += 2;
break;
case Constants.BIPUSH:
case Constants.NEWARRAY:
methodVisitor.visitIntInsn(opcode, classFileBuffer[currentOffset + 1]);
currentOffset += 2;
break;
case Constants.SIPUSH:
methodVisitor.visitIntInsn(opcode, readShort(currentOffset + 1));
currentOffset += 3;
break;
case Constants.LDC:
methodVisitor.visitLdcInsn(
readConst(classFileBuffer[currentOffset + 1] & 0xFF, charBuffer));
currentOffset += 2;
break;
case Constants.LDC_W:
case Constants.LDC2_W:
methodVisitor.visitLdcInsn(readConst(readUnsignedShort(currentOffset + 1), charBuffer));
currentOffset += 3;
break;
case Constants.GETSTATIC:
case Constants.PUTSTATIC:
case Constants.GETFIELD:
case Constants.PUTFIELD:
case Constants.INVOKEVIRTUAL:
case Constants.INVOKESPECIAL:
case Constants.INVOKESTATIC:
case Constants.INVOKEINTERFACE:
{
int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)];
int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)];
String owner = readClass(cpInfoOffset, charBuffer);
String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer);
String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer);
if (opcode < Opcodes.INVOKEVIRTUAL) {
methodVisitor.visitFieldInsn(opcode, owner, name, descriptor);
} else {
boolean isInterface =
classFileBuffer[cpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG;
methodVisitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
if (opcode == Opcodes.INVOKEINTERFACE) {
currentOffset += 5;
} else {
currentOffset += 3;
}
break;
}
case Constants.INVOKEDYNAMIC:
{
int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)];
int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)];
String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer);
String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer);
int bootstrapMethodOffset = bootstrapMethodOffsets[readUnsignedShort(cpInfoOffset)];
Handle handle =
(Handle) readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer);
Object[] bootstrapMethodArguments =
new Object[readUnsignedShort(bootstrapMethodOffset + 2)];
bootstrapMethodOffset += 4;
for (int i = 0; i < bootstrapMethodArguments.length; i++) {
bootstrapMethodArguments[i] =
readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer);
bootstrapMethodOffset += 2;
}
methodVisitor.visitInvokeDynamicInsn(
name, descriptor, handle, bootstrapMethodArguments);
currentOffset += 5;
break;
}
case Constants.NEW:
case Constants.ANEWARRAY:
case Constants.CHECKCAST:
case Constants.INSTANCEOF:
methodVisitor.visitTypeInsn(opcode, readClass(currentOffset + 1, charBuffer));
currentOffset += 3;
break;
case Constants.IINC:
methodVisitor.visitIincInsn(
classFileBuffer[currentOffset + 1] & 0xFF, classFileBuffer[currentOffset + 2]);
currentOffset += 3;
break;
case Constants.MULTIANEWARRAY:
methodVisitor.visitMultiANewArrayInsn(
readClass(currentOffset + 1, charBuffer), classFileBuffer[currentOffset + 3] & 0xFF);
currentOffset += 4;
break;
default:
throw new AssertionError();
}
// Visit the runtime visible instruction annotations, if any.
while (visibleTypeAnnotationOffsets != null
&& currentVisibleTypeAnnotationIndex < visibleTypeAnnotationOffsets.length
&& currentVisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) {
if (currentVisibleTypeAnnotationBytecodeOffset == currentBytecodeOffset) {
// Parse the target_type, target_info and target_path fields.
int currentAnnotationOffset =
readTypeAnnotationTarget(
context, visibleTypeAnnotationOffsets[currentVisibleTypeAnnotationIndex]);
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
readElementValues(
methodVisitor.visitInsnAnnotation(
context.currentTypeAnnotationTarget,
context.currentTypeAnnotationTargetPath,
annotationDescriptor,
/* visible = */ true),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
currentVisibleTypeAnnotationBytecodeOffset =
getTypeAnnotationBytecodeOffset(
visibleTypeAnnotationOffsets, ++currentVisibleTypeAnnotationIndex);
}
// Visit the runtime invisible instruction annotations, if any.
while (invisibleTypeAnnotationOffsets != null
&& currentInvisibleTypeAnnotationIndex < invisibleTypeAnnotationOffsets.length
&& currentInvisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) {
if (currentInvisibleTypeAnnotationBytecodeOffset == currentBytecodeOffset) {
// Parse the target_type, target_info and target_path fields.
int currentAnnotationOffset =
readTypeAnnotationTarget(
context, invisibleTypeAnnotationOffsets[currentInvisibleTypeAnnotationIndex]);
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
readElementValues(
methodVisitor.visitInsnAnnotation(
context.currentTypeAnnotationTarget,
context.currentTypeAnnotationTargetPath,
annotationDescriptor,
/* visible = */ false),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
currentInvisibleTypeAnnotationBytecodeOffset =
getTypeAnnotationBytecodeOffset(
invisibleTypeAnnotationOffsets, ++currentInvisibleTypeAnnotationIndex);
}
}
if (labels[codeLength] != null) {
methodVisitor.visitLabel(labels[codeLength]);
}
// Visit LocalVariableTable and LocalVariableTypeTable attributes.
if (localVariableTableOffset != 0 && (context.parsingOptions & SKIP_DEBUG) == 0) {
// The (start_pc, index, signature_index) fields of each entry of the LocalVariableTypeTable.
int[] typeTable = null;
if (localVariableTypeTableOffset != 0) {
typeTable = new int[readUnsignedShort(localVariableTypeTableOffset) * 3];
currentOffset = localVariableTypeTableOffset + 2;
int typeTableIndex = typeTable.length;
while (typeTableIndex > 0) {
// Store the offset of 'signature_index', and the value of 'index' and 'start_pc'.
typeTable[--typeTableIndex] = currentOffset + 6;
typeTable[--typeTableIndex] = readUnsignedShort(currentOffset + 8);
typeTable[--typeTableIndex] = readUnsignedShort(currentOffset);
currentOffset += 10;
}
}
int localVariableTableLength = readUnsignedShort(localVariableTableOffset);
currentOffset = localVariableTableOffset + 2;
while (localVariableTableLength-- > 0) {
int startPc = readUnsignedShort(currentOffset);
int length = readUnsignedShort(currentOffset + 2);
String name = readUTF8(currentOffset + 4, charBuffer);
String descriptor = readUTF8(currentOffset + 6, charBuffer);
int index = readUnsignedShort(currentOffset + 8);
currentOffset += 10;
String signature = null;
if (typeTable != null) {
for (int i = 0; i < typeTable.length; i += 3) {
if (typeTable[i] == startPc && typeTable[i + 1] == index) {
signature = readUTF8(typeTable[i + 2], charBuffer);
break;
}
}
}
methodVisitor.visitLocalVariable(
name, descriptor, signature, labels[startPc], labels[startPc + length], index);
}
}
// Visit the local variable type annotations of the RuntimeVisibleTypeAnnotations attribute.
if (visibleTypeAnnotationOffsets != null) {
for (int typeAnnotationOffset : visibleTypeAnnotationOffsets) {
int targetType = readByte(typeAnnotationOffset);
if (targetType == TypeReference.LOCAL_VARIABLE
|| targetType == TypeReference.RESOURCE_VARIABLE) {
// Parse the target_type, target_info and target_path fields.
currentOffset = readTypeAnnotationTarget(context, typeAnnotationOffset);
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentOffset, charBuffer);
currentOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
readElementValues(
methodVisitor.visitLocalVariableAnnotation(
context.currentTypeAnnotationTarget,
context.currentTypeAnnotationTargetPath,
context.currentLocalVariableAnnotationRangeStarts,
context.currentLocalVariableAnnotationRangeEnds,
context.currentLocalVariableAnnotationRangeIndices,
annotationDescriptor,
/* visible = */ true),
currentOffset,
/* named = */ true,
charBuffer);
}
}
}
// Visit the local variable type annotations of the RuntimeInvisibleTypeAnnotations attribute.
if (invisibleTypeAnnotationOffsets != null) {
for (int typeAnnotationOffset : invisibleTypeAnnotationOffsets) {
int targetType = readByte(typeAnnotationOffset);
if (targetType == TypeReference.LOCAL_VARIABLE
|| targetType == TypeReference.RESOURCE_VARIABLE) {
// Parse the target_type, target_info and target_path fields.
currentOffset = readTypeAnnotationTarget(context, typeAnnotationOffset);
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentOffset, charBuffer);
currentOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
readElementValues(
methodVisitor.visitLocalVariableAnnotation(
context.currentTypeAnnotationTarget,
context.currentTypeAnnotationTargetPath,
context.currentLocalVariableAnnotationRangeStarts,
context.currentLocalVariableAnnotationRangeEnds,
context.currentLocalVariableAnnotationRangeIndices,
annotationDescriptor,
/* visible = */ false),
currentOffset,
/* named = */ true,
charBuffer);
}
}
}
// Visit the non standard attributes.
while (attributes != null) {
// Copy and reset the nextAttribute field so that it can also be used in MethodWriter.
Attribute nextAttribute = attributes.nextAttribute;
attributes.nextAttribute = null;
methodVisitor.visitAttribute(attributes);
attributes = nextAttribute;
}
// Visit the max stack and max locals values.
methodVisitor.visitMaxs(maxStack, maxLocals);
}
/**
* Returns the label corresponding to the given bytecode offset. The default implementation of
* this method creates a label for the given offset if it has not been already created.
*
* @param bytecodeOffset a bytecode offset in a method.
* @param labels the already created labels, indexed by their offset. If a label already exists
* for bytecodeOffset this method must not create a new one. Otherwise it must store the new
* label in this array.
* @return a non null Label, which must be equal to labels[bytecodeOffset].
*/
protected Label readLabel(final int bytecodeOffset, final Label[] labels) {
if (labels[bytecodeOffset] == null) {
labels[bytecodeOffset] = new Label();
}
return labels[bytecodeOffset];
}
/**
* Creates a label without the {@link Label#FLAG_DEBUG_ONLY} flag set, for the given bytecode
* offset. The label is created with a call to {@link #readLabel} and its {@link
* Label#FLAG_DEBUG_ONLY} flag is cleared.
*
* @param bytecodeOffset a bytecode offset in a method.
* @param labels the already created labels, indexed by their offset.
* @return a Label without the {@link Label#FLAG_DEBUG_ONLY} flag set.
*/
private Label createLabel(final int bytecodeOffset, final Label[] labels) {
Label label = readLabel(bytecodeOffset, labels);
label.flags &= ~Label.FLAG_DEBUG_ONLY;
return label;
}
/**
* Creates a label with the {@link Label#FLAG_DEBUG_ONLY} flag set, if there is no already
* existing label for the given bytecode offset (otherwise does nothing). The label is created
* with a call to {@link #readLabel}.
*
* @param bytecodeOffset a bytecode offset in a method.
* @param labels the already created labels, indexed by their offset.
*/
private void createDebugLabel(final int bytecodeOffset, final Label[] labels) {
if (labels[bytecodeOffset] == null) {
readLabel(bytecodeOffset, labels).flags |= Label.FLAG_DEBUG_ONLY;
}
}
// ----------------------------------------------------------------------------------------------
// Methods to parse annotations, type annotations and parameter annotations
// ----------------------------------------------------------------------------------------------
/**
* Parses a Runtime[In]VisibleTypeAnnotations attribute to find the offset of each type_annotation
* entry it contains, to find the corresponding labels, and to visit the try catch block
* annotations.
*
* @param methodVisitor the method visitor to be used to visit the try catch block annotations.
* @param context information about the class being parsed.
* @param runtimeTypeAnnotationsOffset the start offset of a Runtime[In]VisibleTypeAnnotations
* attribute, excluding the attribute_info's attribute_name_index and attribute_length fields.
* @param visible true if the attribute to parse is a RuntimeVisibleTypeAnnotations attribute,
* false it is a RuntimeInvisibleTypeAnnotations attribute.
* @return the start offset of each entry of the Runtime[In]VisibleTypeAnnotations_attribute's
* 'annotations' array field.
*/
private int[] readTypeAnnotations(
final MethodVisitor methodVisitor,
final Context context,
final int runtimeTypeAnnotationsOffset,
final boolean visible) {
char[] charBuffer = context.charBuffer;
int currentOffset = runtimeTypeAnnotationsOffset;
// Read the num_annotations field and create an array to store the type_annotation offsets.
int[] typeAnnotationsOffsets = new int[readUnsignedShort(currentOffset)];
currentOffset += 2;
// Parse the 'annotations' array field.
for (int i = 0; i < typeAnnotationsOffsets.length; ++i) {
typeAnnotationsOffsets[i] = currentOffset;
// Parse the type_annotation's target_type and the target_info fields. The size of the
// target_info field depends on the value of target_type.
int targetType = readInt(currentOffset);
switch (targetType >>> 24) {
case TypeReference.LOCAL_VARIABLE:
case TypeReference.RESOURCE_VARIABLE:
// A localvar_target has a variable size, which depends on the value of their table_length
// field. It also references bytecode offsets, for which we need labels.
int tableLength = readUnsignedShort(currentOffset + 1);
currentOffset += 3;
while (tableLength-- > 0) {
int startPc = readUnsignedShort(currentOffset);
int length = readUnsignedShort(currentOffset + 2);
// Skip the index field (2 bytes).
currentOffset += 6;
createLabel(startPc, context.currentMethodLabels);
createLabel(startPc + length, context.currentMethodLabels);
}
break;
case TypeReference.CAST:
case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT:
case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT:
case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT:
case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT:
currentOffset += 4;
break;
case TypeReference.CLASS_EXTENDS:
case TypeReference.CLASS_TYPE_PARAMETER_BOUND:
case TypeReference.METHOD_TYPE_PARAMETER_BOUND:
case TypeReference.THROWS:
case TypeReference.EXCEPTION_PARAMETER:
case TypeReference.INSTANCEOF:
case TypeReference.NEW:
case TypeReference.CONSTRUCTOR_REFERENCE:
case TypeReference.METHOD_REFERENCE:
currentOffset += 3;
break;
case TypeReference.CLASS_TYPE_PARAMETER:
case TypeReference.METHOD_TYPE_PARAMETER:
case TypeReference.METHOD_FORMAL_PARAMETER:
case TypeReference.FIELD:
case TypeReference.METHOD_RETURN:
case TypeReference.METHOD_RECEIVER:
default:
// TypeReference type which can't be used in Code attribute, or which is unknown.
throw new IllegalArgumentException();
}
// Parse the rest of the type_annotation structure, starting with the target_path structure
// (whose size depends on its path_length field).
int pathLength = readByte(currentOffset);
if ((targetType >>> 24) == TypeReference.EXCEPTION_PARAMETER) {
// Parse the target_path structure and create a corresponding TypePath.
TypePath path = pathLength == 0 ? null : new TypePath(b, currentOffset);
currentOffset += 1 + 2 * pathLength;
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentOffset, charBuffer);
currentOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentOffset =
readElementValues(
methodVisitor.visitTryCatchAnnotation(
targetType & 0xFFFFFF00, path, annotationDescriptor, visible),
currentOffset,
/* named = */ true,
charBuffer);
} else {
// We don't want to visit the other target_type annotations, so we just skip them (which
// requires some parsing because the element_value_pairs array has a variable size). First,
// skip the target_path structure:
currentOffset += 3 + 2 * pathLength;
// Then skip the num_element_value_pairs and element_value_pairs fields (by reading them
// with a null AnnotationVisitor).
currentOffset =
readElementValues(
/* annotationVisitor = */ null, currentOffset, /* named = */ true, charBuffer);
}
}
return typeAnnotationsOffsets;
}
/**
* Returns the bytecode offset corresponding to the specified JVMS 'type_annotation' structure, or
* -1 if there is no such type_annotation of if it does not have a bytecode offset.
*
* @param typeAnnotationOffsets the offset of each 'type_annotation' entry in a
* Runtime[In]VisibleTypeAnnotations attribute, or null.
* @param typeAnnotationIndex the index a 'type_annotation' entry in typeAnnotationOffsets.
* @return bytecode offset corresponding to the specified JVMS 'type_annotation' structure, or -1
* if there is no such type_annotation of if it does not have a bytecode offset.
*/
private int getTypeAnnotationBytecodeOffset(
final int[] typeAnnotationOffsets, final int typeAnnotationIndex) {
if (typeAnnotationOffsets == null
|| typeAnnotationIndex >= typeAnnotationOffsets.length
|| readByte(typeAnnotationOffsets[typeAnnotationIndex]) < TypeReference.INSTANCEOF) {
return -1;
}
return readUnsignedShort(typeAnnotationOffsets[typeAnnotationIndex] + 1);
}
/**
* Parses the header of a JVMS type_annotation structure to extract its target_type, target_info
* and target_path (the result is stored in the given context), and returns the start offset of
* the rest of the type_annotation structure.
*
* @param context information about the class being parsed. This is where the extracted
* target_type and target_path must be stored.
* @param typeAnnotationOffset the start offset of a type_annotation structure.
* @return the start offset of the rest of the type_annotation structure.
*/
private int readTypeAnnotationTarget(final Context context, final int typeAnnotationOffset) {
int currentOffset = typeAnnotationOffset;
// Parse and store the target_type structure.
int targetType = readInt(typeAnnotationOffset);
switch (targetType >>> 24) {
case TypeReference.CLASS_TYPE_PARAMETER:
case TypeReference.METHOD_TYPE_PARAMETER:
case TypeReference.METHOD_FORMAL_PARAMETER:
targetType &= 0xFFFF0000;
currentOffset += 2;
break;
case TypeReference.FIELD:
case TypeReference.METHOD_RETURN:
case TypeReference.METHOD_RECEIVER:
targetType &= 0xFF000000;
currentOffset += 1;
break;
case TypeReference.LOCAL_VARIABLE:
case TypeReference.RESOURCE_VARIABLE:
targetType &= 0xFF000000;
int tableLength = readUnsignedShort(currentOffset + 1);
currentOffset += 3;
context.currentLocalVariableAnnotationRangeStarts = new Label[tableLength];
context.currentLocalVariableAnnotationRangeEnds = new Label[tableLength];
context.currentLocalVariableAnnotationRangeIndices = new int[tableLength];
for (int i = 0; i < tableLength; ++i) {
int startPc = readUnsignedShort(currentOffset);
int length = readUnsignedShort(currentOffset + 2);
int index = readUnsignedShort(currentOffset + 4);
currentOffset += 6;
context.currentLocalVariableAnnotationRangeStarts[i] =
createLabel(startPc, context.currentMethodLabels);
context.currentLocalVariableAnnotationRangeEnds[i] =
createLabel(startPc + length, context.currentMethodLabels);
context.currentLocalVariableAnnotationRangeIndices[i] = index;
}
break;
case TypeReference.CAST:
case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT:
case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT:
case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT:
case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT:
targetType &= 0xFF0000FF;
currentOffset += 4;
break;
case TypeReference.CLASS_EXTENDS:
case TypeReference.CLASS_TYPE_PARAMETER_BOUND:
case TypeReference.METHOD_TYPE_PARAMETER_BOUND:
case TypeReference.THROWS:
case TypeReference.EXCEPTION_PARAMETER:
targetType &= 0xFFFFFF00;
currentOffset += 3;
break;
case TypeReference.INSTANCEOF:
case TypeReference.NEW:
case TypeReference.CONSTRUCTOR_REFERENCE:
case TypeReference.METHOD_REFERENCE:
targetType &= 0xFF000000;
currentOffset += 3;
break;
default:
throw new IllegalArgumentException();
}
context.currentTypeAnnotationTarget = targetType;
// Parse and store the target_path structure.
int pathLength = readByte(currentOffset);
context.currentTypeAnnotationTargetPath =
pathLength == 0 ? null : new TypePath(b, currentOffset);
// Return the start offset of the rest of the type_annotation structure.
return currentOffset + 1 + 2 * pathLength;
}
/**
* Reads a Runtime[In]VisibleParameterAnnotations attribute and makes the given visitor visit it.
*
* @param methodVisitor the visitor that must visit the parameter annotations.
* @param context information about the class being parsed.
* @param runtimeParameterAnnotationsOffset the start offset of a
* Runtime[In]VisibleParameterAnnotations attribute, excluding the attribute_info's
* attribute_name_index and attribute_length fields.
* @param visible true if the attribute to parse is a RuntimeVisibleParameterAnnotations
* attribute, false it is a RuntimeInvisibleParameterAnnotations attribute.
*/
private void readParameterAnnotations(
final MethodVisitor methodVisitor,
final Context context,
final int runtimeParameterAnnotationsOffset,
final boolean visible) {
int currentOffset = runtimeParameterAnnotationsOffset;
int numParameters = b[currentOffset++] & 0xFF;
methodVisitor.visitAnnotableParameterCount(numParameters, visible);
char[] charBuffer = context.charBuffer;
for (int i = 0; i < numParameters; ++i) {
int numAnnotations = readUnsignedShort(currentOffset);
currentOffset += 2;
while (numAnnotations-- > 0) {
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentOffset, charBuffer);
currentOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentOffset =
readElementValues(
methodVisitor.visitParameterAnnotation(i, annotationDescriptor, visible),
currentOffset,
/* named = */ true,
charBuffer);
}
}
}
/**
* Reads the element values of a JVMS 'annotation' structure and makes the given visitor visit
* them. This method can also be used to read the values of the JVMS 'array_value' field of an
* annotation's 'element_value'.
*
* @param annotationVisitor the visitor that must visit the values.
* @param annotationOffset the start offset of an 'annotation' structure (excluding its type_index
* field) or of an 'array_value' structure.
* @param named if the annotation values are named or not. This should be true to parse the values
* of a JVMS 'annotation' structure, and false to parse the JVMS 'array_value' of an
* annotation's element_value.
* @param charBuffer the buffer used to read strings in the constant pool.
* @return the end offset of the JVMS 'annotation' or 'array_value' structure.
*/
private int readElementValues(
final AnnotationVisitor annotationVisitor,
final int annotationOffset,
final boolean named,
final char[] charBuffer) {
int currentOffset = annotationOffset;
// Read the num_element_value_pairs field (or num_values field for an array_value).
int numElementValuePairs = readUnsignedShort(currentOffset);
currentOffset += 2;
if (named) {
// Parse the element_value_pairs array.
while (numElementValuePairs-- > 0) {
String elementName = readUTF8(currentOffset, charBuffer);
currentOffset =
readElementValue(annotationVisitor, currentOffset + 2, elementName, charBuffer);
}
} else {
// Parse the array_value array.
while (numElementValuePairs-- > 0) {
currentOffset =
readElementValue(annotationVisitor, currentOffset, /* named = */ null, charBuffer);
}
}
if (annotationVisitor != null) {
annotationVisitor.visitEnd();
}
return currentOffset;
}
/**
* Reads a JVMS 'element_value' structure and makes the given visitor visit it.
*
* @param annotationVisitor the visitor that must visit the element_value structure.
* @param elementValueOffset the start offset in {@link #b} of the element_value structure to be
* read.
* @param elementName the name of the element_value structure to be read, or {@literal null}.
* @param charBuffer the buffer used to read strings in the constant pool.
* @return the end offset of the JVMS 'element_value' structure.
*/
private int readElementValue(
final AnnotationVisitor annotationVisitor,
final int elementValueOffset,
final String elementName,
final char[] charBuffer) {
int currentOffset = elementValueOffset;
if (annotationVisitor == null) {
switch (b[currentOffset] & 0xFF) {
case 'e': // enum_const_value
return currentOffset + 5;
case '@': // annotation_value
return readElementValues(null, currentOffset + 3, /* named = */ true, charBuffer);
case '[': // array_value
return readElementValues(null, currentOffset + 1, /* named = */ false, charBuffer);
default:
return currentOffset + 3;
}
}
switch (b[currentOffset++] & 0xFF) {
case 'B': // const_value_index, CONSTANT_Integer
annotationVisitor.visit(
elementName, (byte) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]));
currentOffset += 2;
break;
case 'C': // const_value_index, CONSTANT_Integer
annotationVisitor.visit(
elementName, (char) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]));
currentOffset += 2;
break;
case 'D': // const_value_index, CONSTANT_Double
case 'F': // const_value_index, CONSTANT_Float
case 'I': // const_value_index, CONSTANT_Integer
case 'J': // const_value_index, CONSTANT_Long
annotationVisitor.visit(
elementName, readConst(readUnsignedShort(currentOffset), charBuffer));
currentOffset += 2;
break;
case 'S': // const_value_index, CONSTANT_Integer
annotationVisitor.visit(
elementName, (short) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]));
currentOffset += 2;
break;
case 'Z': // const_value_index, CONSTANT_Integer
annotationVisitor.visit(
elementName,
readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]) == 0
? Boolean.FALSE
: Boolean.TRUE);
currentOffset += 2;
break;
case 's': // const_value_index, CONSTANT_Utf8
annotationVisitor.visit(elementName, readUTF8(currentOffset, charBuffer));
currentOffset += 2;
break;
case 'e': // enum_const_value
annotationVisitor.visitEnum(
elementName,
readUTF8(currentOffset, charBuffer),
readUTF8(currentOffset + 2, charBuffer));
currentOffset += 4;
break;
case 'c': // class_info
annotationVisitor.visit(elementName, Type.getType(readUTF8(currentOffset, charBuffer)));
currentOffset += 2;
break;
case '@': // annotation_value
currentOffset =
readElementValues(
annotationVisitor.visitAnnotation(elementName, readUTF8(currentOffset, charBuffer)),
currentOffset + 2,
true,
charBuffer);
break;
case '[': // array_value
int numValues = readUnsignedShort(currentOffset);
currentOffset += 2;
if (numValues == 0) {
return readElementValues(
annotationVisitor.visitArray(elementName),
currentOffset - 2,
/* named = */ false,
charBuffer);
}
switch (b[currentOffset] & 0xFF) {
case 'B':
byte[] byteValues = new byte[numValues];
for (int i = 0; i < numValues; i++) {
byteValues[i] = (byte) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]);
currentOffset += 3;
}
annotationVisitor.visit(elementName, byteValues);
break;
case 'Z':
boolean[] booleanValues = new boolean[numValues];
for (int i = 0; i < numValues; i++) {
booleanValues[i] = readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]) != 0;
currentOffset += 3;
}
annotationVisitor.visit(elementName, booleanValues);
break;
case 'S':
short[] shortValues = new short[numValues];
for (int i = 0; i < numValues; i++) {
shortValues[i] = (short) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]);
currentOffset += 3;
}
annotationVisitor.visit(elementName, shortValues);
break;
case 'C':
char[] charValues = new char[numValues];
for (int i = 0; i < numValues; i++) {
charValues[i] = (char) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]);
currentOffset += 3;
}
annotationVisitor.visit(elementName, charValues);
break;
case 'I':
int[] intValues = new int[numValues];
for (int i = 0; i < numValues; i++) {
intValues[i] = readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]);
currentOffset += 3;
}
annotationVisitor.visit(elementName, intValues);
break;
case 'J':
long[] longValues = new long[numValues];
for (int i = 0; i < numValues; i++) {
longValues[i] = readLong(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]);
currentOffset += 3;
}
annotationVisitor.visit(elementName, longValues);
break;
case 'F':
float[] floatValues = new float[numValues];
for (int i = 0; i < numValues; i++) {
floatValues[i] =
Float.intBitsToFloat(
readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]));
currentOffset += 3;
}
annotationVisitor.visit(elementName, floatValues);
break;
case 'D':
double[] doubleValues = new double[numValues];
for (int i = 0; i < numValues; i++) {
doubleValues[i] =
Double.longBitsToDouble(
readLong(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]));
currentOffset += 3;
}
annotationVisitor.visit(elementName, doubleValues);
break;
default:
currentOffset =
readElementValues(
annotationVisitor.visitArray(elementName),
currentOffset - 2,
/* named = */ false,
charBuffer);
break;
}
break;
default:
throw new IllegalArgumentException();
}
return currentOffset;
}
// ----------------------------------------------------------------------------------------------
// Methods to parse stack map frames
// ----------------------------------------------------------------------------------------------
/**
* Computes the implicit frame of the method currently being parsed (as defined in the given
* {@link Context}) and stores it in the given context.
*
* @param context information about the class being parsed.
*/
private void computeImplicitFrame(final Context context) {
String methodDescriptor = context.currentMethodDescriptor;
Object[] locals = context.currentFrameLocalTypes;
int numLocal = 0;
if ((context.currentMethodAccessFlags & Opcodes.ACC_STATIC) == 0) {
if ("<init>".equals(context.currentMethodName)) {
locals[numLocal++] = Opcodes.UNINITIALIZED_THIS;
} else {
locals[numLocal++] = readClass(header + 2, context.charBuffer);
}
}
// Parse the method descriptor, one argument type descriptor at each iteration. Start by
// skipping the first method descriptor character, which is always '('.
int currentMethodDescritorOffset = 1;
while (true) {
int currentArgumentDescriptorStartOffset = currentMethodDescritorOffset;
switch (methodDescriptor.charAt(currentMethodDescritorOffset++)) {
case 'Z':
case 'C':
case 'B':
case 'S':
case 'I':
locals[numLocal++] = Opcodes.INTEGER;
break;
case 'F':
locals[numLocal++] = Opcodes.FLOAT;
break;
case 'J':
locals[numLocal++] = Opcodes.LONG;
break;
case 'D':
locals[numLocal++] = Opcodes.DOUBLE;
break;
case '[':
while (methodDescriptor.charAt(currentMethodDescritorOffset) == '[') {
++currentMethodDescritorOffset;
}
if (methodDescriptor.charAt(currentMethodDescritorOffset) == 'L') {
++currentMethodDescritorOffset;
while (methodDescriptor.charAt(currentMethodDescritorOffset) != ';') {
++currentMethodDescritorOffset;
}
}
locals[numLocal++] =
methodDescriptor.substring(
currentArgumentDescriptorStartOffset, ++currentMethodDescritorOffset);
break;
case 'L':
while (methodDescriptor.charAt(currentMethodDescritorOffset) != ';') {
++currentMethodDescritorOffset;
}
locals[numLocal++] =
methodDescriptor.substring(
currentArgumentDescriptorStartOffset + 1, currentMethodDescritorOffset++);
break;
default:
context.currentFrameLocalCount = numLocal;
return;
}
}
}
/**
* Reads a JVMS 'stack_map_frame' structure and stores the result in the given {@link Context}
* object. This method can also be used to read a full_frame structure, excluding its frame_type
* field (this is used to parse the legacy StackMap attributes).
*
* @param stackMapFrameOffset the start offset in {@link #b} of the stack_map_frame_value
* structure to be read, or the start offset of a full_frame structure (excluding its
* frame_type field).
* @param compressed true to read a 'stack_map_frame' structure, false to read a 'full_frame'
* structure without its frame_type field.
* @param expand if the stack map frame must be expanded. See {@link #EXPAND_FRAMES}.
* @param context where the parsed stack map frame must be stored.
* @return the end offset of the JVMS 'stack_map_frame' or 'full_frame' structure.
*/
private int readStackMapFrame(
final int stackMapFrameOffset,
final boolean compressed,
final boolean expand,
final Context context) {
int currentOffset = stackMapFrameOffset;
final char[] charBuffer = context.charBuffer;
final Label[] labels = context.currentMethodLabels;
int frameType;
if (compressed) {
// Read the frame_type field.
frameType = b[currentOffset++] & 0xFF;
} else {
frameType = Frame.FULL_FRAME;
context.currentFrameOffset = -1;
}
int offsetDelta;
context.currentFrameLocalCountDelta = 0;
if (frameType < Frame.SAME_LOCALS_1_STACK_ITEM_FRAME) {
offsetDelta = frameType;
context.currentFrameType = Opcodes.F_SAME;
context.currentFrameStackCount = 0;
} else if (frameType < Frame.RESERVED) {
offsetDelta = frameType - Frame.SAME_LOCALS_1_STACK_ITEM_FRAME;
currentOffset =
readVerificationTypeInfo(
currentOffset, context.currentFrameStackTypes, 0, charBuffer, labels);
context.currentFrameType = Opcodes.F_SAME1;
context.currentFrameStackCount = 1;
} else if (frameType >= Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
offsetDelta = readUnsignedShort(currentOffset);
currentOffset += 2;
if (frameType == Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
currentOffset =
readVerificationTypeInfo(
currentOffset, context.currentFrameStackTypes, 0, charBuffer, labels);
context.currentFrameType = Opcodes.F_SAME1;
context.currentFrameStackCount = 1;
} else if (frameType >= Frame.CHOP_FRAME && frameType < Frame.SAME_FRAME_EXTENDED) {
context.currentFrameType = Opcodes.F_CHOP;
context.currentFrameLocalCountDelta = Frame.SAME_FRAME_EXTENDED - frameType;
context.currentFrameLocalCount -= context.currentFrameLocalCountDelta;
context.currentFrameStackCount = 0;
} else if (frameType == Frame.SAME_FRAME_EXTENDED) {
context.currentFrameType = Opcodes.F_SAME;
context.currentFrameStackCount = 0;
} else if (frameType < Frame.FULL_FRAME) {
int local = expand ? context.currentFrameLocalCount : 0;
for (int k = frameType - Frame.SAME_FRAME_EXTENDED; k > 0; k--) {
currentOffset =
readVerificationTypeInfo(
currentOffset, context.currentFrameLocalTypes, local++, charBuffer, labels);
}
context.currentFrameType = Opcodes.F_APPEND;
context.currentFrameLocalCountDelta = frameType - Frame.SAME_FRAME_EXTENDED;
context.currentFrameLocalCount += context.currentFrameLocalCountDelta;
context.currentFrameStackCount = 0;
} else {
final int numberOfLocals = readUnsignedShort(currentOffset);
currentOffset += 2;
context.currentFrameType = Opcodes.F_FULL;
context.currentFrameLocalCountDelta = numberOfLocals;
context.currentFrameLocalCount = numberOfLocals;
for (int local = 0; local < numberOfLocals; ++local) {
currentOffset =
readVerificationTypeInfo(
currentOffset, context.currentFrameLocalTypes, local, charBuffer, labels);
}
final int numberOfStackItems = readUnsignedShort(currentOffset);
currentOffset += 2;
context.currentFrameStackCount = numberOfStackItems;
for (int stack = 0; stack < numberOfStackItems; ++stack) {
currentOffset =
readVerificationTypeInfo(
currentOffset, context.currentFrameStackTypes, stack, charBuffer, labels);
}
}
} else {
throw new IllegalArgumentException();
}
context.currentFrameOffset += offsetDelta + 1;
createLabel(context.currentFrameOffset, labels);
return currentOffset;
}
/**
* Reads a JVMS 'verification_type_info' structure and stores it at the given index in the given
* array.
*
* @param verificationTypeInfoOffset the start offset of the 'verification_type_info' structure to
* read.
* @param frame the array where the parsed type must be stored.
* @param index the index in 'frame' where the parsed type must be stored.
* @param charBuffer the buffer used to read strings in the constant pool.
* @param labels the labels of the method currently being parsed, indexed by their offset. If the
* parsed type is an ITEM_Uninitialized, a new label for the corresponding NEW instruction is
* stored in this array if it does not already exist.
* @return the end offset of the JVMS 'verification_type_info' structure.
*/
private int readVerificationTypeInfo(
final int verificationTypeInfoOffset,
final Object[] frame,
final int index,
final char[] charBuffer,
final Label[] labels) {
int currentOffset = verificationTypeInfoOffset;
int tag = b[currentOffset++] & 0xFF;
switch (tag) {
case Frame.ITEM_TOP:
frame[index] = Opcodes.TOP;
break;
case Frame.ITEM_INTEGER:
frame[index] = Opcodes.INTEGER;
break;
case Frame.ITEM_FLOAT:
frame[index] = Opcodes.FLOAT;
break;
case Frame.ITEM_DOUBLE:
frame[index] = Opcodes.DOUBLE;
break;
case Frame.ITEM_LONG:
frame[index] = Opcodes.LONG;
break;
case Frame.ITEM_NULL:
frame[index] = Opcodes.NULL;
break;
case Frame.ITEM_UNINITIALIZED_THIS:
frame[index] = Opcodes.UNINITIALIZED_THIS;
break;
case Frame.ITEM_OBJECT:
frame[index] = readClass(currentOffset, charBuffer);
currentOffset += 2;
break;
case Frame.ITEM_UNINITIALIZED:
frame[index] = createLabel(readUnsignedShort(currentOffset), labels);
currentOffset += 2;
break;
default:
throw new IllegalArgumentException();
}
return currentOffset;
}
// ----------------------------------------------------------------------------------------------
// Methods to parse attributes
// ----------------------------------------------------------------------------------------------
/**
* Returns the offset in {@link #b} of the first ClassFile's 'attributes' array field entry.
*
* @return the offset in {@link #b} of the first ClassFile's 'attributes' array field entry.
*/
final int getFirstAttributeOffset() {
// Skip the access_flags, this_class, super_class, and interfaces_count fields (using 2 bytes
// each), as well as the interfaces array field (2 bytes per interface).
int currentOffset = header + 8 + readUnsignedShort(header + 6) * 2;
// Read the fields_count field.
int fieldsCount = readUnsignedShort(currentOffset);
currentOffset += 2;
// Skip the 'fields' array field.
while (fieldsCount-- > 0) {
// Invariant: currentOffset is the offset of a field_info structure.
// Skip the access_flags, name_index and descriptor_index fields (2 bytes each), and read the
// attributes_count field.
int attributesCount = readUnsignedShort(currentOffset + 6);
currentOffset += 8;
// Skip the 'attributes' array field.
while (attributesCount-- > 0) {
// Invariant: currentOffset is the offset of an attribute_info structure.
// Read the attribute_length field (2 bytes after the start of the attribute_info) and skip
// this many bytes, plus 6 for the attribute_name_index and attribute_length fields
// (yielding the total size of the attribute_info structure).
currentOffset += 6 + readInt(currentOffset + 2);
}
}
// Skip the methods_count and 'methods' fields, using the same method as above.
int methodsCount = readUnsignedShort(currentOffset);
currentOffset += 2;
while (methodsCount-- > 0) {
int attributesCount = readUnsignedShort(currentOffset + 6);
currentOffset += 8;
while (attributesCount-- > 0) {
currentOffset += 6 + readInt(currentOffset + 2);
}
}
// Skip the ClassFile's attributes_count field.
return currentOffset + 2;
}
/**
* Reads the BootstrapMethods attribute to compute the offset of each bootstrap method.
*
* @param maxStringLength a conservative estimate of the maximum length of the strings contained
* in the constant pool of the class.
* @return the offsets of the bootstrap methods or null.
*/
private int[] readBootstrapMethodsAttribute(final int maxStringLength) {
char[] charBuffer = new char[maxStringLength];
int currentAttributeOffset = getFirstAttributeOffset();
int[] currentBootstrapMethodOffsets = null;
for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) {
// Read the attribute_info's attribute_name and attribute_length fields.
String attributeName = readUTF8(currentAttributeOffset, charBuffer);
int attributeLength = readInt(currentAttributeOffset + 2);
currentAttributeOffset += 6;
if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
// Read the num_bootstrap_methods field and create an array of this size.
currentBootstrapMethodOffsets = new int[readUnsignedShort(currentAttributeOffset)];
// Compute and store the offset of each 'bootstrap_methods' array field entry.
int currentBootstrapMethodOffset = currentAttributeOffset + 2;
for (int j = 0; j < currentBootstrapMethodOffsets.length; ++j) {
currentBootstrapMethodOffsets[j] = currentBootstrapMethodOffset;
// Skip the bootstrap_method_ref and num_bootstrap_arguments fields (2 bytes each),
// as well as the bootstrap_arguments array field (of size num_bootstrap_arguments * 2).
currentBootstrapMethodOffset +=
4 + readUnsignedShort(currentBootstrapMethodOffset + 2) * 2;
}
return currentBootstrapMethodOffsets;
}
currentAttributeOffset += attributeLength;
}
return null;
}
/**
* Reads a non standard JVMS 'attribute' structure in {@link #b}.
*
* @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
* be parsed: its byte array value will be passed unchanged to the ClassWriter.
* @param type the type of the attribute.
* @param offset the start offset of the JVMS 'attribute' structure in {@link #b}. The 6 attribute
* header bytes (attribute_name_index and attribute_length) are not taken into account here.
* @param length the length of the attribute's content (excluding the 6 attribute header bytes).
* @param charBuffer the buffer to be used to read strings in the constant pool.
* @param codeAttributeOffset the start offset of the enclosing Code attribute in {@link #b}, or
* -1 if the attribute to be read is not a code attribute. The 6 attribute header bytes
* (attribute_name_index and attribute_length) are not taken into account here.
* @param labels the labels of the method's code, or {@literal null} if the attribute to be read
* is not a code attribute.
* @return the attribute that has been read.
*/
private Attribute readAttribute(
final Attribute[] attributePrototypes,
final String type,
final int offset,
final int length,
final char[] charBuffer,
final int codeAttributeOffset,
final Label[] labels) {
for (Attribute attributePrototype : attributePrototypes) {
if (attributePrototype.type.equals(type)) {
return attributePrototype.read(
this, offset, length, charBuffer, codeAttributeOffset, labels);
}
}
return new Attribute(type).read(this, offset, length, null, -1, null);
}
// -----------------------------------------------------------------------------------------------
// Utility methods: low level parsing
// -----------------------------------------------------------------------------------------------
/**
* Returns the number of entries in the class's constant pool table.
*
* @return the number of entries in the class's constant pool table.
*/
public int getItemCount() {
return cpInfoOffsets.length;
}
/**
* Returns the start offset in {@link #b} of a JVMS 'cp_info' structure (i.e. a constant pool
* entry), plus one. <i>This method is intended for {@link Attribute} sub classes, and is normally
* not needed by class generators or adapters.</i>
*
* @param constantPoolEntryIndex the index a constant pool entry in the class's constant pool
* table.
* @return the start offset in {@link #b} of the corresponding JVMS 'cp_info' structure, plus one.
*/
public int getItem(final int constantPoolEntryIndex) {
return cpInfoOffsets[constantPoolEntryIndex];
}
/**
* Returns a conservative estimate of the maximum length of the strings contained in the class's
* constant pool table.
*
* @return a conservative estimate of the maximum length of the strings contained in the class's
* constant pool table.
*/
public int getMaxStringLength() {
return maxStringLength;
}
/**
* Reads a byte value in {@link #b}. <i>This method is intended for {@link Attribute} sub classes,
* and is normally not needed by class generators or adapters.</i>
*
* @param offset the start offset of the value to be read in {@link #b}.
* @return the read value.
*/
public int readByte(final int offset) {
return b[offset] & 0xFF;
}
/**
* Reads an unsigned short value in {@link #b}. <i>This method is intended for {@link Attribute}
* sub classes, and is normally not needed by class generators or adapters.</i>
*
* @param offset the start index of the value to be read in {@link #b}.
* @return the read value.
*/
public int readUnsignedShort(final int offset) {
byte[] classFileBuffer = b;
return ((classFileBuffer[offset] & 0xFF) << 8) | (classFileBuffer[offset + 1] & 0xFF);
}
/**
* Reads a signed short value in {@link #b}. <i>This method is intended for {@link Attribute} sub
* classes, and is normally not needed by class generators or adapters.</i>
*
* @param offset the start offset of the value to be read in {@link #b}.
* @return the read value.
*/
public short readShort(final int offset) {
byte[] classFileBuffer = b;
return (short) (((classFileBuffer[offset] & 0xFF) << 8) | (classFileBuffer[offset + 1] & 0xFF));
}
/**
* Reads a signed int value in {@link #b}. <i>This method is intended for {@link Attribute} sub
* classes, and is normally not needed by class generators or adapters.</i>
*
* @param offset the start offset of the value to be read in {@link #b}.
* @return the read value.
*/
public int readInt(final int offset) {
byte[] classFileBuffer = b;
return ((classFileBuffer[offset] & 0xFF) << 24)
| ((classFileBuffer[offset + 1] & 0xFF) << 16)
| ((classFileBuffer[offset + 2] & 0xFF) << 8)
| (classFileBuffer[offset + 3] & 0xFF);
}
/**
* Reads a signed long value in {@link #b}. <i>This method is intended for {@link Attribute} sub
* classes, and is normally not needed by class generators or adapters.</i>
*
* @param offset the start offset of the value to be read in {@link #b}.
* @return the read value.
*/
public long readLong(final int offset) {
long l1 = readInt(offset);
long l0 = readInt(offset + 4) & 0xFFFFFFFFL;
return (l1 << 32) | l0;
}
/**
* Reads a CONSTANT_Utf8 constant pool entry in {@link #b}. <i>This method is intended for {@link
* Attribute} sub classes, and is normally not needed by class generators or adapters.</i>
*
* @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
* index of a CONSTANT_Utf8 entry in the class's constant pool table.
* @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently
* large. It is not automatically resized.
* @return the String corresponding to the specified CONSTANT_Utf8 entry.
*/
// DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility).
public String readUTF8(final int offset, final char[] charBuffer) {
int constantPoolEntryIndex = readUnsignedShort(offset);
if (offset == 0 || constantPoolEntryIndex == 0) {
return null;
}
return readUtf(constantPoolEntryIndex, charBuffer);
}
/**
* Reads a CONSTANT_Utf8 constant pool entry in {@link #b}.
*
* @param constantPoolEntryIndex the index of a CONSTANT_Utf8 entry in the class's constant pool
* table.
* @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently
* large. It is not automatically resized.
* @return the String corresponding to the specified CONSTANT_Utf8 entry.
*/
final String readUtf(final int constantPoolEntryIndex, final char[] charBuffer) {
String value = constantUtf8Values[constantPoolEntryIndex];
if (value != null) {
return value;
}
int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex];
return constantUtf8Values[constantPoolEntryIndex] =
readUtf(cpInfoOffset + 2, readUnsignedShort(cpInfoOffset), charBuffer);
}
/**
* Reads an UTF8 string in {@link #b}.
*
* @param utfOffset the start offset of the UTF8 string to be read.
* @param utfLength the length of the UTF8 string to be read.
* @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently
* large. It is not automatically resized.
* @return the String corresponding to the specified UTF8 string.
*/
private String readUtf(final int utfOffset, final int utfLength, final char[] charBuffer) {
int currentOffset = utfOffset;
int endOffset = currentOffset + utfLength;
int strLength = 0;
byte[] classFileBuffer = b;
while (currentOffset < endOffset) {
int currentByte = classFileBuffer[currentOffset++];
if ((currentByte & 0x80) == 0) {
charBuffer[strLength++] = (char) (currentByte & 0x7F);
} else if ((currentByte & 0xE0) == 0xC0) {
charBuffer[strLength++] =
(char) (((currentByte & 0x1F) << 6) + (classFileBuffer[currentOffset++] & 0x3F));
} else {
charBuffer[strLength++] =
(char)
(((currentByte & 0xF) << 12)
+ ((classFileBuffer[currentOffset++] & 0x3F) << 6)
+ (classFileBuffer[currentOffset++] & 0x3F));
}
}
return new String(charBuffer, 0, strLength);
}
/**
* Reads a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, CONSTANT_Module or
* CONSTANT_Package constant pool entry in {@link #b}. <i>This method is intended for {@link
* Attribute} sub classes, and is normally not needed by class generators or adapters.</i>
*
* @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
* index of a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, CONSTANT_Module or
* CONSTANT_Package entry in class's constant pool table.
* @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently
* large. It is not automatically resized.
* @return the String corresponding to the specified constant pool entry.
*/
private String readStringish(final int offset, final char[] charBuffer) {
// Get the start offset of the cp_info structure (plus one), and read the CONSTANT_Utf8 entry
// designated by the first two bytes of this cp_info.
return readUTF8(cpInfoOffsets[readUnsignedShort(offset)], charBuffer);
}
/**
* Reads a CONSTANT_Class constant pool entry in {@link #b}. <i>This method is intended for {@link
* Attribute} sub classes, and is normally not needed by class generators or adapters.</i>
*
* @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
* index of a CONSTANT_Class entry in class's constant pool table.
* @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently
* large. It is not automatically resized.
* @return the String corresponding to the specified CONSTANT_Class entry.
*/
public String readClass(final int offset, final char[] charBuffer) {
return readStringish(offset, charBuffer);
}
/**
* Reads a CONSTANT_Module constant pool entry in {@link #b}. <i>This method is intended for
* {@link Attribute} sub classes, and is normally not needed by class generators or adapters.</i>
*
* @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
* index of a CONSTANT_Module entry in class's constant pool table.
* @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently
* large. It is not automatically resized.
* @return the String corresponding to the specified CONSTANT_Module entry.
*/
public String readModule(final int offset, final char[] charBuffer) {
return readStringish(offset, charBuffer);
}
/**
* Reads a CONSTANT_Package constant pool entry in {@link #b}. <i>This method is intended for
* {@link Attribute} sub classes, and is normally not needed by class generators or adapters.</i>
*
* @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
* index of a CONSTANT_Package entry in class's constant pool table.
* @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently
* large. It is not automatically resized.
* @return the String corresponding to the specified CONSTANT_Package entry.
*/
public String readPackage(final int offset, final char[] charBuffer) {
return readStringish(offset, charBuffer);
}
/**
* Reads a CONSTANT_Dynamic constant pool entry in {@link #b}.
*
* @param constantPoolEntryIndex the index of a CONSTANT_Dynamic entry in the class's constant
* pool table.
* @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently
* large. It is not automatically resized.
* @return the ConstantDynamic corresponding to the specified CONSTANT_Dynamic entry.
*/
private ConstantDynamic readConstantDynamic(
final int constantPoolEntryIndex, final char[] charBuffer) {
ConstantDynamic constantDynamic = constantDynamicValues[constantPoolEntryIndex];
if (constantDynamic != null) {
return constantDynamic;
}
int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex];
int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)];
String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer);
String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer);
int bootstrapMethodOffset = bootstrapMethodOffsets[readUnsignedShort(cpInfoOffset)];
Handle handle = (Handle) readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer);
Object[] bootstrapMethodArguments = new Object[readUnsignedShort(bootstrapMethodOffset + 2)];
bootstrapMethodOffset += 4;
for (int i = 0; i < bootstrapMethodArguments.length; i++) {
bootstrapMethodArguments[i] = readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer);
bootstrapMethodOffset += 2;
}
return constantDynamicValues[constantPoolEntryIndex] =
new ConstantDynamic(name, descriptor, handle, bootstrapMethodArguments);
}
/**
* Reads a numeric or string constant pool entry in {@link #b}. <i>This method is intended for
* {@link Attribute} sub classes, and is normally not needed by class generators or adapters.</i>
*
* @param constantPoolEntryIndex the index of a CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long,
* CONSTANT_Double, CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType,
* CONSTANT_MethodHandle or CONSTANT_Dynamic entry in the class's constant pool.
* @param charBuffer the buffer to be used to read strings. This buffer must be sufficiently
* large. It is not automatically resized.
* @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double}, {@link String},
* {@link Type}, {@link Handle} or {@link ConstantDynamic} corresponding to the specified
* constant pool entry.
*/
public Object readConst(final int constantPoolEntryIndex, final char[] charBuffer) {
int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex];
switch (b[cpInfoOffset - 1]) {
case Symbol.CONSTANT_INTEGER_TAG:
return readInt(cpInfoOffset);
case Symbol.CONSTANT_FLOAT_TAG:
return Float.intBitsToFloat(readInt(cpInfoOffset));
case Symbol.CONSTANT_LONG_TAG:
return readLong(cpInfoOffset);
case Symbol.CONSTANT_DOUBLE_TAG:
return Double.longBitsToDouble(readLong(cpInfoOffset));
case Symbol.CONSTANT_CLASS_TAG:
return Type.getObjectType(readUTF8(cpInfoOffset, charBuffer));
case Symbol.CONSTANT_STRING_TAG:
return readUTF8(cpInfoOffset, charBuffer);
case Symbol.CONSTANT_METHOD_TYPE_TAG:
return Type.getMethodType(readUTF8(cpInfoOffset, charBuffer));
case Symbol.CONSTANT_METHOD_HANDLE_TAG:
int referenceKind = readByte(cpInfoOffset);
int referenceCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 1)];
int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(referenceCpInfoOffset + 2)];
String owner = readClass(referenceCpInfoOffset, charBuffer);
String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer);
String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer);
boolean isInterface =
b[referenceCpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG;
return new Handle(referenceKind, owner, name, descriptor, isInterface);
case Symbol.CONSTANT_DYNAMIC_TAG:
return readConstantDynamic(constantPoolEntryIndex, charBuffer);
default:
throw new IllegalArgumentException();
}
}
}