/*** | |
* ASM: a very small and fast Java bytecode manipulation framework | |
* Copyright (c) 2000-2007 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.mockito.asm; | |
import java.io.InputStream; | |
import java.io.IOException; | |
/** | |
* 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; | |
// 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: | |
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.CLASS: | |
// case ClassWriter.STR: | |
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() { | |
int n = items[readUnsignedShort(header + 4)]; | |
return n == 0 ? null : readUTF8(n, 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.STR: | |
// case ClassWriter.CLASS: | |
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; | |
} | |
/** | |
* 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)); | |
} | |
/** | |
* Constructs a new {@link ClassReader} object. | |
* | |
* @param name the fully qualified name of the class to be read. | |
* @throws IOException if an exception occurs during reading. | |
*/ | |
public ClassReader(final String name) throws IOException { | |
this(ClassLoader.getSystemResourceAsStream(name.replace('.', '/') | |
+ ".class")); | |
} | |
/** | |
* Reads the bytecode of a class. | |
* | |
* @param is an input stream from which to read the class. | |
* @return the bytecode read from the given input stream. | |
* @throws IOException if a problem occurs during reading. | |
*/ | |
private static byte[] readClass(final InputStream is) throws IOException { | |
if (is == null) { | |
throw new IOException("Class not found"); | |
} | |
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) { | |
byte[] c = new byte[b.length + 1000]; | |
System.arraycopy(b, 0, c, 0, len); | |
b = c; | |
} | |
} | |
} | |
// ------------------------------------------------------------------------ | |
// 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) | |
{ | |
byte[] b = this.b; // the bytecode array | |
char[] c = new char[maxStringLength]; // buffer used to read strings | |
int i, j, k; // loop variables | |
int u, v, w; // indexes in b | |
Attribute attr; | |
int access; | |
String name; | |
String desc; | |
String attrName; | |
String signature; | |
int anns = 0; | |
int ianns = 0; | |
Attribute cattrs = null; | |
// visits the header | |
u = header; | |
access = readUnsignedShort(u); | |
name = readClass(u + 2, c); | |
v = items[readUnsignedShort(u + 4)]; | |
String superClassName = v == 0 ? null : readUTF8(v, c); | |
String[] implementedItfs = new String[readUnsignedShort(u + 6)]; | |
w = 0; | |
u += 8; | |
for (i = 0; i < implementedItfs.length; ++i) { | |
implementedItfs[i] = readClass(u, c); | |
u += 2; | |
} | |
boolean skipCode = (flags & SKIP_CODE) != 0; | |
boolean skipDebug = (flags & SKIP_DEBUG) != 0; | |
boolean unzip = (flags & EXPAND_FRAMES) != 0; | |
// skips fields and methods | |
v = u; | |
i = readUnsignedShort(v); | |
v += 2; | |
for (; i > 0; --i) { | |
j = readUnsignedShort(v + 6); | |
v += 8; | |
for (; j > 0; --j) { | |
v += 6 + readInt(v + 2); | |
} | |
} | |
i = readUnsignedShort(v); | |
v += 2; | |
for (; i > 0; --i) { | |
j = readUnsignedShort(v + 6); | |
v += 8; | |
for (; j > 0; --j) { | |
v += 6 + readInt(v + 2); | |
} | |
} | |
// reads the class's attributes | |
signature = null; | |
String sourceFile = null; | |
String sourceDebug = null; | |
String enclosingOwner = null; | |
String enclosingName = null; | |
String enclosingDesc = null; | |
i = readUnsignedShort(v); | |
v += 2; | |
for (; i > 0; --i) { | |
attrName = readUTF8(v, c); | |
// tests are sorted in decreasing frequency order | |
// (based on frequencies observed on typical classes) | |
if ("SourceFile".equals(attrName)) { | |
sourceFile = readUTF8(v + 6, c); | |
} else if ("InnerClasses".equals(attrName)) { | |
w = v + 6; | |
} else if ("EnclosingMethod".equals(attrName)) { | |
enclosingOwner = readClass(v + 6, c); | |
int item = readUnsignedShort(v + 8); | |
if (item != 0) { | |
enclosingName = readUTF8(items[item], c); | |
enclosingDesc = readUTF8(items[item] + 2, c); | |
} | |
} else if (SIGNATURES && "Signature".equals(attrName)) { | |
signature = readUTF8(v + 6, c); | |
} else if (ANNOTATIONS && "RuntimeVisibleAnnotations".equals(attrName)) { | |
anns = v + 6; | |
} else if ("Deprecated".equals(attrName)) { | |
access |= Opcodes.ACC_DEPRECATED; | |
} else if ("Synthetic".equals(attrName)) { | |
access |= Opcodes.ACC_SYNTHETIC; | |
} else if ("SourceDebugExtension".equals(attrName)) { | |
int len = readInt(v + 2); | |
sourceDebug = readUTF(v + 6, len, new char[len]); | |
} else if (ANNOTATIONS && "RuntimeInvisibleAnnotations".equals(attrName)) { | |
ianns = v + 6; | |
} else { | |
attr = readAttribute(attrs, | |
attrName, | |
v + 6, | |
readInt(v + 2), | |
c, | |
-1, | |
null); | |
if (attr != null) { | |
attr.next = cattrs; | |
cattrs = attr; | |
} | |
} | |
v += 6 + readInt(v + 2); | |
} | |
// calls the visit method | |
classVisitor.visit(readInt(4), | |
access, | |
name, | |
signature, | |
superClassName, | |
implementedItfs); | |
// calls the visitSource method | |
if (!skipDebug && (sourceFile != null || sourceDebug != null)) { | |
classVisitor.visitSource(sourceFile, sourceDebug); | |
} | |
// calls the visitOuterClass method | |
if (enclosingOwner != null) { | |
classVisitor.visitOuterClass(enclosingOwner, | |
enclosingName, | |
enclosingDesc); | |
} | |
// visits the class annotations | |
if (ANNOTATIONS) { | |
for (i = 1; i >= 0; --i) { | |
v = i == 0 ? ianns : anns; | |
if (v != 0) { | |
j = readUnsignedShort(v); | |
v += 2; | |
for (; j > 0; --j) { | |
v = readAnnotationValues(v + 2, | |
c, | |
true, | |
classVisitor.visitAnnotation(readUTF8(v, c), i != 0)); | |
} | |
} | |
} | |
} | |
// visits the class attributes | |
while (cattrs != null) { | |
attr = cattrs.next; | |
cattrs.next = null; | |
classVisitor.visitAttribute(cattrs); | |
cattrs = attr; | |
} | |
// calls the visitInnerClass method | |
if (w != 0) { | |
i = readUnsignedShort(w); | |
w += 2; | |
for (; i > 0; --i) { | |
classVisitor.visitInnerClass(readUnsignedShort(w) == 0 | |
? null | |
: readClass(w, c), readUnsignedShort(w + 2) == 0 | |
? null | |
: readClass(w + 2, c), readUnsignedShort(w + 4) == 0 | |
? null | |
: readUTF8(w + 4, c), readUnsignedShort(w + 6)); | |
w += 8; | |
} | |
} | |
// visits the fields | |
i = readUnsignedShort(u); | |
u += 2; | |
for (; i > 0; --i) { | |
access = readUnsignedShort(u); | |
name = readUTF8(u + 2, c); | |
desc = readUTF8(u + 4, c); | |
// visits the field's attributes and looks for a ConstantValue | |
// attribute | |
int fieldValueItem = 0; | |
signature = null; | |
anns = 0; | |
ianns = 0; | |
cattrs = null; | |
j = readUnsignedShort(u + 6); | |
u += 8; | |
for (; j > 0; --j) { | |
attrName = readUTF8(u, c); | |
// tests are sorted in decreasing frequency order | |
// (based on frequencies observed on typical classes) | |
if ("ConstantValue".equals(attrName)) { | |
fieldValueItem = readUnsignedShort(u + 6); | |
} else if (SIGNATURES && "Signature".equals(attrName)) { | |
signature = readUTF8(u + 6, c); | |
} else if ("Deprecated".equals(attrName)) { | |
access |= Opcodes.ACC_DEPRECATED; | |
} else if ("Synthetic".equals(attrName)) { | |
access |= Opcodes.ACC_SYNTHETIC; | |
} else if (ANNOTATIONS && "RuntimeVisibleAnnotations".equals(attrName)) { | |
anns = u + 6; | |
} else if (ANNOTATIONS && "RuntimeInvisibleAnnotations".equals(attrName)) { | |
ianns = u + 6; | |
} else { | |
attr = readAttribute(attrs, | |
attrName, | |
u + 6, | |
readInt(u + 2), | |
c, | |
-1, | |
null); | |
if (attr != null) { | |
attr.next = cattrs; | |
cattrs = attr; | |
} | |
} | |
u += 6 + readInt(u + 2); | |
} | |
// visits the field | |
FieldVisitor fv = classVisitor.visitField(access, | |
name, | |
desc, | |
signature, | |
fieldValueItem == 0 ? null : readConst(fieldValueItem, c)); | |
// visits the field annotations and attributes | |
if (fv != null) { | |
if (ANNOTATIONS) { | |
for (j = 1; j >= 0; --j) { | |
v = j == 0 ? ianns : anns; | |
if (v != 0) { | |
k = readUnsignedShort(v); | |
v += 2; | |
for (; k > 0; --k) { | |
v = readAnnotationValues(v + 2, | |
c, | |
true, | |
fv.visitAnnotation(readUTF8(v, c), j != 0)); | |
} | |
} | |
} | |
} | |
while (cattrs != null) { | |
attr = cattrs.next; | |
cattrs.next = null; | |
fv.visitAttribute(cattrs); | |
cattrs = attr; | |
} | |
fv.visitEnd(); | |
} | |
} | |
// visits the methods | |
i = readUnsignedShort(u); | |
u += 2; | |
for (; i > 0; --i) { | |
int u0 = u + 6; | |
access = readUnsignedShort(u); | |
name = readUTF8(u + 2, c); | |
desc = readUTF8(u + 4, c); | |
signature = null; | |
anns = 0; | |
ianns = 0; | |
int dann = 0; | |
int mpanns = 0; | |
int impanns = 0; | |
cattrs = null; | |
v = 0; | |
w = 0; | |
// looks for Code and Exceptions attributes | |
j = readUnsignedShort(u + 6); | |
u += 8; | |
for (; j > 0; --j) { | |
attrName = readUTF8(u, c); | |
int attrSize = readInt(u + 2); | |
u += 6; | |
// tests are sorted in decreasing frequency order | |
// (based on frequencies observed on typical classes) | |
if ("Code".equals(attrName)) { | |
if (!skipCode) { | |
v = u; | |
} | |
} else if ("Exceptions".equals(attrName)) { | |
w = u; | |
} else if (SIGNATURES && "Signature".equals(attrName)) { | |
signature = readUTF8(u, c); | |
} else if ("Deprecated".equals(attrName)) { | |
access |= Opcodes.ACC_DEPRECATED; | |
} else if (ANNOTATIONS && "RuntimeVisibleAnnotations".equals(attrName)) { | |
anns = u; | |
} else if (ANNOTATIONS && "AnnotationDefault".equals(attrName)) { | |
dann = u; | |
} else if ("Synthetic".equals(attrName)) { | |
access |= Opcodes.ACC_SYNTHETIC; | |
} else if (ANNOTATIONS && "RuntimeInvisibleAnnotations".equals(attrName)) { | |
ianns = u; | |
} else if (ANNOTATIONS && "RuntimeVisibleParameterAnnotations".equals(attrName)) | |
{ | |
mpanns = u; | |
} else if (ANNOTATIONS && "RuntimeInvisibleParameterAnnotations".equals(attrName)) | |
{ | |
impanns = u; | |
} else { | |
attr = readAttribute(attrs, | |
attrName, | |
u, | |
attrSize, | |
c, | |
-1, | |
null); | |
if (attr != null) { | |
attr.next = cattrs; | |
cattrs = attr; | |
} | |
} | |
u += attrSize; | |
} | |
// reads declared exceptions | |
String[] exceptions; | |
if (w == 0) { | |
exceptions = null; | |
} else { | |
exceptions = new String[readUnsignedShort(w)]; | |
w += 2; | |
for (j = 0; j < exceptions.length; ++j) { | |
exceptions[j] = readClass(w, c); | |
w += 2; | |
} | |
} | |
// visits the method's code, if any | |
MethodVisitor mv = classVisitor.visitMethod(access, | |
name, | |
desc, | |
signature, | |
exceptions); | |
if (mv != null) { | |
/* | |
* 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) { | |
if (signature == mw.signature) { | |
boolean sameExceptions = false; | |
if (exceptions == null) { | |
sameExceptions = mw.exceptionCount == 0; | |
} else { | |
if (exceptions.length == mw.exceptionCount) { | |
sameExceptions = true; | |
for (j = exceptions.length - 1; j >= 0; --j) | |
{ | |
w -= 2; | |
if (mw.exceptions[j] != readUnsignedShort(w)) | |
{ | |
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 = u0; | |
mw.classReaderLength = u - u0; | |
continue; | |
} | |
} | |
} | |
} | |
if (ANNOTATIONS && dann != 0) { | |
AnnotationVisitor dv = mv.visitAnnotationDefault(); | |
readAnnotationValue(dann, c, null, dv); | |
if (dv != null) { | |
dv.visitEnd(); | |
} | |
} | |
if (ANNOTATIONS) { | |
for (j = 1; j >= 0; --j) { | |
w = j == 0 ? ianns : anns; | |
if (w != 0) { | |
k = readUnsignedShort(w); | |
w += 2; | |
for (; k > 0; --k) { | |
w = readAnnotationValues(w + 2, | |
c, | |
true, | |
mv.visitAnnotation(readUTF8(w, c), j != 0)); | |
} | |
} | |
} | |
} | |
if (ANNOTATIONS && mpanns != 0) { | |
readParameterAnnotations(mpanns, desc, c, true, mv); | |
} | |
if (ANNOTATIONS && impanns != 0) { | |
readParameterAnnotations(impanns, desc, c, false, mv); | |
} | |
while (cattrs != null) { | |
attr = cattrs.next; | |
cattrs.next = null; | |
mv.visitAttribute(cattrs); | |
cattrs = attr; | |
} | |
} | |
if (mv != null && v != 0) { | |
int maxStack = readUnsignedShort(v); | |
int maxLocals = readUnsignedShort(v + 2); | |
int codeLength = readInt(v + 4); | |
v += 8; | |
int codeStart = v; | |
int codeEnd = v + codeLength; | |
mv.visitCode(); | |
// 1st phase: finds the labels | |
int label; | |
Label[] labels = new Label[codeLength + 2]; | |
readLabel(codeLength + 1, labels); | |
while (v < codeEnd) { | |
w = v - codeStart; | |
int opcode = b[v] & 0xFF; | |
switch (ClassWriter.TYPE[opcode]) { | |
case ClassWriter.NOARG_INSN: | |
case ClassWriter.IMPLVAR_INSN: | |
v += 1; | |
break; | |
case ClassWriter.LABEL_INSN: | |
readLabel(w + readShort(v + 1), labels); | |
v += 3; | |
break; | |
case ClassWriter.LABELW_INSN: | |
readLabel(w + readInt(v + 1), labels); | |
v += 5; | |
break; | |
case ClassWriter.WIDE_INSN: | |
opcode = b[v + 1] & 0xFF; | |
if (opcode == Opcodes.IINC) { | |
v += 6; | |
} else { | |
v += 4; | |
} | |
break; | |
case ClassWriter.TABL_INSN: | |
// skips 0 to 3 padding bytes* | |
v = v + 4 - (w & 3); | |
// reads instruction | |
readLabel(w + readInt(v), labels); | |
j = readInt(v + 8) - readInt(v + 4) + 1; | |
v += 12; | |
for (; j > 0; --j) { | |
readLabel(w + readInt(v), labels); | |
v += 4; | |
} | |
break; | |
case ClassWriter.LOOK_INSN: | |
// skips 0 to 3 padding bytes* | |
v = v + 4 - (w & 3); | |
// reads instruction | |
readLabel(w + readInt(v), labels); | |
j = readInt(v + 4); | |
v += 8; | |
for (; j > 0; --j) { | |
readLabel(w + readInt(v + 4), labels); | |
v += 8; | |
} | |
break; | |
case ClassWriter.VAR_INSN: | |
case ClassWriter.SBYTE_INSN: | |
case ClassWriter.LDC_INSN: | |
v += 2; | |
break; | |
case ClassWriter.SHORT_INSN: | |
case ClassWriter.LDCW_INSN: | |
case ClassWriter.FIELDORMETH_INSN: | |
case ClassWriter.TYPE_INSN: | |
case ClassWriter.IINC_INSN: | |
v += 3; | |
break; | |
case ClassWriter.ITFMETH_INSN: | |
v += 5; | |
break; | |
// case MANA_INSN: | |
default: | |
v += 4; | |
break; | |
} | |
} | |
// parses the try catch entries | |
j = readUnsignedShort(v); | |
v += 2; | |
for (; j > 0; --j) { | |
Label start = readLabel(readUnsignedShort(v), labels); | |
Label end = readLabel(readUnsignedShort(v + 2), labels); | |
Label handler = readLabel(readUnsignedShort(v + 4), labels); | |
int type = readUnsignedShort(v + 6); | |
if (type == 0) { | |
mv.visitTryCatchBlock(start, end, handler, null); | |
} else { | |
mv.visitTryCatchBlock(start, | |
end, | |
handler, | |
readUTF8(items[type], c)); | |
} | |
v += 8; | |
} | |
// parses the local variable, line number tables, and code | |
// attributes | |
int varTable = 0; | |
int varTypeTable = 0; | |
int stackMap = 0; | |
int frameCount = 0; | |
int frameMode = 0; | |
int frameOffset = 0; | |
int frameLocalCount = 0; | |
int frameLocalDiff = 0; | |
int frameStackCount = 0; | |
Object[] frameLocal = null; | |
Object[] frameStack = null; | |
boolean zip = true; | |
cattrs = null; | |
j = readUnsignedShort(v); | |
v += 2; | |
for (; j > 0; --j) { | |
attrName = readUTF8(v, c); | |
if ("LocalVariableTable".equals(attrName)) { | |
if (!skipDebug) { | |
varTable = v + 6; | |
k = readUnsignedShort(v + 6); | |
w = v + 8; | |
for (; k > 0; --k) { | |
label = readUnsignedShort(w); | |
if (labels[label] == null) { | |
readLabel(label, labels).status |= Label.DEBUG; | |
} | |
label += readUnsignedShort(w + 2); | |
if (labels[label] == null) { | |
readLabel(label, labels).status |= Label.DEBUG; | |
} | |
w += 10; | |
} | |
} | |
} else if ("LocalVariableTypeTable".equals(attrName)) { | |
varTypeTable = v + 6; | |
} else if ("LineNumberTable".equals(attrName)) { | |
if (!skipDebug) { | |
k = readUnsignedShort(v + 6); | |
w = v + 8; | |
for (; k > 0; --k) { | |
label = readUnsignedShort(w); | |
if (labels[label] == null) { | |
readLabel(label, labels).status |= Label.DEBUG; | |
} | |
labels[label].line = readUnsignedShort(w + 2); | |
w += 4; | |
} | |
} | |
} else if (FRAMES && "StackMapTable".equals(attrName)) { | |
if ((flags & SKIP_FRAMES) == 0) { | |
stackMap = v + 8; | |
frameCount = readUnsignedShort(v + 6); | |
} | |
/* | |
* 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. | |
*/ | |
// TODO true for frame offsets, | |
// but for UNINITIALIZED type offsets? | |
} else if (FRAMES && "StackMap".equals(attrName)) { | |
if ((flags & SKIP_FRAMES) == 0) { | |
stackMap = v + 8; | |
frameCount = readUnsignedShort(v + 6); | |
zip = false; | |
} | |
/* | |
* 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 (k = 0; k < attrs.length; ++k) { | |
if (attrs[k].type.equals(attrName)) { | |
attr = attrs[k].read(this, | |
v + 6, | |
readInt(v + 2), | |
c, | |
codeStart - 8, | |
labels); | |
if (attr != null) { | |
attr.next = cattrs; | |
cattrs = attr; | |
} | |
} | |
} | |
} | |
v += 6 + readInt(v + 2); | |
} | |
// 2nd phase: visits each instruction | |
if (FRAMES && stackMap != 0) { | |
// creates the very first (implicit) frame from the method | |
// descriptor | |
frameLocal = new Object[maxLocals]; | |
frameStack = new Object[maxStack]; | |
if (unzip) { | |
int local = 0; | |
if ((access & Opcodes.ACC_STATIC) == 0) { | |
if ("<init>".equals(name)) { | |
frameLocal[local++] = Opcodes.UNINITIALIZED_THIS; | |
} else { | |
frameLocal[local++] = readClass(header + 2, c); | |
} | |
} | |
j = 1; | |
loop: while (true) { | |
k = j; | |
switch (desc.charAt(j++)) { | |
case 'Z': | |
case 'C': | |
case 'B': | |
case 'S': | |
case 'I': | |
frameLocal[local++] = Opcodes.INTEGER; | |
break; | |
case 'F': | |
frameLocal[local++] = Opcodes.FLOAT; | |
break; | |
case 'J': | |
frameLocal[local++] = Opcodes.LONG; | |
break; | |
case 'D': | |
frameLocal[local++] = Opcodes.DOUBLE; | |
break; | |
case '[': | |
while (desc.charAt(j) == '[') { | |
++j; | |
} | |
if (desc.charAt(j) == 'L') { | |
++j; | |
while (desc.charAt(j) != ';') { | |
++j; | |
} | |
} | |
frameLocal[local++] = desc.substring(k, ++j); | |
break; | |
case 'L': | |
while (desc.charAt(j) != ';') { | |
++j; | |
} | |
frameLocal[local++] = desc.substring(k + 1, | |
j++); | |
break; | |
default: | |
break loop; | |
} | |
} | |
frameLocalCount = local; | |
} | |
/* | |
* 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 | |
*/ | |
frameOffset = -1; | |
} | |
v = codeStart; | |
Label l; | |
while (v < codeEnd) { | |
w = v - codeStart; | |
l = labels[w]; | |
if (l != null) { | |
mv.visitLabel(l); | |
if (!skipDebug && l.line > 0) { | |
mv.visitLineNumber(l.line, l); | |
} | |
} | |
while (FRAMES && frameLocal != null | |
&& (frameOffset == w || frameOffset == -1)) | |
{ | |
// if there is a frame for this offset, | |
// makes the visitor visit it, | |
// and reads the next frame if there is one. | |
if (!zip || unzip) { | |
mv.visitFrame(Opcodes.F_NEW, | |
frameLocalCount, | |
frameLocal, | |
frameStackCount, | |
frameStack); | |
} else if (frameOffset != -1) { | |
mv.visitFrame(frameMode, | |
frameLocalDiff, | |
frameLocal, | |
frameStackCount, | |
frameStack); | |
} | |
if (frameCount > 0) { | |
int tag, delta, n; | |
if (zip) { | |
tag = b[stackMap++] & 0xFF; | |
} else { | |
tag = MethodWriter.FULL_FRAME; | |
frameOffset = -1; | |
} | |
frameLocalDiff = 0; | |
if (tag < MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME) | |
{ | |
delta = tag; | |
frameMode = Opcodes.F_SAME; | |
frameStackCount = 0; | |
} else if (tag < MethodWriter.RESERVED) { | |
delta = tag | |
- MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME; | |
stackMap = readFrameType(frameStack, | |
0, | |
stackMap, | |
c, | |
labels); | |
frameMode = Opcodes.F_SAME1; | |
frameStackCount = 1; | |
} else { | |
delta = readUnsignedShort(stackMap); | |
stackMap += 2; | |
if (tag == MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) | |
{ | |
stackMap = readFrameType(frameStack, | |
0, | |
stackMap, | |
c, | |
labels); | |
frameMode = Opcodes.F_SAME1; | |
frameStackCount = 1; | |
} else if (tag >= MethodWriter.CHOP_FRAME | |
&& tag < MethodWriter.SAME_FRAME_EXTENDED) | |
{ | |
frameMode = Opcodes.F_CHOP; | |
frameLocalDiff = MethodWriter.SAME_FRAME_EXTENDED | |
- tag; | |
frameLocalCount -= frameLocalDiff; | |
frameStackCount = 0; | |
} else if (tag == MethodWriter.SAME_FRAME_EXTENDED) | |
{ | |
frameMode = Opcodes.F_SAME; | |
frameStackCount = 0; | |
} else if (tag < MethodWriter.FULL_FRAME) { | |
j = unzip ? frameLocalCount : 0; | |
for (k = tag | |
- MethodWriter.SAME_FRAME_EXTENDED; k > 0; k--) | |
{ | |
stackMap = readFrameType(frameLocal, | |
j++, | |
stackMap, | |
c, | |
labels); | |
} | |
frameMode = Opcodes.F_APPEND; | |
frameLocalDiff = tag | |
- MethodWriter.SAME_FRAME_EXTENDED; | |
frameLocalCount += frameLocalDiff; | |
frameStackCount = 0; | |
} else { // if (tag == FULL_FRAME) { | |
frameMode = Opcodes.F_FULL; | |
n = frameLocalDiff = frameLocalCount = readUnsignedShort(stackMap); | |
stackMap += 2; | |
for (j = 0; n > 0; n--) { | |
stackMap = readFrameType(frameLocal, | |
j++, | |
stackMap, | |
c, | |
labels); | |
} | |
n = frameStackCount = readUnsignedShort(stackMap); | |
stackMap += 2; | |
for (j = 0; n > 0; n--) { | |
stackMap = readFrameType(frameStack, | |
j++, | |
stackMap, | |
c, | |
labels); | |
} | |
} | |
} | |
frameOffset += delta + 1; | |
readLabel(frameOffset, labels); | |
--frameCount; | |
} else { | |
frameLocal = null; | |
} | |
} | |
int opcode = b[v] & 0xFF; | |
switch (ClassWriter.TYPE[opcode]) { | |
case ClassWriter.NOARG_INSN: | |
mv.visitInsn(opcode); | |
v += 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); | |
} | |
v += 1; | |
break; | |
case ClassWriter.LABEL_INSN: | |
mv.visitJumpInsn(opcode, labels[w | |
+ readShort(v + 1)]); | |
v += 3; | |
break; | |
case ClassWriter.LABELW_INSN: | |
mv.visitJumpInsn(opcode - 33, labels[w | |
+ readInt(v + 1)]); | |
v += 5; | |
break; | |
case ClassWriter.WIDE_INSN: | |
opcode = b[v + 1] & 0xFF; | |
if (opcode == Opcodes.IINC) { | |
mv.visitIincInsn(readUnsignedShort(v + 2), | |
readShort(v + 4)); | |
v += 6; | |
} else { | |
mv.visitVarInsn(opcode, | |
readUnsignedShort(v + 2)); | |
v += 4; | |
} | |
break; | |
case ClassWriter.TABL_INSN: | |
// skips 0 to 3 padding bytes | |
v = v + 4 - (w & 3); | |
// reads instruction | |
label = w + readInt(v); | |
int min = readInt(v + 4); | |
int max = readInt(v + 8); | |
v += 12; | |
Label[] table = new Label[max - min + 1]; | |
for (j = 0; j < table.length; ++j) { | |
table[j] = labels[w + readInt(v)]; | |
v += 4; | |
} | |
mv.visitTableSwitchInsn(min, | |
max, | |
labels[label], | |
table); | |
break; | |
case ClassWriter.LOOK_INSN: | |
// skips 0 to 3 padding bytes | |
v = v + 4 - (w & 3); | |
// reads instruction | |
label = w + readInt(v); | |
j = readInt(v + 4); | |
v += 8; | |
int[] keys = new int[j]; | |
Label[] values = new Label[j]; | |
for (j = 0; j < keys.length; ++j) { | |
keys[j] = readInt(v); | |
values[j] = labels[w + readInt(v + 4)]; | |
v += 8; | |
} | |
mv.visitLookupSwitchInsn(labels[label], | |
keys, | |
values); | |
break; | |
case ClassWriter.VAR_INSN: | |
mv.visitVarInsn(opcode, b[v + 1] & 0xFF); | |
v += 2; | |
break; | |
case ClassWriter.SBYTE_INSN: | |
mv.visitIntInsn(opcode, b[v + 1]); | |
v += 2; | |
break; | |
case ClassWriter.SHORT_INSN: | |
mv.visitIntInsn(opcode, readShort(v + 1)); | |
v += 3; | |
break; | |
case ClassWriter.LDC_INSN: | |
mv.visitLdcInsn(readConst(b[v + 1] & 0xFF, c)); | |
v += 2; | |
break; | |
case ClassWriter.LDCW_INSN: | |
mv.visitLdcInsn(readConst(readUnsignedShort(v + 1), | |
c)); | |
v += 3; | |
break; | |
case ClassWriter.FIELDORMETH_INSN: | |
case ClassWriter.ITFMETH_INSN: | |
int cpIndex = items[readUnsignedShort(v + 1)]; | |
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); | |
} | |
if (opcode == Opcodes.INVOKEINTERFACE) { | |
v += 5; | |
} else { | |
v += 3; | |
} | |
break; | |
case ClassWriter.TYPE_INSN: | |
mv.visitTypeInsn(opcode, readClass(v + 1, c)); | |
v += 3; | |
break; | |
case ClassWriter.IINC_INSN: | |
mv.visitIincInsn(b[v + 1] & 0xFF, b[v + 2]); | |
v += 3; | |
break; | |
// case MANA_INSN: | |
default: | |
mv.visitMultiANewArrayInsn(readClass(v + 1, c), | |
b[v + 3] & 0xFF); | |
v += 4; | |
break; | |
} | |
} | |
l = labels[codeEnd - codeStart]; | |
if (l != null) { | |
mv.visitLabel(l); | |
} | |
// visits the local variable tables | |
if (!skipDebug && varTable != 0) { | |
int[] typeTable = null; | |
if (varTypeTable != 0) { | |
k = readUnsignedShort(varTypeTable) * 3; | |
w = varTypeTable + 2; | |
typeTable = new int[k]; | |
while (k > 0) { | |
typeTable[--k] = w + 6; // signature | |
typeTable[--k] = readUnsignedShort(w + 8); // index | |
typeTable[--k] = readUnsignedShort(w); // start | |
w += 10; | |
} | |
} | |
k = readUnsignedShort(varTable); | |
w = varTable + 2; | |
for (; k > 0; --k) { | |
int start = readUnsignedShort(w); | |
int length = readUnsignedShort(w + 2); | |
int index = readUnsignedShort(w + 8); | |
String vsignature = null; | |
if (typeTable != null) { | |
for (int a = 0; a < typeTable.length; a += 3) { | |
if (typeTable[a] == start | |
&& typeTable[a + 1] == index) | |
{ | |
vsignature = readUTF8(typeTable[a + 2], c); | |
break; | |
} | |
} | |
} | |
mv.visitLocalVariable(readUTF8(w + 4, c), | |
readUTF8(w + 6, c), | |
vsignature, | |
labels[start], | |
labels[start + length], | |
index); | |
w += 10; | |
} | |
} | |
// visits the other attributes | |
while (cattrs != null) { | |
attr = cattrs.next; | |
cattrs.next = null; | |
mv.visitAttribute(cattrs); | |
cattrs = attr; | |
} | |
// visits the max stack and max locals values | |
mv.visitMaxs(maxStack, maxLocals); | |
} | |
if (mv != null) { | |
mv.visitEnd(); | |
} | |
} | |
// visits the end of the class | |
classVisitor.visitEnd(); | |
} | |
/** | |
* Reads parameter annotations and makes the given visitor visit them. | |
* | |
* @param v start offset in {@link #b b} of the annotations to be read. | |
* @param desc the method descriptor. | |
* @param buf buffer to be used to call {@link #readUTF8 readUTF8}, | |
* {@link #readClass(int,char[]) readClass} or | |
* {@link #readConst readConst}. | |
* @param visible <tt>true</tt> if the annotations to be read are visible | |
* at runtime. | |
* @param mv the visitor that must visit the annotations. | |
*/ | |
private void readParameterAnnotations( | |
int v, | |
final String desc, | |
final char[] buf, | |
final boolean visible, | |
final MethodVisitor mv) | |
{ | |
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(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(); | |
} | |
} | |
for (; i < n + synthetics; ++i) { | |
int j = readUnsignedShort(v); | |
v += 2; | |
for (; j > 0; --j) { | |
av = mv.visitParameterAnnotation(i, readUTF8(v, buf), visible); | |
v = readAnnotationValues(v + 2, buf, 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, | |
new Byte((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, | |
new Short((short) readInt(items[readUnsignedShort(v)]))); | |
v += 2; | |
break; | |
case 'C': // pointer to CONSTANT_Char | |
av.visit(name, | |
new Character((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; | |
} | |
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]; | |
} | |
/** | |
* 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 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]; | |
} | |
/** | |
* 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); | |
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, d, e; | |
while (index < endIndex) { | |
c = b[index++] & 0xFF; | |
switch (c >> 4) { | |
case 0: | |
case 1: | |
case 2: | |
case 3: | |
case 4: | |
case 5: | |
case 6: | |
case 7: | |
// 0xxxxxxx | |
buf[strLen++] = (char) c; | |
break; | |
case 12: | |
case 13: | |
// 110x xxxx 10xx xxxx | |
d = b[index++]; | |
buf[strLen++] = (char) (((c & 0x1F) << 6) | (d & 0x3F)); | |
break; | |
default: | |
// 1110 xxxx 10xx xxxx 10xx xxxx | |
d = b[index++]; | |
e = b[index++]; | |
buf[strLen++] = (char) (((c & 0x0F) << 12) | |
| ((d & 0x3F) << 6) | (e & 0x3F)); | |
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} or {@link Type} 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 new Integer(readInt(index)); | |
case ClassWriter.FLOAT: | |
return new Float(Float.intBitsToFloat(readInt(index))); | |
case ClassWriter.LONG: | |
return new Long(readLong(index)); | |
case ClassWriter.DOUBLE: | |
return new Double(Double.longBitsToDouble(readLong(index))); | |
case ClassWriter.CLASS: | |
return Type.getObjectType(readUTF8(index, buf)); | |
// case ClassWriter.STR: | |
default: | |
return readUTF8(index, buf); | |
} | |
} | |
} |