2424 lines
98 KiB
Java
2424 lines
98 KiB
Java
/***
|
|
* 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.objectweb.asm;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
|
|
/**
|
|
* A Java class parser to make a {@link ClassVisitor} visit an existing class.
|
|
* This class parses a byte array conforming to the Java class file format and
|
|
* calls the appropriate visit methods of a given class visitor for each field,
|
|
* method and bytecode instruction encountered.
|
|
*
|
|
* @author Eric Bruneton
|
|
* @author Eugene Kuleshov
|
|
*/
|
|
public class ClassReader {
|
|
|
|
/**
|
|
* True to enable signatures support.
|
|
*/
|
|
static final boolean SIGNATURES = true;
|
|
|
|
/**
|
|
* True to enable annotations support.
|
|
*/
|
|
static final boolean ANNOTATIONS = true;
|
|
|
|
/**
|
|
* True to enable stack map frames support.
|
|
*/
|
|
static final boolean FRAMES = true;
|
|
|
|
/**
|
|
* True to enable bytecode writing support.
|
|
*/
|
|
static final boolean WRITER = true;
|
|
|
|
/**
|
|
* True to enable JSR_W and GOTO_W support.
|
|
*/
|
|
static final boolean RESIZE = true;
|
|
|
|
/**
|
|
* Flag to skip method code. If this class is set <code>CODE</code>
|
|
* attribute won't be visited. This can be used, for example, to retrieve
|
|
* annotations for methods and method parameters.
|
|
*/
|
|
public static final int SKIP_CODE = 1;
|
|
|
|
/**
|
|
* Flag to skip the debug information in the class. If this flag is set the
|
|
* debug information of the class is not visited, i.e. the
|
|
* {@link MethodVisitor#visitLocalVariable visitLocalVariable} and
|
|
* {@link MethodVisitor#visitLineNumber visitLineNumber} methods will not be
|
|
* called.
|
|
*/
|
|
public static final int SKIP_DEBUG = 2;
|
|
|
|
/**
|
|
* Flag to skip the stack map frames in the class. If this flag is set the
|
|
* stack map frames of the class is not visited, i.e. the
|
|
* {@link MethodVisitor#visitFrame visitFrame} method will not be 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 in the class writer.
|
|
*/
|
|
public static final int SKIP_FRAMES = 4;
|
|
|
|
/**
|
|
* 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/recompression step in ClassReader and
|
|
* ClassWriter which degrades performances quite a lot).
|
|
*/
|
|
public static final int EXPAND_FRAMES = 8;
|
|
|
|
/**
|
|
* The class 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 generators or adapters.</i>
|
|
*/
|
|
public final byte[] b;
|
|
|
|
/**
|
|
* The start index of each constant pool item in {@link #b b}, plus one. The
|
|
* one byte offset skips the constant pool item tag that indicates its type.
|
|
*/
|
|
private final int[] items;
|
|
|
|
/**
|
|
* The String objects corresponding to the CONSTANT_Utf8 items. This cache
|
|
* avoids multiple parsing of a given CONSTANT_Utf8 constant pool item,
|
|
* which GREATLY improves performances (by a factor 2 to 3). This caching
|
|
* strategy could be extended to all constant pool items, but its benefit
|
|
* would not be so great for these items (because they are much less
|
|
* expensive to parse than CONSTANT_Utf8 items).
|
|
*/
|
|
private final String[] strings;
|
|
|
|
/**
|
|
* Maximum length of the strings contained in the constant pool of the
|
|
* class.
|
|
*/
|
|
private final int maxStringLength;
|
|
|
|
/**
|
|
* Start index of the class header information (access, name...) in
|
|
* {@link #b b}.
|
|
*/
|
|
public final int header;
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Constructors
|
|
// ------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Constructs a new {@link ClassReader} object.
|
|
*
|
|
* @param b the bytecode of the class to be read.
|
|
*/
|
|
public ClassReader(final byte[] b) {
|
|
this(b, 0, b.length);
|
|
}
|
|
|
|
/**
|
|
* Constructs a new {@link ClassReader} object.
|
|
*
|
|
* @param b the bytecode of the class to be read.
|
|
* @param off the start offset of the class data.
|
|
* @param len the length of the class data.
|
|
*/
|
|
public ClassReader(final byte[] b, final int off, final int len) {
|
|
this.b = b;
|
|
// checks the class version
|
|
if (readShort(off + 6) > Opcodes.V1_8) {
|
|
throw new IllegalArgumentException();
|
|
}
|
|
// parses the constant pool
|
|
items = new int[readUnsignedShort(off + 8)];
|
|
int n = items.length;
|
|
strings = new String[n];
|
|
int max = 0;
|
|
int index = off + 10;
|
|
for (int i = 1; i < n; ++i) {
|
|
items[i] = index + 1;
|
|
int size;
|
|
switch (b[index]) {
|
|
case ClassWriter.FIELD:
|
|
case ClassWriter.METH:
|
|
case ClassWriter.IMETH:
|
|
case ClassWriter.INT:
|
|
case ClassWriter.FLOAT:
|
|
case ClassWriter.NAME_TYPE:
|
|
case ClassWriter.INDY:
|
|
size = 5;
|
|
break;
|
|
case ClassWriter.LONG:
|
|
case ClassWriter.DOUBLE:
|
|
size = 9;
|
|
++i;
|
|
break;
|
|
case ClassWriter.UTF8:
|
|
size = 3 + readUnsignedShort(index + 1);
|
|
if (size > max) {
|
|
max = size;
|
|
}
|
|
break;
|
|
case ClassWriter.HANDLE:
|
|
size = 4;
|
|
break;
|
|
// case ClassWriter.CLASS:
|
|
// case ClassWriter.STR:
|
|
// case ClassWriter.MTYPE
|
|
default:
|
|
size = 3;
|
|
break;
|
|
}
|
|
index += size;
|
|
}
|
|
maxStringLength = max;
|
|
// the class header information starts just after the constant pool
|
|
header = index;
|
|
}
|
|
|
|
/**
|
|
* 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() getInternalName}).
|
|
*
|
|
* @return the internal class name
|
|
* @see ClassVisitor#visit(int, int, String, String, String, String[])
|
|
*/
|
|
public String getClassName() {
|
|
return readClass(header + 2, new char[maxStringLength]);
|
|
}
|
|
|
|
/**
|
|
* Returns the internal of name of the super class (see
|
|
* {@link Type#getInternalName() getInternalName}). For interfaces, the
|
|
* super class is {@link Object}.
|
|
*
|
|
* @return the internal name of super class, or <tt>null</tt> for
|
|
* {@link Object} class.
|
|
* @see ClassVisitor#visit(int, int, String, String, String, String[])
|
|
*/
|
|
public String getSuperName() {
|
|
return readClass(header + 4, new char[maxStringLength]);
|
|
}
|
|
|
|
/**
|
|
* Returns the internal names of the class's interfaces (see
|
|
* {@link Type#getInternalName() getInternalName}).
|
|
*
|
|
* @return the array of internal names for all implemented interfaces or
|
|
* <tt>null</tt>.
|
|
* @see ClassVisitor#visit(int, int, String, String, String, String[])
|
|
*/
|
|
public String[] getInterfaces() {
|
|
int index = header + 6;
|
|
int n = readUnsignedShort(index);
|
|
String[] interfaces = new String[n];
|
|
if (n > 0) {
|
|
char[] buf = new char[maxStringLength];
|
|
for (int i = 0; i < n; ++i) {
|
|
index += 2;
|
|
interfaces[i] = readClass(index, buf);
|
|
}
|
|
}
|
|
return interfaces;
|
|
}
|
|
|
|
/**
|
|
* Copies the constant pool data into the given {@link ClassWriter}. Should
|
|
* be called before the {@link #accept(ClassVisitor, int)} method.
|
|
*
|
|
* @param classWriter the {@link ClassWriter} to copy constant pool into.
|
|
*/
|
|
void copyPool(final ClassWriter classWriter) {
|
|
char[] buf = new char[maxStringLength];
|
|
int ll = items.length;
|
|
Item[] items2 = new Item[ll];
|
|
for (int i = 1; i < ll; i++) {
|
|
int index = items[i];
|
|
int tag = b[index - 1];
|
|
Item item = new Item(i);
|
|
int nameType;
|
|
switch (tag) {
|
|
case ClassWriter.FIELD:
|
|
case ClassWriter.METH:
|
|
case ClassWriter.IMETH:
|
|
nameType = items[readUnsignedShort(index + 2)];
|
|
item.set(tag, readClass(index, buf), readUTF8(nameType, buf),
|
|
readUTF8(nameType + 2, buf));
|
|
break;
|
|
case ClassWriter.INT:
|
|
item.set(readInt(index));
|
|
break;
|
|
case ClassWriter.FLOAT:
|
|
item.set(Float.intBitsToFloat(readInt(index)));
|
|
break;
|
|
case ClassWriter.NAME_TYPE:
|
|
item.set(tag, readUTF8(index, buf), readUTF8(index + 2, buf),
|
|
null);
|
|
break;
|
|
case ClassWriter.LONG:
|
|
item.set(readLong(index));
|
|
++i;
|
|
break;
|
|
case ClassWriter.DOUBLE:
|
|
item.set(Double.longBitsToDouble(readLong(index)));
|
|
++i;
|
|
break;
|
|
case ClassWriter.UTF8: {
|
|
String s = strings[i];
|
|
if (s == null) {
|
|
index = items[i];
|
|
s = strings[i] = readUTF(index + 2,
|
|
readUnsignedShort(index), buf);
|
|
}
|
|
item.set(tag, s, null, null);
|
|
break;
|
|
}
|
|
case ClassWriter.HANDLE: {
|
|
int fieldOrMethodRef = items[readUnsignedShort(index + 1)];
|
|
nameType = items[readUnsignedShort(fieldOrMethodRef + 2)];
|
|
item.set(ClassWriter.HANDLE_BASE + readByte(index),
|
|
readClass(fieldOrMethodRef, buf),
|
|
readUTF8(nameType, buf), readUTF8(nameType + 2, buf));
|
|
break;
|
|
}
|
|
case ClassWriter.INDY:
|
|
if (classWriter.bootstrapMethods == null) {
|
|
copyBootstrapMethods(classWriter, items2, buf);
|
|
}
|
|
nameType = items[readUnsignedShort(index + 2)];
|
|
item.set(readUTF8(nameType, buf), readUTF8(nameType + 2, buf),
|
|
readUnsignedShort(index));
|
|
break;
|
|
// case ClassWriter.STR:
|
|
// case ClassWriter.CLASS:
|
|
// case ClassWriter.MTYPE
|
|
default:
|
|
item.set(tag, readUTF8(index, buf), null, null);
|
|
break;
|
|
}
|
|
|
|
int index2 = item.hashCode % items2.length;
|
|
item.next = items2[index2];
|
|
items2[index2] = item;
|
|
}
|
|
|
|
int off = items[1] - 1;
|
|
classWriter.pool.putByteArray(b, off, header - off);
|
|
classWriter.items = items2;
|
|
classWriter.threshold = (int) (0.75d * ll);
|
|
classWriter.index = ll;
|
|
}
|
|
|
|
/**
|
|
* Copies the bootstrap method data into the given {@link ClassWriter}.
|
|
* Should be called before the {@link #accept(ClassVisitor, int)} method.
|
|
*
|
|
* @param classWriter the {@link ClassWriter} to copy bootstrap methods into.
|
|
*/
|
|
private void copyBootstrapMethods(final ClassWriter classWriter,
|
|
final Item[] items, final char[] c) {
|
|
// finds the "BootstrapMethods" attribute
|
|
int u = getAttributes();
|
|
boolean found = false;
|
|
for (int i = readUnsignedShort(u); i > 0; --i) {
|
|
String attrName = readUTF8(u + 2, c);
|
|
if ("BootstrapMethods".equals(attrName)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
u += 6 + readInt(u + 4);
|
|
}
|
|
if (!found) {
|
|
return;
|
|
}
|
|
// copies the bootstrap methods in the class writer
|
|
int boostrapMethodCount = readUnsignedShort(u + 8);
|
|
for (int j = 0, v = u + 10; j < boostrapMethodCount; j++) {
|
|
int position = v - u - 10;
|
|
int hashCode = readConst(readUnsignedShort(v), c).hashCode();
|
|
for (int k = readUnsignedShort(v + 2); k > 0; --k) {
|
|
hashCode ^= readConst(readUnsignedShort(v + 4), c).hashCode();
|
|
v += 2;
|
|
}
|
|
v += 4;
|
|
Item item = new Item(j);
|
|
item.set(position, hashCode & 0x7FFFFFFF);
|
|
int index = item.hashCode % items.length;
|
|
item.next = items[index];
|
|
items[index] = item;
|
|
}
|
|
int attrSize = readInt(u + 4);
|
|
ByteVector bootstrapMethods = new ByteVector(attrSize + 62);
|
|
bootstrapMethods.putByteArray(b, u + 10, attrSize - 2);
|
|
classWriter.bootstrapMethodsCount = boostrapMethodCount;
|
|
classWriter.bootstrapMethods = bootstrapMethods;
|
|
}
|
|
|
|
/**
|
|
* Constructs a new {@link ClassReader} object.
|
|
*
|
|
* @param is an input stream from which to read the class.
|
|
* @throws IOException if a problem occurs during reading.
|
|
*/
|
|
public ClassReader(final InputStream is) throws IOException {
|
|
this(readClass(is, false));
|
|
}
|
|
|
|
/**
|
|
* Constructs a new {@link ClassReader} object.
|
|
*
|
|
* @param name the binary qualified name of the class to be read.
|
|
* @throws IOException if an exception occurs during reading.
|
|
*/
|
|
public ClassReader(final String name) throws IOException {
|
|
this(readClass(
|
|
ClassLoader.getSystemResourceAsStream(name.replace('.', '/')
|
|
+ ".class"), true));
|
|
}
|
|
|
|
/**
|
|
* Reads the bytecode of a class.
|
|
*
|
|
* @param is an input stream from which to read the class.
|
|
* @param close true to close the input stream after reading.
|
|
* @return the bytecode read from the given input stream.
|
|
* @throws IOException if a problem occurs during reading.
|
|
*/
|
|
private static byte[] readClass(final InputStream is, boolean close)
|
|
throws IOException {
|
|
if (is == null) {
|
|
throw new IOException("Class not found");
|
|
}
|
|
try {
|
|
byte[] b = new byte[is.available()];
|
|
int len = 0;
|
|
while (true) {
|
|
int n = is.read(b, len, b.length - len);
|
|
if (n == -1) {
|
|
if (len < b.length) {
|
|
byte[] c = new byte[len];
|
|
System.arraycopy(b, 0, c, 0, len);
|
|
b = c;
|
|
}
|
|
return b;
|
|
}
|
|
len += n;
|
|
if (len == b.length) {
|
|
int last = is.read();
|
|
if (last < 0) {
|
|
return b;
|
|
}
|
|
byte[] c = new byte[b.length + 1000];
|
|
System.arraycopy(b, 0, c, 0, len);
|
|
c[len++] = (byte) last;
|
|
b = c;
|
|
}
|
|
}
|
|
} finally {
|
|
if (close) {
|
|
is.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Public methods
|
|
// ------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Makes the given visitor visit the Java class of this {@link ClassReader}
|
|
* . This class is the one specified in the constructor (see
|
|
* {@link #ClassReader(byte[]) ClassReader}).
|
|
*
|
|
* @param classVisitor the visitor that must visit this class.
|
|
* @param flags option flags that can be used to modify the default behavior
|
|
* of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
|
|
* , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
|
|
*/
|
|
public void accept(final ClassVisitor classVisitor, final int flags) {
|
|
accept(classVisitor, new Attribute[0], flags);
|
|
}
|
|
|
|
/**
|
|
* Makes the given visitor visit the Java class of this {@link ClassReader}.
|
|
* This class is the one specified in the constructor (see
|
|
* {@link #ClassReader(byte[]) ClassReader}).
|
|
*
|
|
* @param classVisitor the visitor that must visit this class.
|
|
* @param attrs 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 flags option flags that can be used to modify the default behavior
|
|
* of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
|
|
* , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
|
|
*/
|
|
public void accept(final ClassVisitor classVisitor,
|
|
final Attribute[] attrs, final int flags) {
|
|
int u = header; // current offset in the class file
|
|
char[] c = new char[maxStringLength]; // buffer used to read strings
|
|
|
|
Context context = new Context();
|
|
context.attrs = attrs;
|
|
context.flags = flags;
|
|
context.buffer = c;
|
|
|
|
// reads the class declaration
|
|
int access = readUnsignedShort(u);
|
|
String name = readClass(u + 2, c);
|
|
String superClass = readClass(u + 4, c);
|
|
String[] interfaces = new String[readUnsignedShort(u + 6)];
|
|
u += 8;
|
|
for (int i = 0; i < interfaces.length; ++i) {
|
|
interfaces[i] = readClass(u, c);
|
|
u += 2;
|
|
}
|
|
|
|
// reads the class attributes
|
|
String signature = null;
|
|
String sourceFile = null;
|
|
String sourceDebug = null;
|
|
String enclosingOwner = null;
|
|
String enclosingName = null;
|
|
String enclosingDesc = null;
|
|
int anns = 0;
|
|
int ianns = 0;
|
|
int tanns = 0;
|
|
int itanns = 0;
|
|
int innerClasses = 0;
|
|
Attribute attributes = null;
|
|
|
|
u = getAttributes();
|
|
for (int i = readUnsignedShort(u); i > 0; --i) {
|
|
String attrName = readUTF8(u + 2, c);
|
|
// tests are sorted in decreasing frequency order
|
|
// (based on frequencies observed on typical classes)
|
|
if ("SourceFile".equals(attrName)) {
|
|
sourceFile = readUTF8(u + 8, c);
|
|
} else if ("InnerClasses".equals(attrName)) {
|
|
innerClasses = u + 8;
|
|
} else if ("EnclosingMethod".equals(attrName)) {
|
|
enclosingOwner = readClass(u + 8, c);
|
|
int item = readUnsignedShort(u + 10);
|
|
if (item != 0) {
|
|
enclosingName = readUTF8(items[item], c);
|
|
enclosingDesc = readUTF8(items[item] + 2, c);
|
|
}
|
|
} else if (SIGNATURES && "Signature".equals(attrName)) {
|
|
signature = readUTF8(u + 8, c);
|
|
} else if (ANNOTATIONS
|
|
&& "RuntimeVisibleAnnotations".equals(attrName)) {
|
|
anns = u + 8;
|
|
} else if (ANNOTATIONS
|
|
&& "RuntimeVisibleTypeAnnotations".equals(attrName)) {
|
|
tanns = u + 8;
|
|
} else if ("Deprecated".equals(attrName)) {
|
|
access |= Opcodes.ACC_DEPRECATED;
|
|
} else if ("Synthetic".equals(attrName)) {
|
|
access |= Opcodes.ACC_SYNTHETIC
|
|
| ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
|
|
} else if ("SourceDebugExtension".equals(attrName)) {
|
|
int len = readInt(u + 4);
|
|
sourceDebug = readUTF(u + 8, len, new char[len]);
|
|
} else if (ANNOTATIONS
|
|
&& "RuntimeInvisibleAnnotations".equals(attrName)) {
|
|
ianns = u + 8;
|
|
} else if (ANNOTATIONS
|
|
&& "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
|
|
itanns = u + 8;
|
|
} else if ("BootstrapMethods".equals(attrName)) {
|
|
int[] bootstrapMethods = new int[readUnsignedShort(u + 8)];
|
|
for (int j = 0, v = u + 10; j < bootstrapMethods.length; j++) {
|
|
bootstrapMethods[j] = v;
|
|
v += 2 + readUnsignedShort(v + 2) << 1;
|
|
}
|
|
context.bootstrapMethods = bootstrapMethods;
|
|
} else {
|
|
Attribute attr = readAttribute(attrs, attrName, u + 8,
|
|
readInt(u + 4), c, -1, null);
|
|
if (attr != null) {
|
|
attr.next = attributes;
|
|
attributes = attr;
|
|
}
|
|
}
|
|
u += 6 + readInt(u + 4);
|
|
}
|
|
|
|
// visits the class declaration
|
|
classVisitor.visit(readInt(items[1] - 7), access, name, signature,
|
|
superClass, interfaces);
|
|
|
|
// visits the source and debug info
|
|
if ((flags & SKIP_DEBUG) == 0
|
|
&& (sourceFile != null || sourceDebug != null)) {
|
|
classVisitor.visitSource(sourceFile, sourceDebug);
|
|
}
|
|
|
|
// visits the outer class
|
|
if (enclosingOwner != null) {
|
|
classVisitor.visitOuterClass(enclosingOwner, enclosingName,
|
|
enclosingDesc);
|
|
}
|
|
|
|
// visits the class annotations and type annotations
|
|
if (ANNOTATIONS && anns != 0) {
|
|
for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
|
|
v = readAnnotationValues(v + 2, c, true,
|
|
classVisitor.visitAnnotation(readUTF8(v, c), true));
|
|
}
|
|
}
|
|
if (ANNOTATIONS && ianns != 0) {
|
|
for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
|
|
v = readAnnotationValues(v + 2, c, true,
|
|
classVisitor.visitAnnotation(readUTF8(v, c), false));
|
|
}
|
|
}
|
|
if (ANNOTATIONS && tanns != 0) {
|
|
for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
|
|
v = readAnnotationTarget(context, v);
|
|
v = readAnnotationValues(v + 2, c, true,
|
|
classVisitor.visitTypeAnnotation(context.typeRef,
|
|
context.typePath, readUTF8(v, c), true));
|
|
}
|
|
}
|
|
if (ANNOTATIONS && itanns != 0) {
|
|
for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
|
|
v = readAnnotationTarget(context, v);
|
|
v = readAnnotationValues(v + 2, c, true,
|
|
classVisitor.visitTypeAnnotation(context.typeRef,
|
|
context.typePath, readUTF8(v, c), false));
|
|
}
|
|
}
|
|
|
|
// visits the attributes
|
|
while (attributes != null) {
|
|
Attribute attr = attributes.next;
|
|
attributes.next = null;
|
|
classVisitor.visitAttribute(attributes);
|
|
attributes = attr;
|
|
}
|
|
|
|
// visits the inner classes
|
|
if (innerClasses != 0) {
|
|
int v = innerClasses + 2;
|
|
for (int i = readUnsignedShort(innerClasses); i > 0; --i) {
|
|
classVisitor.visitInnerClass(readClass(v, c),
|
|
readClass(v + 2, c), readUTF8(v + 4, c),
|
|
readUnsignedShort(v + 6));
|
|
v += 8;
|
|
}
|
|
}
|
|
|
|
// visits the fields and methods
|
|
u = header + 10 + 2 * interfaces.length;
|
|
for (int i = readUnsignedShort(u - 2); i > 0; --i) {
|
|
u = readField(classVisitor, context, u);
|
|
}
|
|
u += 2;
|
|
for (int i = readUnsignedShort(u - 2); i > 0; --i) {
|
|
u = readMethod(classVisitor, context, u);
|
|
}
|
|
|
|
// visits the end of the class
|
|
classVisitor.visitEnd();
|
|
}
|
|
|
|
/**
|
|
* Reads a field 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 u the start offset of the field in the class file.
|
|
* @return the offset of the first byte following the field in the class.
|
|
*/
|
|
private int readField(final ClassVisitor classVisitor,
|
|
final Context context, int u) {
|
|
// reads the field declaration
|
|
char[] c = context.buffer;
|
|
int access = readUnsignedShort(u);
|
|
String name = readUTF8(u + 2, c);
|
|
String desc = readUTF8(u + 4, c);
|
|
u += 6;
|
|
|
|
// reads the field attributes
|
|
String signature = null;
|
|
int anns = 0;
|
|
int ianns = 0;
|
|
int tanns = 0;
|
|
int itanns = 0;
|
|
Object value = null;
|
|
Attribute attributes = null;
|
|
|
|
for (int i = readUnsignedShort(u); i > 0; --i) {
|
|
String attrName = readUTF8(u + 2, c);
|
|
// tests are sorted in decreasing frequency order
|
|
// (based on frequencies observed on typical classes)
|
|
if ("ConstantValue".equals(attrName)) {
|
|
int item = readUnsignedShort(u + 8);
|
|
value = item == 0 ? null : readConst(item, c);
|
|
} else if (SIGNATURES && "Signature".equals(attrName)) {
|
|
signature = readUTF8(u + 8, c);
|
|
} else if ("Deprecated".equals(attrName)) {
|
|
access |= Opcodes.ACC_DEPRECATED;
|
|
} else if ("Synthetic".equals(attrName)) {
|
|
access |= Opcodes.ACC_SYNTHETIC
|
|
| ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
|
|
} else if (ANNOTATIONS
|
|
&& "RuntimeVisibleAnnotations".equals(attrName)) {
|
|
anns = u + 8;
|
|
} else if (ANNOTATIONS
|
|
&& "RuntimeVisibleTypeAnnotations".equals(attrName)) {
|
|
tanns = u + 8;
|
|
} else if (ANNOTATIONS
|
|
&& "RuntimeInvisibleAnnotations".equals(attrName)) {
|
|
ianns = u + 8;
|
|
} else if (ANNOTATIONS
|
|
&& "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
|
|
itanns = u + 8;
|
|
} else {
|
|
Attribute attr = readAttribute(context.attrs, attrName, u + 8,
|
|
readInt(u + 4), c, -1, null);
|
|
if (attr != null) {
|
|
attr.next = attributes;
|
|
attributes = attr;
|
|
}
|
|
}
|
|
u += 6 + readInt(u + 4);
|
|
}
|
|
u += 2;
|
|
|
|
// visits the field declaration
|
|
FieldVisitor fv = classVisitor.visitField(access, name, desc,
|
|
signature, value);
|
|
if (fv == null) {
|
|
return u;
|
|
}
|
|
|
|
// visits the field annotations and type annotations
|
|
if (ANNOTATIONS && anns != 0) {
|
|
for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
|
|
v = readAnnotationValues(v + 2, c, true,
|
|
fv.visitAnnotation(readUTF8(v, c), true));
|
|
}
|
|
}
|
|
if (ANNOTATIONS && ianns != 0) {
|
|
for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
|
|
v = readAnnotationValues(v + 2, c, true,
|
|
fv.visitAnnotation(readUTF8(v, c), false));
|
|
}
|
|
}
|
|
if (ANNOTATIONS && tanns != 0) {
|
|
for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
|
|
v = readAnnotationTarget(context, v);
|
|
v = readAnnotationValues(v + 2, c, true,
|
|
fv.visitTypeAnnotation(context.typeRef,
|
|
context.typePath, readUTF8(v, c), true));
|
|
}
|
|
}
|
|
if (ANNOTATIONS && itanns != 0) {
|
|
for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
|
|
v = readAnnotationTarget(context, v);
|
|
v = readAnnotationValues(v + 2, c, true,
|
|
fv.visitTypeAnnotation(context.typeRef,
|
|
context.typePath, readUTF8(v, c), false));
|
|
}
|
|
}
|
|
|
|
// visits the field attributes
|
|
while (attributes != null) {
|
|
Attribute attr = attributes.next;
|
|
attributes.next = null;
|
|
fv.visitAttribute(attributes);
|
|
attributes = attr;
|
|
}
|
|
|
|
// visits the end of the field
|
|
fv.visitEnd();
|
|
|
|
return u;
|
|
}
|
|
|
|
/**
|
|
* Reads a method 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 u the start offset of the method in the class file.
|
|
* @return the offset of the first byte following the method in the class.
|
|
*/
|
|
private int readMethod(final ClassVisitor classVisitor,
|
|
final Context context, int u) {
|
|
// reads the method declaration
|
|
char[] c = context.buffer;
|
|
context.access = readUnsignedShort(u);
|
|
context.name = readUTF8(u + 2, c);
|
|
context.desc = readUTF8(u + 4, c);
|
|
u += 6;
|
|
|
|
// reads the method attributes
|
|
int code = 0;
|
|
int exception = 0;
|
|
String[] exceptions = null;
|
|
String signature = null;
|
|
int methodParameters = 0;
|
|
int anns = 0;
|
|
int ianns = 0;
|
|
int tanns = 0;
|
|
int itanns = 0;
|
|
int dann = 0;
|
|
int mpanns = 0;
|
|
int impanns = 0;
|
|
int firstAttribute = u;
|
|
Attribute attributes = null;
|
|
|
|
for (int i = readUnsignedShort(u); i > 0; --i) {
|
|
String attrName = readUTF8(u + 2, c);
|
|
// tests are sorted in decreasing frequency order
|
|
// (based on frequencies observed on typical classes)
|
|
if ("Code".equals(attrName)) {
|
|
if ((context.flags & SKIP_CODE) == 0) {
|
|
code = u + 8;
|
|
}
|
|
} else if ("Exceptions".equals(attrName)) {
|
|
exceptions = new String[readUnsignedShort(u + 8)];
|
|
exception = u + 10;
|
|
for (int j = 0; j < exceptions.length; ++j) {
|
|
exceptions[j] = readClass(exception, c);
|
|
exception += 2;
|
|
}
|
|
} else if (SIGNATURES && "Signature".equals(attrName)) {
|
|
signature = readUTF8(u + 8, c);
|
|
} else if ("Deprecated".equals(attrName)) {
|
|
context.access |= Opcodes.ACC_DEPRECATED;
|
|
} else if (ANNOTATIONS
|
|
&& "RuntimeVisibleAnnotations".equals(attrName)) {
|
|
anns = u + 8;
|
|
} else if (ANNOTATIONS
|
|
&& "RuntimeVisibleTypeAnnotations".equals(attrName)) {
|
|
tanns = u + 8;
|
|
} else if (ANNOTATIONS && "AnnotationDefault".equals(attrName)) {
|
|
dann = u + 8;
|
|
} else if ("Synthetic".equals(attrName)) {
|
|
context.access |= Opcodes.ACC_SYNTHETIC
|
|
| ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
|
|
} else if (ANNOTATIONS
|
|
&& "RuntimeInvisibleAnnotations".equals(attrName)) {
|
|
ianns = u + 8;
|
|
} else if (ANNOTATIONS
|
|
&& "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
|
|
itanns = u + 8;
|
|
} else if (ANNOTATIONS
|
|
&& "RuntimeVisibleParameterAnnotations".equals(attrName)) {
|
|
mpanns = u + 8;
|
|
} else if (ANNOTATIONS
|
|
&& "RuntimeInvisibleParameterAnnotations".equals(attrName)) {
|
|
impanns = u + 8;
|
|
} else if ("MethodParameters".equals(attrName)) {
|
|
methodParameters = u + 8;
|
|
} else {
|
|
Attribute attr = readAttribute(context.attrs, attrName, u + 8,
|
|
readInt(u + 4), c, -1, null);
|
|
if (attr != null) {
|
|
attr.next = attributes;
|
|
attributes = attr;
|
|
}
|
|
}
|
|
u += 6 + readInt(u + 4);
|
|
}
|
|
u += 2;
|
|
|
|
// visits the method declaration
|
|
MethodVisitor mv = classVisitor.visitMethod(context.access,
|
|
context.name, context.desc, signature, exceptions);
|
|
if (mv == null) {
|
|
return u;
|
|
}
|
|
|
|
/*
|
|
* if the returned MethodVisitor is in fact a MethodWriter, it means
|
|
* there is no method adapter between the reader and the writer. If, in
|
|
* addition, the writer's constant pool was copied from this reader
|
|
* (mw.cw.cr == this), and the signature and exceptions of the method
|
|
* have not been changed, then it is possible to skip all visit events
|
|
* and just copy the original code of the method to the writer (the
|
|
* access, name and descriptor can have been changed, this is not
|
|
* important since they are not copied as is from the reader).
|
|
*/
|
|
if (WRITER && mv instanceof MethodWriter) {
|
|
MethodWriter mw = (MethodWriter) mv;
|
|
if (mw.cw.cr == this && signature == mw.signature) {
|
|
boolean sameExceptions = false;
|
|
if (exceptions == null) {
|
|
sameExceptions = mw.exceptionCount == 0;
|
|
} else if (exceptions.length == mw.exceptionCount) {
|
|
sameExceptions = true;
|
|
for (int j = exceptions.length - 1; j >= 0; --j) {
|
|
exception -= 2;
|
|
if (mw.exceptions[j] != readUnsignedShort(exception)) {
|
|
sameExceptions = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (sameExceptions) {
|
|
/*
|
|
* we do not copy directly the code into MethodWriter to
|
|
* save a byte array copy operation. The real copy will be
|
|
* done in ClassWriter.toByteArray().
|
|
*/
|
|
mw.classReaderOffset = firstAttribute;
|
|
mw.classReaderLength = u - firstAttribute;
|
|
return u;
|
|
}
|
|
}
|
|
}
|
|
|
|
// visit the method parameters
|
|
if (methodParameters != 0) {
|
|
for (int i = b[methodParameters] & 0xFF, v = methodParameters + 1; i > 0; --i, v = v + 4) {
|
|
mv.visitParameter(readUTF8(v, c), readUnsignedShort(v + 2));
|
|
}
|
|
}
|
|
|
|
// visits the method annotations
|
|
if (ANNOTATIONS && dann != 0) {
|
|
AnnotationVisitor dv = mv.visitAnnotationDefault();
|
|
readAnnotationValue(dann, c, null, dv);
|
|
if (dv != null) {
|
|
dv.visitEnd();
|
|
}
|
|
}
|
|
if (ANNOTATIONS && anns != 0) {
|
|
for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
|
|
v = readAnnotationValues(v + 2, c, true,
|
|
mv.visitAnnotation(readUTF8(v, c), true));
|
|
}
|
|
}
|
|
if (ANNOTATIONS && ianns != 0) {
|
|
for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
|
|
v = readAnnotationValues(v + 2, c, true,
|
|
mv.visitAnnotation(readUTF8(v, c), false));
|
|
}
|
|
}
|
|
if (ANNOTATIONS && tanns != 0) {
|
|
for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
|
|
v = readAnnotationTarget(context, v);
|
|
v = readAnnotationValues(v + 2, c, true,
|
|
mv.visitTypeAnnotation(context.typeRef,
|
|
context.typePath, readUTF8(v, c), true));
|
|
}
|
|
}
|
|
if (ANNOTATIONS && itanns != 0) {
|
|
for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
|
|
v = readAnnotationTarget(context, v);
|
|
v = readAnnotationValues(v + 2, c, true,
|
|
mv.visitTypeAnnotation(context.typeRef,
|
|
context.typePath, readUTF8(v, c), false));
|
|
}
|
|
}
|
|
if (ANNOTATIONS && mpanns != 0) {
|
|
readParameterAnnotations(mv, context, mpanns, true);
|
|
}
|
|
if (ANNOTATIONS && impanns != 0) {
|
|
readParameterAnnotations(mv, context, impanns, false);
|
|
}
|
|
|
|
// visits the method attributes
|
|
while (attributes != null) {
|
|
Attribute attr = attributes.next;
|
|
attributes.next = null;
|
|
mv.visitAttribute(attributes);
|
|
attributes = attr;
|
|
}
|
|
|
|
// visits the method code
|
|
if (code != 0) {
|
|
mv.visitCode();
|
|
readCode(mv, context, code);
|
|
}
|
|
|
|
// visits the end of the method
|
|
mv.visitEnd();
|
|
|
|
return u;
|
|
}
|
|
|
|
/**
|
|
* Reads the bytecode of a method and makes the given visitor visit it.
|
|
*
|
|
* @param mv the visitor that must visit the method's code.
|
|
* @param context information about the class being parsed.
|
|
* @param u the start offset of the code attribute in the class file.
|
|
*/
|
|
private void readCode(final MethodVisitor mv, final Context context, int u) {
|
|
// reads the header
|
|
byte[] b = this.b;
|
|
char[] c = context.buffer;
|
|
int maxStack = readUnsignedShort(u);
|
|
int maxLocals = readUnsignedShort(u + 2);
|
|
int codeLength = readInt(u + 4);
|
|
u += 8;
|
|
|
|
// reads the bytecode to find the labels
|
|
int codeStart = u;
|
|
int codeEnd = u + codeLength;
|
|
Label[] labels = context.labels = new Label[codeLength + 2];
|
|
readLabel(codeLength + 1, labels);
|
|
while (u < codeEnd) {
|
|
int offset = u - codeStart;
|
|
int opcode = b[u] & 0xFF;
|
|
switch (ClassWriter.TYPE[opcode]) {
|
|
case ClassWriter.NOARG_INSN:
|
|
case ClassWriter.IMPLVAR_INSN:
|
|
u += 1;
|
|
break;
|
|
case ClassWriter.LABEL_INSN:
|
|
readLabel(offset + readShort(u + 1), labels);
|
|
u += 3;
|
|
break;
|
|
case ClassWriter.LABELW_INSN:
|
|
readLabel(offset + readInt(u + 1), labels);
|
|
u += 5;
|
|
break;
|
|
case ClassWriter.WIDE_INSN:
|
|
opcode = b[u + 1] & 0xFF;
|
|
if (opcode == Opcodes.IINC) {
|
|
u += 6;
|
|
} else {
|
|
u += 4;
|
|
}
|
|
break;
|
|
case ClassWriter.TABL_INSN:
|
|
// skips 0 to 3 padding bytes
|
|
u = u + 4 - (offset & 3);
|
|
// reads instruction
|
|
readLabel(offset + readInt(u), labels);
|
|
for (int i = readInt(u + 8) - readInt(u + 4) + 1; i > 0; --i) {
|
|
readLabel(offset + readInt(u + 12), labels);
|
|
u += 4;
|
|
}
|
|
u += 12;
|
|
break;
|
|
case ClassWriter.LOOK_INSN:
|
|
// skips 0 to 3 padding bytes
|
|
u = u + 4 - (offset & 3);
|
|
// reads instruction
|
|
readLabel(offset + readInt(u), labels);
|
|
for (int i = readInt(u + 4); i > 0; --i) {
|
|
readLabel(offset + readInt(u + 12), labels);
|
|
u += 8;
|
|
}
|
|
u += 8;
|
|
break;
|
|
case ClassWriter.VAR_INSN:
|
|
case ClassWriter.SBYTE_INSN:
|
|
case ClassWriter.LDC_INSN:
|
|
u += 2;
|
|
break;
|
|
case ClassWriter.SHORT_INSN:
|
|
case ClassWriter.LDCW_INSN:
|
|
case ClassWriter.FIELDORMETH_INSN:
|
|
case ClassWriter.TYPE_INSN:
|
|
case ClassWriter.IINC_INSN:
|
|
u += 3;
|
|
break;
|
|
case ClassWriter.ITFMETH_INSN:
|
|
case ClassWriter.INDYMETH_INSN:
|
|
u += 5;
|
|
break;
|
|
// case MANA_INSN:
|
|
default:
|
|
u += 4;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// reads the try catch entries to find the labels, and also visits them
|
|
for (int i = readUnsignedShort(u); i > 0; --i) {
|
|
Label start = readLabel(readUnsignedShort(u + 2), labels);
|
|
Label end = readLabel(readUnsignedShort(u + 4), labels);
|
|
Label handler = readLabel(readUnsignedShort(u + 6), labels);
|
|
String type = readUTF8(items[readUnsignedShort(u + 8)], c);
|
|
mv.visitTryCatchBlock(start, end, handler, type);
|
|
u += 8;
|
|
}
|
|
u += 2;
|
|
|
|
// reads the code attributes
|
|
int[] tanns = null; // start index of each visible type annotation
|
|
int[] itanns = null; // start index of each invisible type annotation
|
|
int tann = 0; // current index in tanns array
|
|
int itann = 0; // current index in itanns array
|
|
int ntoff = -1; // next visible type annotation code offset
|
|
int nitoff = -1; // next invisible type annotation code offset
|
|
int varTable = 0;
|
|
int varTypeTable = 0;
|
|
boolean zip = true;
|
|
boolean unzip = (context.flags & EXPAND_FRAMES) != 0;
|
|
int stackMap = 0;
|
|
int stackMapSize = 0;
|
|
int frameCount = 0;
|
|
Context frame = null;
|
|
Attribute attributes = null;
|
|
|
|
for (int i = readUnsignedShort(u); i > 0; --i) {
|
|
String attrName = readUTF8(u + 2, c);
|
|
if ("LocalVariableTable".equals(attrName)) {
|
|
if ((context.flags & SKIP_DEBUG) == 0) {
|
|
varTable = u + 8;
|
|
for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) {
|
|
int label = readUnsignedShort(v + 10);
|
|
if (labels[label] == null) {
|
|
readLabel(label, labels).status |= Label.DEBUG;
|
|
}
|
|
label += readUnsignedShort(v + 12);
|
|
if (labels[label] == null) {
|
|
readLabel(label, labels).status |= Label.DEBUG;
|
|
}
|
|
v += 10;
|
|
}
|
|
}
|
|
} else if ("LocalVariableTypeTable".equals(attrName)) {
|
|
varTypeTable = u + 8;
|
|
} else if ("LineNumberTable".equals(attrName)) {
|
|
if ((context.flags & SKIP_DEBUG) == 0) {
|
|
for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) {
|
|
int label = readUnsignedShort(v + 10);
|
|
if (labels[label] == null) {
|
|
readLabel(label, labels).status |= Label.DEBUG;
|
|
}
|
|
Label l = labels[label];
|
|
while (l.line > 0) {
|
|
if (l.next == null) {
|
|
l.next = new Label();
|
|
}
|
|
l = l.next;
|
|
}
|
|
l.line = readUnsignedShort(v + 12);
|
|
v += 4;
|
|
}
|
|
}
|
|
} else if (ANNOTATIONS
|
|
&& "RuntimeVisibleTypeAnnotations".equals(attrName)) {
|
|
tanns = readTypeAnnotations(mv, context, u + 8, true);
|
|
ntoff = tanns.length == 0 || readByte(tanns[0]) < 0x43 ? -1
|
|
: readUnsignedShort(tanns[0] + 1);
|
|
} else if (ANNOTATIONS
|
|
&& "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
|
|
itanns = readTypeAnnotations(mv, context, u + 8, false);
|
|
nitoff = itanns.length == 0 || readByte(itanns[0]) < 0x43 ? -1
|
|
: readUnsignedShort(itanns[0] + 1);
|
|
} else if (FRAMES && "StackMapTable".equals(attrName)) {
|
|
if ((context.flags & SKIP_FRAMES) == 0) {
|
|
stackMap = u + 10;
|
|
stackMapSize = readInt(u + 4);
|
|
frameCount = readUnsignedShort(u + 8);
|
|
}
|
|
/*
|
|
* 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 in the second
|
|
* phase (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 insn and for which no
|
|
* Label exist.
|
|
*/
|
|
/*
|
|
* This is not true for UNINITIALIZED type offsets. We solve
|
|
* this by parsing the stack map table without a full decoding
|
|
* (see below).
|
|
*/
|
|
} else if (FRAMES && "StackMap".equals(attrName)) {
|
|
if ((context.flags & SKIP_FRAMES) == 0) {
|
|
zip = false;
|
|
stackMap = u + 10;
|
|
stackMapSize = readInt(u + 4);
|
|
frameCount = readUnsignedShort(u + 8);
|
|
}
|
|
/*
|
|
* IMPORTANT! here we assume that the frames are ordered, as in
|
|
* the StackMapTable attribute, although this is not guaranteed
|
|
* by the attribute format.
|
|
*/
|
|
} else {
|
|
for (int j = 0; j < context.attrs.length; ++j) {
|
|
if (context.attrs[j].type.equals(attrName)) {
|
|
Attribute attr = context.attrs[j].read(this, u + 8,
|
|
readInt(u + 4), c, codeStart - 8, labels);
|
|
if (attr != null) {
|
|
attr.next = attributes;
|
|
attributes = attr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
u += 6 + readInt(u + 4);
|
|
}
|
|
u += 2;
|
|
|
|
// generates the first (implicit) stack map frame
|
|
if (FRAMES && stackMap != 0) {
|
|
/*
|
|
* for the first explicit frame the offset is not offset_delta + 1
|
|
* but only offset_delta; setting the implicit frame offset to -1
|
|
* allow the use of the "offset_delta + 1" rule in all cases
|
|
*/
|
|
frame = context;
|
|
frame.offset = -1;
|
|
frame.mode = 0;
|
|
frame.localCount = 0;
|
|
frame.localDiff = 0;
|
|
frame.stackCount = 0;
|
|
frame.local = new Object[maxLocals];
|
|
frame.stack = new Object[maxStack];
|
|
if (unzip) {
|
|
getImplicitFrame(context);
|
|
}
|
|
/*
|
|
* Finds 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 8, offset
|
|
* within code 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 i = stackMap; i < stackMap + stackMapSize - 2; ++i) {
|
|
if (b[i] == 8) { // UNINITIALIZED FRAME TYPE
|
|
int v = readUnsignedShort(i + 1);
|
|
if (v >= 0 && v < codeLength) {
|
|
if ((b[codeStart + v] & 0xFF) == Opcodes.NEW) {
|
|
readLabel(v, labels);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// visits the instructions
|
|
u = codeStart;
|
|
while (u < codeEnd) {
|
|
int offset = u - codeStart;
|
|
|
|
// visits the label and line number for this offset, if any
|
|
Label l = labels[offset];
|
|
if (l != null) {
|
|
Label next = l.next;
|
|
l.next = null;
|
|
mv.visitLabel(l);
|
|
if ((context.flags & SKIP_DEBUG) == 0 && l.line > 0) {
|
|
mv.visitLineNumber(l.line, l);
|
|
while (next != null) {
|
|
mv.visitLineNumber(next.line, l);
|
|
next = next.next;
|
|
}
|
|
}
|
|
}
|
|
|
|
// visits the frame for this offset, if any
|
|
while (FRAMES && frame != null
|
|
&& (frame.offset == offset || frame.offset == -1)) {
|
|
// if there is a frame for this offset, makes the visitor visit
|
|
// it, and reads the next frame if there is one.
|
|
if (frame.offset != -1) {
|
|
if (!zip || unzip) {
|
|
mv.visitFrame(Opcodes.F_NEW, frame.localCount,
|
|
frame.local, frame.stackCount, frame.stack);
|
|
} else {
|
|
mv.visitFrame(frame.mode, frame.localDiff, frame.local,
|
|
frame.stackCount, frame.stack);
|
|
}
|
|
}
|
|
if (frameCount > 0) {
|
|
stackMap = readFrame(stackMap, zip, unzip, frame);
|
|
--frameCount;
|
|
} else {
|
|
frame = null;
|
|
}
|
|
}
|
|
|
|
// visits the instruction at this offset
|
|
int opcode = b[u] & 0xFF;
|
|
switch (ClassWriter.TYPE[opcode]) {
|
|
case ClassWriter.NOARG_INSN:
|
|
mv.visitInsn(opcode);
|
|
u += 1;
|
|
break;
|
|
case ClassWriter.IMPLVAR_INSN:
|
|
if (opcode > Opcodes.ISTORE) {
|
|
opcode -= 59; // ISTORE_0
|
|
mv.visitVarInsn(Opcodes.ISTORE + (opcode >> 2),
|
|
opcode & 0x3);
|
|
} else {
|
|
opcode -= 26; // ILOAD_0
|
|
mv.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), opcode & 0x3);
|
|
}
|
|
u += 1;
|
|
break;
|
|
case ClassWriter.LABEL_INSN:
|
|
mv.visitJumpInsn(opcode, labels[offset + readShort(u + 1)]);
|
|
u += 3;
|
|
break;
|
|
case ClassWriter.LABELW_INSN:
|
|
mv.visitJumpInsn(opcode - 33, labels[offset + readInt(u + 1)]);
|
|
u += 5;
|
|
break;
|
|
case ClassWriter.WIDE_INSN:
|
|
opcode = b[u + 1] & 0xFF;
|
|
if (opcode == Opcodes.IINC) {
|
|
mv.visitIincInsn(readUnsignedShort(u + 2), readShort(u + 4));
|
|
u += 6;
|
|
} else {
|
|
mv.visitVarInsn(opcode, readUnsignedShort(u + 2));
|
|
u += 4;
|
|
}
|
|
break;
|
|
case ClassWriter.TABL_INSN: {
|
|
// skips 0 to 3 padding bytes
|
|
u = u + 4 - (offset & 3);
|
|
// reads instruction
|
|
int label = offset + readInt(u);
|
|
int min = readInt(u + 4);
|
|
int max = readInt(u + 8);
|
|
Label[] table = new Label[max - min + 1];
|
|
u += 12;
|
|
for (int i = 0; i < table.length; ++i) {
|
|
table[i] = labels[offset + readInt(u)];
|
|
u += 4;
|
|
}
|
|
mv.visitTableSwitchInsn(min, max, labels[label], table);
|
|
break;
|
|
}
|
|
case ClassWriter.LOOK_INSN: {
|
|
// skips 0 to 3 padding bytes
|
|
u = u + 4 - (offset & 3);
|
|
// reads instruction
|
|
int label = offset + readInt(u);
|
|
int len = readInt(u + 4);
|
|
int[] keys = new int[len];
|
|
Label[] values = new Label[len];
|
|
u += 8;
|
|
for (int i = 0; i < len; ++i) {
|
|
keys[i] = readInt(u);
|
|
values[i] = labels[offset + readInt(u + 4)];
|
|
u += 8;
|
|
}
|
|
mv.visitLookupSwitchInsn(labels[label], keys, values);
|
|
break;
|
|
}
|
|
case ClassWriter.VAR_INSN:
|
|
mv.visitVarInsn(opcode, b[u + 1] & 0xFF);
|
|
u += 2;
|
|
break;
|
|
case ClassWriter.SBYTE_INSN:
|
|
mv.visitIntInsn(opcode, b[u + 1]);
|
|
u += 2;
|
|
break;
|
|
case ClassWriter.SHORT_INSN:
|
|
mv.visitIntInsn(opcode, readShort(u + 1));
|
|
u += 3;
|
|
break;
|
|
case ClassWriter.LDC_INSN:
|
|
mv.visitLdcInsn(readConst(b[u + 1] & 0xFF, c));
|
|
u += 2;
|
|
break;
|
|
case ClassWriter.LDCW_INSN:
|
|
mv.visitLdcInsn(readConst(readUnsignedShort(u + 1), c));
|
|
u += 3;
|
|
break;
|
|
case ClassWriter.FIELDORMETH_INSN:
|
|
case ClassWriter.ITFMETH_INSN: {
|
|
int cpIndex = items[readUnsignedShort(u + 1)];
|
|
boolean itf = b[cpIndex - 1] == ClassWriter.IMETH;
|
|
String iowner = readClass(cpIndex, c);
|
|
cpIndex = items[readUnsignedShort(cpIndex + 2)];
|
|
String iname = readUTF8(cpIndex, c);
|
|
String idesc = readUTF8(cpIndex + 2, c);
|
|
if (opcode < Opcodes.INVOKEVIRTUAL) {
|
|
mv.visitFieldInsn(opcode, iowner, iname, idesc);
|
|
} else {
|
|
mv.visitMethodInsn(opcode, iowner, iname, idesc, itf);
|
|
}
|
|
if (opcode == Opcodes.INVOKEINTERFACE) {
|
|
u += 5;
|
|
} else {
|
|
u += 3;
|
|
}
|
|
break;
|
|
}
|
|
case ClassWriter.INDYMETH_INSN: {
|
|
int cpIndex = items[readUnsignedShort(u + 1)];
|
|
int bsmIndex = context.bootstrapMethods[readUnsignedShort(cpIndex)];
|
|
Handle bsm = (Handle) readConst(readUnsignedShort(bsmIndex), c);
|
|
int bsmArgCount = readUnsignedShort(bsmIndex + 2);
|
|
Object[] bsmArgs = new Object[bsmArgCount];
|
|
bsmIndex += 4;
|
|
for (int i = 0; i < bsmArgCount; i++) {
|
|
bsmArgs[i] = readConst(readUnsignedShort(bsmIndex), c);
|
|
bsmIndex += 2;
|
|
}
|
|
cpIndex = items[readUnsignedShort(cpIndex + 2)];
|
|
String iname = readUTF8(cpIndex, c);
|
|
String idesc = readUTF8(cpIndex + 2, c);
|
|
mv.visitInvokeDynamicInsn(iname, idesc, bsm, bsmArgs);
|
|
u += 5;
|
|
break;
|
|
}
|
|
case ClassWriter.TYPE_INSN:
|
|
mv.visitTypeInsn(opcode, readClass(u + 1, c));
|
|
u += 3;
|
|
break;
|
|
case ClassWriter.IINC_INSN:
|
|
mv.visitIincInsn(b[u + 1] & 0xFF, b[u + 2]);
|
|
u += 3;
|
|
break;
|
|
// case MANA_INSN:
|
|
default:
|
|
mv.visitMultiANewArrayInsn(readClass(u + 1, c), b[u + 3] & 0xFF);
|
|
u += 4;
|
|
break;
|
|
}
|
|
|
|
// visit the instruction annotations, if any
|
|
while (tanns != null && tann < tanns.length && ntoff <= offset) {
|
|
if (ntoff == offset) {
|
|
int v = readAnnotationTarget(context, tanns[tann]);
|
|
readAnnotationValues(v + 2, c, true,
|
|
mv.visitInsnAnnotation(context.typeRef,
|
|
context.typePath, readUTF8(v, c), true));
|
|
}
|
|
ntoff = ++tann >= tanns.length || readByte(tanns[tann]) < 0x43 ? -1
|
|
: readUnsignedShort(tanns[tann] + 1);
|
|
}
|
|
while (itanns != null && itann < itanns.length && nitoff <= offset) {
|
|
if (nitoff == offset) {
|
|
int v = readAnnotationTarget(context, itanns[itann]);
|
|
readAnnotationValues(v + 2, c, true,
|
|
mv.visitInsnAnnotation(context.typeRef,
|
|
context.typePath, readUTF8(v, c), false));
|
|
}
|
|
nitoff = ++itann >= itanns.length
|
|
|| readByte(itanns[itann]) < 0x43 ? -1
|
|
: readUnsignedShort(itanns[itann] + 1);
|
|
}
|
|
}
|
|
if (labels[codeLength] != null) {
|
|
mv.visitLabel(labels[codeLength]);
|
|
}
|
|
|
|
// visits the local variable tables
|
|
if ((context.flags & SKIP_DEBUG) == 0 && varTable != 0) {
|
|
int[] typeTable = null;
|
|
if (varTypeTable != 0) {
|
|
u = varTypeTable + 2;
|
|
typeTable = new int[readUnsignedShort(varTypeTable) * 3];
|
|
for (int i = typeTable.length; i > 0; ) {
|
|
typeTable[--i] = u + 6; // signature
|
|
typeTable[--i] = readUnsignedShort(u + 8); // index
|
|
typeTable[--i] = readUnsignedShort(u); // start
|
|
u += 10;
|
|
}
|
|
}
|
|
u = varTable + 2;
|
|
for (int i = readUnsignedShort(varTable); i > 0; --i) {
|
|
int start = readUnsignedShort(u);
|
|
int length = readUnsignedShort(u + 2);
|
|
int index = readUnsignedShort(u + 8);
|
|
String vsignature = null;
|
|
if (typeTable != null) {
|
|
for (int j = 0; j < typeTable.length; j += 3) {
|
|
if (typeTable[j] == start && typeTable[j + 1] == index) {
|
|
vsignature = readUTF8(typeTable[j + 2], c);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
mv.visitLocalVariable(readUTF8(u + 4, c), readUTF8(u + 6, c),
|
|
vsignature, labels[start], labels[start + length],
|
|
index);
|
|
u += 10;
|
|
}
|
|
}
|
|
|
|
// visits the local variables type annotations
|
|
if (tanns != null) {
|
|
for (int i = 0; i < tanns.length; ++i) {
|
|
if ((readByte(tanns[i]) >> 1) == (0x40 >> 1)) {
|
|
int v = readAnnotationTarget(context, tanns[i]);
|
|
v = readAnnotationValues(v + 2, c, true,
|
|
mv.visitLocalVariableAnnotation(context.typeRef,
|
|
context.typePath, context.start,
|
|
context.end, context.index, readUTF8(v, c),
|
|
true));
|
|
}
|
|
}
|
|
}
|
|
if (itanns != null) {
|
|
for (int i = 0; i < itanns.length; ++i) {
|
|
if ((readByte(itanns[i]) >> 1) == (0x40 >> 1)) {
|
|
int v = readAnnotationTarget(context, itanns[i]);
|
|
v = readAnnotationValues(v + 2, c, true,
|
|
mv.visitLocalVariableAnnotation(context.typeRef,
|
|
context.typePath, context.start,
|
|
context.end, context.index, readUTF8(v, c),
|
|
false));
|
|
}
|
|
}
|
|
}
|
|
|
|
// visits the code attributes
|
|
while (attributes != null) {
|
|
Attribute attr = attributes.next;
|
|
attributes.next = null;
|
|
mv.visitAttribute(attributes);
|
|
attributes = attr;
|
|
}
|
|
|
|
// visits the max stack and max locals values
|
|
mv.visitMaxs(maxStack, maxLocals);
|
|
}
|
|
|
|
/**
|
|
* Parses a type annotation table to find the labels, and to visit the try
|
|
* catch block annotations.
|
|
*
|
|
* @param u the start offset of a type annotation table.
|
|
* @param mv the method visitor to be used to visit the try catch block
|
|
* annotations.
|
|
* @param context information about the class being parsed.
|
|
* @param visible if the type annotation table to parse contains runtime visible
|
|
* annotations.
|
|
* @return the start offset of each type annotation in the parsed table.
|
|
*/
|
|
private int[] readTypeAnnotations(final MethodVisitor mv,
|
|
final Context context, int u, boolean visible) {
|
|
char[] c = context.buffer;
|
|
int[] offsets = new int[readUnsignedShort(u)];
|
|
u += 2;
|
|
for (int i = 0; i < offsets.length; ++i) {
|
|
offsets[i] = u;
|
|
int target = readInt(u);
|
|
switch (target >>> 24) {
|
|
case 0x00: // CLASS_TYPE_PARAMETER
|
|
case 0x01: // METHOD_TYPE_PARAMETER
|
|
case 0x16: // METHOD_FORMAL_PARAMETER
|
|
u += 2;
|
|
break;
|
|
case 0x13: // FIELD
|
|
case 0x14: // METHOD_RETURN
|
|
case 0x15: // METHOD_RECEIVER
|
|
u += 1;
|
|
break;
|
|
case 0x40: // LOCAL_VARIABLE
|
|
case 0x41: // RESOURCE_VARIABLE
|
|
for (int j = readUnsignedShort(u + 1); j > 0; --j) {
|
|
int start = readUnsignedShort(u + 3);
|
|
int length = readUnsignedShort(u + 5);
|
|
readLabel(start, context.labels);
|
|
readLabel(start + length, context.labels);
|
|
u += 6;
|
|
}
|
|
u += 3;
|
|
break;
|
|
case 0x47: // CAST
|
|
case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
|
|
case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT
|
|
case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
|
|
case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT
|
|
u += 4;
|
|
break;
|
|
// case 0x10: // CLASS_EXTENDS
|
|
// case 0x11: // CLASS_TYPE_PARAMETER_BOUND
|
|
// case 0x12: // METHOD_TYPE_PARAMETER_BOUND
|
|
// case 0x17: // THROWS
|
|
// case 0x42: // EXCEPTION_PARAMETER
|
|
// case 0x43: // INSTANCEOF
|
|
// case 0x44: // NEW
|
|
// case 0x45: // CONSTRUCTOR_REFERENCE
|
|
// case 0x46: // METHOD_REFERENCE
|
|
default:
|
|
u += 3;
|
|
break;
|
|
}
|
|
int pathLength = readByte(u);
|
|
if ((target >>> 24) == 0x42) {
|
|
TypePath path = pathLength == 0 ? null : new TypePath(b, u);
|
|
u += 1 + 2 * pathLength;
|
|
u = readAnnotationValues(u + 2, c, true,
|
|
mv.visitTryCatchAnnotation(target, path,
|
|
readUTF8(u, c), visible));
|
|
} else {
|
|
u = readAnnotationValues(u + 3 + 2 * pathLength, c, true, null);
|
|
}
|
|
}
|
|
return offsets;
|
|
}
|
|
|
|
/**
|
|
* Parses the header of a type annotation to extract its target_type and
|
|
* target_path (the result is stored in the given context), and returns the
|
|
* start offset of the rest of the type_annotation structure (i.e. the
|
|
* offset to the type_index field, which is followed by
|
|
* num_element_value_pairs and then the name,value pairs).
|
|
*
|
|
* @param context information about the class being parsed. This is where the
|
|
* extracted target_type and target_path must be stored.
|
|
* @param u the start offset of a type_annotation structure.
|
|
* @return the start offset of the rest of the type_annotation structure.
|
|
*/
|
|
private int readAnnotationTarget(final Context context, int u) {
|
|
int target = readInt(u);
|
|
switch (target >>> 24) {
|
|
case 0x00: // CLASS_TYPE_PARAMETER
|
|
case 0x01: // METHOD_TYPE_PARAMETER
|
|
case 0x16: // METHOD_FORMAL_PARAMETER
|
|
target &= 0xFFFF0000;
|
|
u += 2;
|
|
break;
|
|
case 0x13: // FIELD
|
|
case 0x14: // METHOD_RETURN
|
|
case 0x15: // METHOD_RECEIVER
|
|
target &= 0xFF000000;
|
|
u += 1;
|
|
break;
|
|
case 0x40: // LOCAL_VARIABLE
|
|
case 0x41: { // RESOURCE_VARIABLE
|
|
target &= 0xFF000000;
|
|
int n = readUnsignedShort(u + 1);
|
|
context.start = new Label[n];
|
|
context.end = new Label[n];
|
|
context.index = new int[n];
|
|
u += 3;
|
|
for (int i = 0; i < n; ++i) {
|
|
int start = readUnsignedShort(u);
|
|
int length = readUnsignedShort(u + 2);
|
|
context.start[i] = readLabel(start, context.labels);
|
|
context.end[i] = readLabel(start + length, context.labels);
|
|
context.index[i] = readUnsignedShort(u + 4);
|
|
u += 6;
|
|
}
|
|
break;
|
|
}
|
|
case 0x47: // CAST
|
|
case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
|
|
case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT
|
|
case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
|
|
case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT
|
|
target &= 0xFF0000FF;
|
|
u += 4;
|
|
break;
|
|
// case 0x10: // CLASS_EXTENDS
|
|
// case 0x11: // CLASS_TYPE_PARAMETER_BOUND
|
|
// case 0x12: // METHOD_TYPE_PARAMETER_BOUND
|
|
// case 0x17: // THROWS
|
|
// case 0x42: // EXCEPTION_PARAMETER
|
|
// case 0x43: // INSTANCEOF
|
|
// case 0x44: // NEW
|
|
// case 0x45: // CONSTRUCTOR_REFERENCE
|
|
// case 0x46: // METHOD_REFERENCE
|
|
default:
|
|
target &= (target >>> 24) < 0x43 ? 0xFFFFFF00 : 0xFF000000;
|
|
u += 3;
|
|
break;
|
|
}
|
|
int pathLength = readByte(u);
|
|
context.typeRef = target;
|
|
context.typePath = pathLength == 0 ? null : new TypePath(b, u);
|
|
return u + 1 + 2 * pathLength;
|
|
}
|
|
|
|
/**
|
|
* Reads parameter annotations and makes the given visitor visit them.
|
|
*
|
|
* @param mv the visitor that must visit the annotations.
|
|
* @param context information about the class being parsed.
|
|
* @param v start offset in {@link #b b} of the annotations to be read.
|
|
* @param visible <tt>true</tt> if the annotations to be read are visible at
|
|
* runtime.
|
|
*/
|
|
private void readParameterAnnotations(final MethodVisitor mv,
|
|
final Context context, int v, final boolean visible) {
|
|
int i;
|
|
int n = b[v++] & 0xFF;
|
|
// workaround for a bug in javac (javac compiler generates a parameter
|
|
// annotation array whose size is equal to the number of parameters in
|
|
// the Java source file, while it should generate an array whose size is
|
|
// equal to the number of parameters in the method descriptor - which
|
|
// includes the synthetic parameters added by the compiler). This work-
|
|
// around supposes that the synthetic parameters are the first ones.
|
|
int synthetics = Type.getArgumentTypes(context.desc).length - n;
|
|
AnnotationVisitor av;
|
|
for (i = 0; i < synthetics; ++i) {
|
|
// virtual annotation to detect synthetic parameters in MethodWriter
|
|
av = mv.visitParameterAnnotation(i, "Ljava/lang/Synthetic;", false);
|
|
if (av != null) {
|
|
av.visitEnd();
|
|
}
|
|
}
|
|
char[] c = context.buffer;
|
|
for (; i < n + synthetics; ++i) {
|
|
int j = readUnsignedShort(v);
|
|
v += 2;
|
|
for (; j > 0; --j) {
|
|
av = mv.visitParameterAnnotation(i, readUTF8(v, c), visible);
|
|
v = readAnnotationValues(v + 2, c, true, av);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads the values of an annotation and makes the given visitor visit them.
|
|
*
|
|
* @param v the start offset in {@link #b b} of the values to be read
|
|
* (including the unsigned short that gives the number of
|
|
* values).
|
|
* @param buf buffer to be used to call {@link #readUTF8 readUTF8},
|
|
* {@link #readClass(int, char[]) readClass} or {@link #readConst
|
|
* readConst}.
|
|
* @param named if the annotation values are named or not.
|
|
* @param av the visitor that must visit the values.
|
|
* @return the end offset of the annotation values.
|
|
*/
|
|
private int readAnnotationValues(int v, final char[] buf,
|
|
final boolean named, final AnnotationVisitor av) {
|
|
int i = readUnsignedShort(v);
|
|
v += 2;
|
|
if (named) {
|
|
for (; i > 0; --i) {
|
|
v = readAnnotationValue(v + 2, buf, readUTF8(v, buf), av);
|
|
}
|
|
} else {
|
|
for (; i > 0; --i) {
|
|
v = readAnnotationValue(v, buf, null, av);
|
|
}
|
|
}
|
|
if (av != null) {
|
|
av.visitEnd();
|
|
}
|
|
return v;
|
|
}
|
|
|
|
/**
|
|
* Reads a value of an annotation and makes the given visitor visit it.
|
|
*
|
|
* @param v the start offset in {@link #b b} of the value to be read
|
|
* (<i>not including the value name constant pool index</i>).
|
|
* @param buf buffer to be used to call {@link #readUTF8 readUTF8},
|
|
* {@link #readClass(int, char[]) readClass} or {@link #readConst
|
|
* readConst}.
|
|
* @param name the name of the value to be read.
|
|
* @param av the visitor that must visit the value.
|
|
* @return the end offset of the annotation value.
|
|
*/
|
|
private int readAnnotationValue(int v, final char[] buf, final String name,
|
|
final AnnotationVisitor av) {
|
|
int i;
|
|
if (av == null) {
|
|
switch (b[v] & 0xFF) {
|
|
case 'e': // enum_const_value
|
|
return v + 5;
|
|
case '@': // annotation_value
|
|
return readAnnotationValues(v + 3, buf, true, null);
|
|
case '[': // array_value
|
|
return readAnnotationValues(v + 1, buf, false, null);
|
|
default:
|
|
return v + 3;
|
|
}
|
|
}
|
|
switch (b[v++] & 0xFF) {
|
|
case 'I': // pointer to CONSTANT_Integer
|
|
case 'J': // pointer to CONSTANT_Long
|
|
case 'F': // pointer to CONSTANT_Float
|
|
case 'D': // pointer to CONSTANT_Double
|
|
av.visit(name, readConst(readUnsignedShort(v), buf));
|
|
v += 2;
|
|
break;
|
|
case 'B': // pointer to CONSTANT_Byte
|
|
av.visit(name, (byte) readInt(items[readUnsignedShort(v)]));
|
|
v += 2;
|
|
break;
|
|
case 'Z': // pointer to CONSTANT_Boolean
|
|
av.visit(name,
|
|
readInt(items[readUnsignedShort(v)]) == 0 ? Boolean.FALSE
|
|
: Boolean.TRUE);
|
|
v += 2;
|
|
break;
|
|
case 'S': // pointer to CONSTANT_Short
|
|
av.visit(name, (short) readInt(items[readUnsignedShort(v)]));
|
|
v += 2;
|
|
break;
|
|
case 'C': // pointer to CONSTANT_Char
|
|
av.visit(name, (char) readInt(items[readUnsignedShort(v)]));
|
|
v += 2;
|
|
break;
|
|
case 's': // pointer to CONSTANT_Utf8
|
|
av.visit(name, readUTF8(v, buf));
|
|
v += 2;
|
|
break;
|
|
case 'e': // enum_const_value
|
|
av.visitEnum(name, readUTF8(v, buf), readUTF8(v + 2, buf));
|
|
v += 4;
|
|
break;
|
|
case 'c': // class_info
|
|
av.visit(name, Type.getType(readUTF8(v, buf)));
|
|
v += 2;
|
|
break;
|
|
case '@': // annotation_value
|
|
v = readAnnotationValues(v + 2, buf, true,
|
|
av.visitAnnotation(name, readUTF8(v, buf)));
|
|
break;
|
|
case '[': // array_value
|
|
int size = readUnsignedShort(v);
|
|
v += 2;
|
|
if (size == 0) {
|
|
return readAnnotationValues(v - 2, buf, false,
|
|
av.visitArray(name));
|
|
}
|
|
switch (this.b[v++] & 0xFF) {
|
|
case 'B':
|
|
byte[] bv = new byte[size];
|
|
for (i = 0; i < size; i++) {
|
|
bv[i] = (byte) readInt(items[readUnsignedShort(v)]);
|
|
v += 3;
|
|
}
|
|
av.visit(name, bv);
|
|
--v;
|
|
break;
|
|
case 'Z':
|
|
boolean[] zv = new boolean[size];
|
|
for (i = 0; i < size; i++) {
|
|
zv[i] = readInt(items[readUnsignedShort(v)]) != 0;
|
|
v += 3;
|
|
}
|
|
av.visit(name, zv);
|
|
--v;
|
|
break;
|
|
case 'S':
|
|
short[] sv = new short[size];
|
|
for (i = 0; i < size; i++) {
|
|
sv[i] = (short) readInt(items[readUnsignedShort(v)]);
|
|
v += 3;
|
|
}
|
|
av.visit(name, sv);
|
|
--v;
|
|
break;
|
|
case 'C':
|
|
char[] cv = new char[size];
|
|
for (i = 0; i < size; i++) {
|
|
cv[i] = (char) readInt(items[readUnsignedShort(v)]);
|
|
v += 3;
|
|
}
|
|
av.visit(name, cv);
|
|
--v;
|
|
break;
|
|
case 'I':
|
|
int[] iv = new int[size];
|
|
for (i = 0; i < size; i++) {
|
|
iv[i] = readInt(items[readUnsignedShort(v)]);
|
|
v += 3;
|
|
}
|
|
av.visit(name, iv);
|
|
--v;
|
|
break;
|
|
case 'J':
|
|
long[] lv = new long[size];
|
|
for (i = 0; i < size; i++) {
|
|
lv[i] = readLong(items[readUnsignedShort(v)]);
|
|
v += 3;
|
|
}
|
|
av.visit(name, lv);
|
|
--v;
|
|
break;
|
|
case 'F':
|
|
float[] fv = new float[size];
|
|
for (i = 0; i < size; i++) {
|
|
fv[i] = Float
|
|
.intBitsToFloat(readInt(items[readUnsignedShort(v)]));
|
|
v += 3;
|
|
}
|
|
av.visit(name, fv);
|
|
--v;
|
|
break;
|
|
case 'D':
|
|
double[] dv = new double[size];
|
|
for (i = 0; i < size; i++) {
|
|
dv[i] = Double
|
|
.longBitsToDouble(readLong(items[readUnsignedShort(v)]));
|
|
v += 3;
|
|
}
|
|
av.visit(name, dv);
|
|
--v;
|
|
break;
|
|
default:
|
|
v = readAnnotationValues(v - 3, buf, false, av.visitArray(name));
|
|
}
|
|
}
|
|
return v;
|
|
}
|
|
|
|
/**
|
|
* 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 frame information about the class being parsed.
|
|
*/
|
|
private void getImplicitFrame(final Context frame) {
|
|
String desc = frame.desc;
|
|
Object[] locals = frame.local;
|
|
int local = 0;
|
|
if ((frame.access & Opcodes.ACC_STATIC) == 0) {
|
|
if ("<init>".equals(frame.name)) {
|
|
locals[local++] = Opcodes.UNINITIALIZED_THIS;
|
|
} else {
|
|
locals[local++] = readClass(header + 2, frame.buffer);
|
|
}
|
|
}
|
|
int i = 1;
|
|
loop:
|
|
while (true) {
|
|
int j = i;
|
|
switch (desc.charAt(i++)) {
|
|
case 'Z':
|
|
case 'C':
|
|
case 'B':
|
|
case 'S':
|
|
case 'I':
|
|
locals[local++] = Opcodes.INTEGER;
|
|
break;
|
|
case 'F':
|
|
locals[local++] = Opcodes.FLOAT;
|
|
break;
|
|
case 'J':
|
|
locals[local++] = Opcodes.LONG;
|
|
break;
|
|
case 'D':
|
|
locals[local++] = Opcodes.DOUBLE;
|
|
break;
|
|
case '[':
|
|
while (desc.charAt(i) == '[') {
|
|
++i;
|
|
}
|
|
if (desc.charAt(i) == 'L') {
|
|
++i;
|
|
while (desc.charAt(i) != ';') {
|
|
++i;
|
|
}
|
|
}
|
|
locals[local++] = desc.substring(j, ++i);
|
|
break;
|
|
case 'L':
|
|
while (desc.charAt(i) != ';') {
|
|
++i;
|
|
}
|
|
locals[local++] = desc.substring(j + 1, i++);
|
|
break;
|
|
default:
|
|
break loop;
|
|
}
|
|
}
|
|
frame.localCount = local;
|
|
}
|
|
|
|
/**
|
|
* Reads a stack map frame and stores the result in the given
|
|
* {@link Context} object.
|
|
*
|
|
* @param stackMap the start offset of a stack map frame in the class file.
|
|
* @param zip if the stack map frame at stackMap is compressed or not.
|
|
* @param unzip if the stack map frame must be uncompressed.
|
|
* @param frame where the parsed stack map frame must be stored.
|
|
* @return the offset of the first byte following the parsed frame.
|
|
*/
|
|
private int readFrame(int stackMap, boolean zip, boolean unzip,
|
|
Context frame) {
|
|
char[] c = frame.buffer;
|
|
Label[] labels = frame.labels;
|
|
int tag;
|
|
int delta;
|
|
if (zip) {
|
|
tag = b[stackMap++] & 0xFF;
|
|
} else {
|
|
tag = MethodWriter.FULL_FRAME;
|
|
frame.offset = -1;
|
|
}
|
|
frame.localDiff = 0;
|
|
if (tag < MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME) {
|
|
delta = tag;
|
|
frame.mode = Opcodes.F_SAME;
|
|
frame.stackCount = 0;
|
|
} else if (tag < MethodWriter.RESERVED) {
|
|
delta = tag - MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME;
|
|
stackMap = readFrameType(frame.stack, 0, stackMap, c, labels);
|
|
frame.mode = Opcodes.F_SAME1;
|
|
frame.stackCount = 1;
|
|
} else {
|
|
delta = readUnsignedShort(stackMap);
|
|
stackMap += 2;
|
|
if (tag == MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
|
|
stackMap = readFrameType(frame.stack, 0, stackMap, c, labels);
|
|
frame.mode = Opcodes.F_SAME1;
|
|
frame.stackCount = 1;
|
|
} else if (tag >= MethodWriter.CHOP_FRAME
|
|
&& tag < MethodWriter.SAME_FRAME_EXTENDED) {
|
|
frame.mode = Opcodes.F_CHOP;
|
|
frame.localDiff = MethodWriter.SAME_FRAME_EXTENDED - tag;
|
|
frame.localCount -= frame.localDiff;
|
|
frame.stackCount = 0;
|
|
} else if (tag == MethodWriter.SAME_FRAME_EXTENDED) {
|
|
frame.mode = Opcodes.F_SAME;
|
|
frame.stackCount = 0;
|
|
} else if (tag < MethodWriter.FULL_FRAME) {
|
|
int local = unzip ? frame.localCount : 0;
|
|
for (int i = tag - MethodWriter.SAME_FRAME_EXTENDED; i > 0; i--) {
|
|
stackMap = readFrameType(frame.local, local++, stackMap, c,
|
|
labels);
|
|
}
|
|
frame.mode = Opcodes.F_APPEND;
|
|
frame.localDiff = tag - MethodWriter.SAME_FRAME_EXTENDED;
|
|
frame.localCount += frame.localDiff;
|
|
frame.stackCount = 0;
|
|
} else { // if (tag == FULL_FRAME) {
|
|
frame.mode = Opcodes.F_FULL;
|
|
int n = readUnsignedShort(stackMap);
|
|
stackMap += 2;
|
|
frame.localDiff = n;
|
|
frame.localCount = n;
|
|
for (int local = 0; n > 0; n--) {
|
|
stackMap = readFrameType(frame.local, local++, stackMap, c,
|
|
labels);
|
|
}
|
|
n = readUnsignedShort(stackMap);
|
|
stackMap += 2;
|
|
frame.stackCount = n;
|
|
for (int stack = 0; n > 0; n--) {
|
|
stackMap = readFrameType(frame.stack, stack++, stackMap, c,
|
|
labels);
|
|
}
|
|
}
|
|
}
|
|
frame.offset += delta + 1;
|
|
readLabel(frame.offset, labels);
|
|
return stackMap;
|
|
}
|
|
|
|
/**
|
|
* Reads a stack map frame type and stores it at the given index in the
|
|
* given array.
|
|
*
|
|
* @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 v the start offset of the stack map frame type to read.
|
|
* @param buf a buffer to read strings.
|
|
* @param labels the labels of the method currently being parsed, indexed by
|
|
* their offset. If the parsed type is an Uninitialized type, a
|
|
* new label for the corresponding NEW instruction is stored in
|
|
* this array if it does not already exist.
|
|
* @return the offset of the first byte after the parsed type.
|
|
*/
|
|
private int readFrameType(final Object[] frame, final int index, int v,
|
|
final char[] buf, final Label[] labels) {
|
|
int type = b[v++] & 0xFF;
|
|
switch (type) {
|
|
case 0:
|
|
frame[index] = Opcodes.TOP;
|
|
break;
|
|
case 1:
|
|
frame[index] = Opcodes.INTEGER;
|
|
break;
|
|
case 2:
|
|
frame[index] = Opcodes.FLOAT;
|
|
break;
|
|
case 3:
|
|
frame[index] = Opcodes.DOUBLE;
|
|
break;
|
|
case 4:
|
|
frame[index] = Opcodes.LONG;
|
|
break;
|
|
case 5:
|
|
frame[index] = Opcodes.NULL;
|
|
break;
|
|
case 6:
|
|
frame[index] = Opcodes.UNINITIALIZED_THIS;
|
|
break;
|
|
case 7: // Object
|
|
frame[index] = readClass(v, buf);
|
|
v += 2;
|
|
break;
|
|
default: // Uninitialized
|
|
frame[index] = readLabel(readUnsignedShort(v), labels);
|
|
v += 2;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
/**
|
|
* Returns the label corresponding to the given offset. The default
|
|
* implementation of this method creates a label for the given offset if it
|
|
* has not been already created.
|
|
*
|
|
* @param offset a bytecode offset in a method.
|
|
* @param labels the already created labels, indexed by their offset. If a
|
|
* label already exists for offset 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[offset].
|
|
*/
|
|
protected Label readLabel(int offset, Label[] labels) {
|
|
if (labels[offset] == null) {
|
|
labels[offset] = new Label();
|
|
}
|
|
return labels[offset];
|
|
}
|
|
|
|
/**
|
|
* Returns the start index of the attribute_info structure of this class.
|
|
*
|
|
* @return the start index of the attribute_info structure of this class.
|
|
*/
|
|
private int getAttributes() {
|
|
// skips the header
|
|
int u = header + 8 + readUnsignedShort(header + 6) * 2;
|
|
// skips fields and methods
|
|
for (int i = readUnsignedShort(u); i > 0; --i) {
|
|
for (int j = readUnsignedShort(u + 8); j > 0; --j) {
|
|
u += 6 + readInt(u + 12);
|
|
}
|
|
u += 8;
|
|
}
|
|
u += 2;
|
|
for (int i = readUnsignedShort(u); i > 0; --i) {
|
|
for (int j = readUnsignedShort(u + 8); j > 0; --j) {
|
|
u += 6 + readInt(u + 12);
|
|
}
|
|
u += 8;
|
|
}
|
|
// the attribute_info structure starts just after the methods
|
|
return u + 2;
|
|
}
|
|
|
|
/**
|
|
* Reads an attribute in {@link #b b}.
|
|
*
|
|
* @param attrs 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 is ignored (i.e. an empty
|
|
* {@link Attribute} instance is returned).
|
|
* @param type the type of the attribute.
|
|
* @param off index of the first byte of the attribute's content in
|
|
* {@link #b b}. The 6 attribute header bytes, containing the
|
|
* type and the length of the attribute, are not taken into
|
|
* account here (they have already been read).
|
|
* @param len the length of the attribute's content.
|
|
* @param buf buffer to be used to call {@link #readUTF8 readUTF8},
|
|
* {@link #readClass(int, char[]) readClass} or {@link #readConst
|
|
* readConst}.
|
|
* @param codeOff index of the first byte of code's attribute content in
|
|
* {@link #b b}, or -1 if the attribute to be read is not a code
|
|
* attribute. The 6 attribute header bytes, containing the type
|
|
* and the length of the attribute, are not taken into account
|
|
* here.
|
|
* @param labels the labels of the method's code, or <tt>null</tt> if the
|
|
* attribute to be read is not a code attribute.
|
|
* @return the attribute that has been read, or <tt>null</tt> to skip this
|
|
* attribute.
|
|
*/
|
|
private Attribute readAttribute(final Attribute[] attrs, final String type,
|
|
final int off, final int len, final char[] buf, final int codeOff,
|
|
final Label[] labels) {
|
|
for (int i = 0; i < attrs.length; ++i) {
|
|
if (attrs[i].type.equals(type)) {
|
|
return attrs[i].read(this, off, len, buf, codeOff, labels);
|
|
}
|
|
}
|
|
return new Attribute(type).read(this, off, len, null, -1, null);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Utility methods: low level parsing
|
|
// ------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Returns the number of constant pool items in {@link #b b}.
|
|
*
|
|
* @return the number of constant pool items in {@link #b b}.
|
|
*/
|
|
public int getItemCount() {
|
|
return items.length;
|
|
}
|
|
|
|
/**
|
|
* Returns the start index of the constant pool item in {@link #b b}, plus
|
|
* one. <i>This method is intended for {@link Attribute} sub classes, and is
|
|
* normally not needed by class generators or adapters.</i>
|
|
*
|
|
* @param item the index a constant pool item.
|
|
* @return the start index of the constant pool item in {@link #b b}, plus
|
|
* one.
|
|
*/
|
|
public int getItem(final int item) {
|
|
return items[item];
|
|
}
|
|
|
|
/**
|
|
* Returns the maximum length of the strings contained in the constant pool
|
|
* of the class.
|
|
*
|
|
* @return the maximum length of the strings contained in the constant pool
|
|
* of the class.
|
|
*/
|
|
public int getMaxStringLength() {
|
|
return maxStringLength;
|
|
}
|
|
|
|
/**
|
|
* Reads a byte value in {@link #b b}. <i>This method is intended for
|
|
* {@link Attribute} sub classes, and is normally not needed by class
|
|
* generators or adapters.</i>
|
|
*
|
|
* @param index the start index of the value to be read in {@link #b b}.
|
|
* @return the read value.
|
|
*/
|
|
public int readByte(final int index) {
|
|
return b[index] & 0xFF;
|
|
}
|
|
|
|
/**
|
|
* Reads an unsigned short value in {@link #b b}. <i>This method is intended
|
|
* for {@link Attribute} sub classes, and is normally not needed by class
|
|
* generators or adapters.</i>
|
|
*
|
|
* @param index the start index of the value to be read in {@link #b b}.
|
|
* @return the read value.
|
|
*/
|
|
public int readUnsignedShort(final int index) {
|
|
byte[] b = this.b;
|
|
return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
|
|
}
|
|
|
|
/**
|
|
* Reads a signed short value in {@link #b b}. <i>This method is intended
|
|
* for {@link Attribute} sub classes, and is normally not needed by class
|
|
* generators or adapters.</i>
|
|
*
|
|
* @param index the start index of the value to be read in {@link #b b}.
|
|
* @return the read value.
|
|
*/
|
|
public short readShort(final int index) {
|
|
byte[] b = this.b;
|
|
return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF));
|
|
}
|
|
|
|
/**
|
|
* Reads a signed int value in {@link #b b}. <i>This method is intended for
|
|
* {@link Attribute} sub classes, and is normally not needed by class
|
|
* generators or adapters.</i>
|
|
*
|
|
* @param index the start index of the value to be read in {@link #b b}.
|
|
* @return the read value.
|
|
*/
|
|
public int readInt(final int index) {
|
|
byte[] b = this.b;
|
|
return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16)
|
|
| ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF);
|
|
}
|
|
|
|
/**
|
|
* Reads a signed long value in {@link #b b}. <i>This method is intended for
|
|
* {@link Attribute} sub classes, and is normally not needed by class
|
|
* generators or adapters.</i>
|
|
*
|
|
* @param index the start index of the value to be read in {@link #b b}.
|
|
* @return the read value.
|
|
*/
|
|
public long readLong(final int index) {
|
|
long l1 = readInt(index);
|
|
long l0 = readInt(index + 4) & 0xFFFFFFFFL;
|
|
return (l1 << 32) | l0;
|
|
}
|
|
|
|
/**
|
|
* Reads an UTF8 string constant pool item in {@link #b b}. <i>This method
|
|
* is intended for {@link Attribute} sub classes, and is normally not needed
|
|
* by class generators or adapters.</i>
|
|
*
|
|
* @param index the start index of an unsigned short value in {@link #b b},
|
|
* whose value is the index of an UTF8 constant pool item.
|
|
* @param buf 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 UTF8 item.
|
|
*/
|
|
public String readUTF8(int index, final char[] buf) {
|
|
int item = readUnsignedShort(index);
|
|
if (index == 0 || item == 0) {
|
|
return null;
|
|
}
|
|
String s = strings[item];
|
|
if (s != null) {
|
|
return s;
|
|
}
|
|
index = items[item];
|
|
return strings[item] = readUTF(index + 2, readUnsignedShort(index), buf);
|
|
}
|
|
|
|
/**
|
|
* Reads UTF8 string in {@link #b b}.
|
|
*
|
|
* @param index start offset of the UTF8 string to be read.
|
|
* @param utfLen length of the UTF8 string to be read.
|
|
* @param buf 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(int index, final int utfLen, final char[] buf) {
|
|
int endIndex = index + utfLen;
|
|
byte[] b = this.b;
|
|
int strLen = 0;
|
|
int c;
|
|
int st = 0;
|
|
char cc = 0;
|
|
while (index < endIndex) {
|
|
c = b[index++];
|
|
switch (st) {
|
|
case 0:
|
|
c = c & 0xFF;
|
|
if (c < 0x80) { // 0xxxxxxx
|
|
buf[strLen++] = (char) c;
|
|
} else if (c < 0xE0 && c > 0xBF) { // 110x xxxx 10xx xxxx
|
|
cc = (char) (c & 0x1F);
|
|
st = 1;
|
|
} else { // 1110 xxxx 10xx xxxx 10xx xxxx
|
|
cc = (char) (c & 0x0F);
|
|
st = 2;
|
|
}
|
|
break;
|
|
|
|
case 1: // byte 2 of 2-byte char or byte 3 of 3-byte char
|
|
buf[strLen++] = (char) ((cc << 6) | (c & 0x3F));
|
|
st = 0;
|
|
break;
|
|
|
|
case 2: // byte 2 of 3-byte char
|
|
cc = (char) ((cc << 6) | (c & 0x3F));
|
|
st = 1;
|
|
break;
|
|
}
|
|
}
|
|
return new String(buf, 0, strLen);
|
|
}
|
|
|
|
/**
|
|
* Reads a class constant pool item in {@link #b b}. <i>This method is
|
|
* intended for {@link Attribute} sub classes, and is normally not needed by
|
|
* class generators or adapters.</i>
|
|
*
|
|
* @param index the start index of an unsigned short value in {@link #b b},
|
|
* whose value is the index of a class constant pool item.
|
|
* @param buf 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 class item.
|
|
*/
|
|
public String readClass(final int index, final char[] buf) {
|
|
// computes the start index of the CONSTANT_Class item in b
|
|
// and reads the CONSTANT_Utf8 item designated by
|
|
// the first two bytes of this CONSTANT_Class item
|
|
return readUTF8(items[readUnsignedShort(index)], buf);
|
|
}
|
|
|
|
/**
|
|
* Reads a numeric or string constant pool item in {@link #b b}. <i>This
|
|
* method is intended for {@link Attribute} sub classes, and is normally not
|
|
* needed by class generators or adapters.</i>
|
|
*
|
|
* @param item the index of a constant pool item.
|
|
* @param buf buffer to be used to read the item. 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} or {@link Handle} corresponding to
|
|
* the given constant pool item.
|
|
*/
|
|
public Object readConst(final int item, final char[] buf) {
|
|
int index = items[item];
|
|
switch (b[index - 1]) {
|
|
case ClassWriter.INT:
|
|
return readInt(index);
|
|
case ClassWriter.FLOAT:
|
|
return Float.intBitsToFloat(readInt(index));
|
|
case ClassWriter.LONG:
|
|
return readLong(index);
|
|
case ClassWriter.DOUBLE:
|
|
return Double.longBitsToDouble(readLong(index));
|
|
case ClassWriter.CLASS:
|
|
return Type.getObjectType(readUTF8(index, buf));
|
|
case ClassWriter.STR:
|
|
return readUTF8(index, buf);
|
|
case ClassWriter.MTYPE:
|
|
return Type.getMethodType(readUTF8(index, buf));
|
|
default: // case ClassWriter.HANDLE_BASE + [1..9]:
|
|
int tag = readByte(index);
|
|
int[] items = this.items;
|
|
int cpIndex = items[readUnsignedShort(index + 1)];
|
|
String owner = readClass(cpIndex, buf);
|
|
cpIndex = items[readUnsignedShort(cpIndex + 2)];
|
|
String name = readUTF8(cpIndex, buf);
|
|
String desc = readUTF8(cpIndex + 2, buf);
|
|
return new Handle(tag, owner, name, desc);
|
|
}
|
|
}
|
|
}
|