| /*** |
| * ASM: a very small and fast Java bytecode manipulation framework |
| * Copyright (c) 2000-2005 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 com.sun.tools.javac.code.TargetType; |
| import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * 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 performance (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; |
| |
| /** |
| * The start index of each bootstrap method. |
| */ |
| int[] bootstrapMethods; |
| |
| // ------------------------------------------------------------------------ |
| // 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 ll = items.length; |
| strings = new String[ll]; |
| int max = 0; |
| int index = off + 10; |
| for (int i = 1; i < ll; ++i) { |
| items[i] = index + 1; |
| int tag = b[index]; |
| int size; |
| switch (tag) { |
| 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; |
| } |
| |
| /** |
| * Copies the constant pool data into the given {@link ClassWriter}. Should |
| * be called before the {@link #accept(ClassVisitor,boolean)} 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)); |
| } |
| |
| /** |
| * 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) { |
| 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; |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // 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 skipDebug <tt>true</tt> if the debug information of the class |
| * must not be visited. In this case the |
| * {@link MethodVisitor#visitLocalVariable visitLocalVariable} and |
| * {@link MethodVisitor#visitLineNumber visitLineNumber} methods will |
| * not be called. |
| */ |
| public void accept(final ClassVisitor classVisitor, final boolean skipDebug) |
| { |
| accept(classVisitor, new Attribute[0], skipDebug); |
| } |
| |
| /** |
| * 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 be ignored. |
| * @param skipDebug <tt>true</tt> if the debug information of the class |
| * must not be visited. In this case the |
| * {@link MethodVisitor#visitLocalVariable visitLocalVariable} and |
| * {@link MethodVisitor#visitLineNumber visitLineNumber} methods will |
| * not be called. |
| */ |
| public void accept( |
| final ClassVisitor classVisitor, |
| final Attribute[] attrs, |
| final boolean skipDebug) |
| { |
| 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; |
| int xanns = 0; |
| int ixanns = 0; |
| Attribute cattrs = null; |
| |
| // visits the header |
| u = header; // u = u2 access_flags |
| access = readUnsignedShort(u); |
| name = readClass(u + 2, c); // name = u2 this_class |
| v = items[readUnsignedShort(u + 4)]; // u + 4 = u2 super_class |
| String superClassName = v == 0 ? null : readUTF8(v, c); |
| String[] implementedItfs = new String[readUnsignedShort(u + 6)]; |
| // u + 6 = u2 interfaces_count; |
| w = 0; |
| u += 8; // u + 8 = interfaces[interfaces_count] |
| for (i = 0; i < implementedItfs.length; ++i) { |
| implementedItfs[i] = readClass(u, c); |
| u += 2; |
| } |
| |
| // u = u2 fields_count |
| |
| // skips fields and methods |
| v = u; |
| i = readUnsignedShort(v); // i = u2 fields_count |
| v += 2; // v = field_info fields[fields_count] |
| for (; i > 0; --i) { |
| j = readUnsignedShort(v + 6); |
| v += 8; |
| for (; j > 0; --j) { |
| v += 6 + readInt(v + 2); |
| } |
| } |
| i = readUnsignedShort(v); // i = u2 methods_count |
| v += 2; // v = method_info methods[methods_count] |
| 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); // i = u2 attributes_count |
| v += 2; // v = attribute_info attributes[attributes_count] |
| for (; i > 0; --i) { |
| attrName = readUTF8(v, c); |
| if (attrName.equals("SourceFile")) { |
| sourceFile = readUTF8(v + 6, c); |
| } else if (attrName.equals("Deprecated")) { |
| access |= Opcodes.ACC_DEPRECATED; |
| } else if (attrName.equals("Synthetic")) { |
| access |= Opcodes.ACC_SYNTHETIC; |
| } else if (attrName.equals("Annotation")) { |
| access |= Opcodes.ACC_ANNOTATION; |
| } else if (attrName.equals("Enum")) { |
| access |= Opcodes.ACC_ENUM; |
| } else if (attrName.equals("InnerClasses")) { |
| w = v + 6; |
| } else if (attrName.equals("Signature")) { |
| signature = readUTF8(v + 6, c); |
| } else if (attrName.equals("SourceDebugExtension")) { |
| int len = readInt(v + 2); |
| sourceDebug = readUTF(v + 6, len, new char[len]); |
| } else if (attrName.equals("EnclosingMethod")) { |
| 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 (attrName.equals("RuntimeVisibleAnnotations")) { |
| anns = v + 6; |
| } else if (attrName.equals("RuntimeInvisibleAnnotations")) { |
| ianns = v + 6; |
| } else if (attrName.equals("RuntimeVisibleTypeAnnotations")) { |
| xanns = v + 6; |
| } else if (attrName.equals("RuntimeInvisibleTypeAnnotations")) { |
| ixanns = v + 6; |
| } else if (attrName.equals("BootstrapMethods")) { |
| bootstrapMethods = new int[readUnsignedShort(v + 6)]; |
| for (j = 0, u = v + 8; j < bootstrapMethods.length; j++) { |
| bootstrapMethods[j] = u; |
| u += 2 + readUnsignedShort(u + 2) << 1; |
| } |
| } 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 (sourceFile != null || sourceDebug != null) { |
| classVisitor.visitSource(sourceFile, sourceDebug); |
| } |
| |
| // calls the visitOuterClass method |
| if (enclosingOwner != null) { |
| classVisitor.visitOuterClass(enclosingOwner, |
| enclosingName, |
| enclosingDesc); |
| } |
| |
| // visits the class annotations |
| for (i = 1; i >= 0; --i) { |
| v = i == 0 ? ianns : anns; |
| if (v != 0) { |
| j = readUnsignedShort(v); |
| v += 2; |
| for (; j > 0; --j) { |
| desc = readUTF8(v, c); |
| v += 2; |
| v = readAnnotationValues(v, |
| c, |
| classVisitor.visitAnnotation(desc, i != 0)); |
| } |
| } |
| } |
| |
| // TODO |
| // visits the class extended annotations |
| for (i = 1; i >= 0; --i) { |
| v = i == 0 ? ixanns : xanns; |
| if (v != 0) { |
| j = readUnsignedShort(v); |
| v += 2; |
| for (; j > 0; --j) { |
| v = readTypeAnnotationValues(v, |
| c, classVisitor, i != 0); |
| } |
| } |
| } |
| |
| // visits the class attributes |
| while (cattrs != null) { |
| attr = cattrs.next; |
| cattrs.next = null; |
| classVisitor.visitAttribute(cattrs); |
| cattrs = attr; |
| } |
| |
| // class 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 |
| u = header + 8 + 2 * implementedItfs.length; |
| i = readUnsignedShort(u); // i = u2 fields_count |
| u += 2; // u = field_info[fields_count] |
| 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; |
| xanns = 0; |
| ixanns = 0; |
| cattrs = null; |
| |
| j = readUnsignedShort(u + 6); // j = u2 attributes_count |
| u += 8; // u = attributes[attributes_count] |
| for (; j > 0; --j) { |
| attrName = readUTF8(u, c); |
| if (attrName.equals("ConstantValue")) { |
| fieldValueItem = readUnsignedShort(u + 6); |
| } else if (attrName.equals("Synthetic")) { |
| access |= Opcodes.ACC_SYNTHETIC; |
| } else if (attrName.equals("Deprecated")) { |
| access |= Opcodes.ACC_DEPRECATED; |
| } else if (attrName.equals("Enum")) { |
| access |= Opcodes.ACC_ENUM; |
| } else if (attrName.equals("Signature")) { |
| signature = readUTF8(u + 6, c); |
| } else if (attrName.equals("RuntimeVisibleAnnotations")) { |
| anns = u + 6; |
| } else if (attrName.equals("RuntimeInvisibleAnnotations")) { |
| ianns = u + 6; |
| } else if (attrName.equals("RuntimeVisibleTypeAnnotations")) { |
| xanns = u + 6; |
| } else if (attrName.equals("RuntimeInvisibleTypeAnnotations")) { |
| ixanns = 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); |
| } |
| // reads the field's value, if any |
| Object value = (fieldValueItem == 0 |
| ? null |
| : readConst(fieldValueItem, c)); |
| // visits the field |
| FieldVisitor fv = classVisitor.visitField(access, |
| name, |
| desc, |
| signature, |
| value); |
| // visits the field annotations and attributes |
| if (fv != null) { |
| for (j = 1; j >= 0; --j) { |
| v = j == 0 ? ianns : anns; |
| if (v != 0) { |
| k = readUnsignedShort(v); |
| v += 2; |
| for (; k > 0; --k) { |
| desc = readUTF8(v, c); |
| v += 2; |
| v = readAnnotationValues(v, |
| c, |
| fv.visitAnnotation(desc, j != 0)); |
| } |
| } |
| } |
| //TODO |
| // now visit the extended annotations |
| if(xanns != 0) { |
| v = xanns; |
| k = readUnsignedShort(v); |
| v += 2; |
| for(; k > 0; --k) { |
| v = readTypeAnnotationValues(v, |
| c, fv, true); |
| } |
| } |
| |
| if(ixanns != 0) { |
| v = ixanns; |
| k = readUnsignedShort(v); |
| v += 2; |
| for(; k > 0; --k) { |
| v = readTypeAnnotationValues(v, |
| c, fv, false); |
| } |
| } |
| |
| 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; |
| //jaime |
| xanns = 0; |
| ixanns = 0; |
| // end jaime |
| 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); |
| u += 2; |
| int attrSize = readInt(u); |
| u += 4; |
| if (attrName.equals("Code")) { |
| v = u; |
| } else if (attrName.equals("Exceptions")) { |
| w = u; |
| } else if (attrName.equals("Synthetic")) { |
| access |= Opcodes.ACC_SYNTHETIC; |
| } else if (attrName.equals("Varargs")) { |
| access |= Opcodes.ACC_VARARGS; |
| } else if (attrName.equals("Bridge")) { |
| access |= Opcodes.ACC_BRIDGE; |
| } else if (attrName.equals("Deprecated")) { |
| access |= Opcodes.ACC_DEPRECATED; |
| } else if (attrName.equals("Signature")) { |
| signature = readUTF8(u, c); |
| } else if (attrName.equals("AnnotationDefault")) { |
| dann = u; |
| } else if (attrName.equals("RuntimeVisibleAnnotations")) { |
| anns = u; |
| } else if (attrName.equals("RuntimeInvisibleAnnotations")) { |
| ianns = u; |
| } else if (attrName.equals("RuntimeVisibleTypeAnnotations")) { |
| xanns = u; |
| } else if (attrName.equals("RuntimeInvisibleTypeAnnotations")) { |
| ixanns = u; |
| } else if (attrName.equals("RuntimeVisibleParameterAnnotations")) { |
| mpanns = u; |
| } else if (attrName.equals("RuntimeInvisibleParameterAnnotations")) { |
| 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 (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 (dann != 0) { |
| AnnotationVisitor dv = mv.visitAnnotationDefault(); |
| readAnnotationValue(dann, c, null, dv); |
| dv.visitEnd(); |
| } |
| for (j = 1; j >= 0; --j) { |
| w = j == 0 ? ianns : anns; |
| if (w != 0) { |
| k = readUnsignedShort(w); |
| w += 2; |
| for (; k > 0; --k) { |
| desc = readUTF8(w, c); |
| w += 2; |
| w = readAnnotationValues(w, |
| c, |
| mv.visitAnnotation(desc, j != 0)); |
| } |
| } |
| } |
| |
| // now visit the method extended annotations |
| for (j = 1; j >= 0; --j) { |
| w = j == 0 ? ixanns : xanns; |
| if (w != 0) { |
| k = readUnsignedShort(w); |
| w += 2; |
| for (; k > 0; --k) { |
| w = readTypeAnnotationValues(w, |
| c, mv, j != 0); |
| } |
| } |
| } |
| |
| if (mpanns != 0) { |
| readParameterAnnotations(mpanns, c, true, mv); |
| } |
| if (impanns != 0) { |
| readParameterAnnotations(impanns, 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 + 1]; |
| while (v < codeEnd) { |
| int opcode = b[v] & 0xFF; |
| switch (ClassWriter.TYPE[opcode]) { |
| case ClassWriter.NOARG_INSN: |
| case ClassWriter.IMPLVAR_INSN: |
| v += 1; |
| break; |
| case ClassWriter.LABEL_INSN: |
| label = v - codeStart + readShort(v + 1); |
| if (labels[label] == null) { |
| labels[label] = new Label(); |
| } |
| v += 3; |
| break; |
| case ClassWriter.LABELW_INSN: |
| label = v - codeStart + readInt(v + 1); |
| if (labels[label] == null) { |
| labels[label] = new Label(); |
| } |
| 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 |
| w = v - codeStart; |
| v = v + 4 - (w & 3); |
| // reads instruction |
| label = w + readInt(v); |
| v += 4; |
| if (labels[label] == null) { |
| labels[label] = new Label(); |
| } |
| j = readInt(v); |
| v += 4; |
| j = readInt(v) - j + 1; |
| v += 4; |
| for (; j > 0; --j) { |
| label = w + readInt(v); |
| v += 4; |
| if (labels[label] == null) { |
| labels[label] = new Label(); |
| } |
| } |
| break; |
| case ClassWriter.LOOK_INSN: |
| // skips 0 to 3 padding bytes |
| w = v - codeStart; |
| v = v + 4 - (w & 3); |
| // reads instruction |
| label = w + readInt(v); |
| v += 4; |
| if (labels[label] == null) { |
| labels[label] = new Label(); |
| } |
| j = readInt(v); |
| v += 4; |
| for (; j > 0; --j) { |
| v += 4; // skips key |
| label = w + readInt(v); |
| v += 4; |
| if (labels[label] == null) { |
| labels[label] = new Label(); |
| } |
| } |
| 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: |
| case ClassWriter.INDY: |
| 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 = readUnsignedShort(v); |
| Label start = labels[label]; |
| if (start == null) { |
| labels[label] = start = new Label(); |
| } |
| label = readUnsignedShort(v + 2); |
| Label end = labels[label]; |
| if (end == null) { |
| labels[label] = end = new Label(); |
| } |
| label = readUnsignedShort(v + 4); |
| Label handler = labels[label]; |
| if (handler == null) { |
| labels[label] = handler = new Label(); |
| } |
| |
| 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; |
| cattrs = null; |
| j = readUnsignedShort(v); |
| v += 2; |
| for (; j > 0; --j) { |
| attrName = readUTF8(v, c); |
| if (attrName.equals("LocalVariableTable")) { |
| if (!skipDebug) { |
| varTable = v + 6; |
| k = readUnsignedShort(v + 6); |
| w = v + 8; |
| for (; k > 0; --k) { |
| label = readUnsignedShort(w); |
| if (labels[label] == null) { |
| labels[label] = new Label(); |
| } |
| label += readUnsignedShort(w + 2); |
| if (labels[label] == null) { |
| labels[label] = new Label(); |
| } |
| w += 10; |
| } |
| } |
| } else if (attrName.equals("LocalVariableTypeTable")) { |
| varTypeTable = v + 6; |
| } else if (attrName.equals("LineNumberTable")) { |
| if (!skipDebug) { |
| k = readUnsignedShort(v + 6); |
| w = v + 8; |
| for (; k > 0; --k) { |
| label = readUnsignedShort(w); |
| if (labels[label] == null) { |
| labels[label] = new Label(); |
| } |
| labels[label].line = readUnsignedShort(w + 2); |
| w += 4; |
| } |
| } |
| } else if (attrName.equals("RuntimeInvisibleTypeAnnotations")) { |
| k = readUnsignedShort(v + 6); |
| w = v + 8; |
| for (; k > 0; --k) { |
| w = readTypeAnnotationValues(w, |
| c, mv, false); |
| } |
| } else if (attrName.equals("RuntimeVisibleTypeAnnotations")) { |
| k = readUnsignedShort(v + 6); |
| w = v + 8; |
| for (; k > 0; --k) { |
| w = readTypeAnnotationValues(w, |
| c, mv, true); |
| } |
| } 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 |
| v = codeStart; |
| PrecompiledMethodVisitor pmv = |
| (mv instanceof PrecompiledMethodVisitor) |
| ? (PrecompiledMethodVisitor) mv : null; |
| Label l; |
| while (v < codeEnd) { |
| w = v - codeStart; |
| if (pmv != null) |
| pmv.visitCurrentPosition(w); |
| l = labels[w]; |
| if (l != null) { |
| mv.visitLabel(l); |
| if (!skipDebug && l.line > 0) { |
| mv.visitLineNumber(l.line, l); |
| } |
| } |
| 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); |
| v += 4; |
| int min = readInt(v); |
| v += 4; |
| int max = readInt(v); |
| v += 4; |
| 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); |
| v += 4; |
| j = readInt(v); |
| v += 4; |
| int[] keys = new int[j]; |
| Label[] values = new Label[j]; |
| for (j = 0; j < keys.length; ++j) { |
| keys[j] = readInt(v); |
| v += 4; |
| values[j] = labels[w + readInt(v)]; |
| v += 4; |
| } |
| 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 ClassWriter.INDY: |
| cpIndex = items[readUnsignedShort(v + 1)]; |
| int bsmIndex = bootstrapMethods[readUnsignedShort(cpIndex)]; |
| Handle bsm = (Handle) readConst(readUnsignedShort(bsmIndex), c); |
| int bsmArgCount = readUnsignedShort(bsmIndex + 2); |
| Object[] bsmArgs = new Object[bsmArgCount]; |
| bsmIndex += 4; |
| for (j = 0; j < bsmArgCount; j++) { |
| bsmArgs[j] = readConst(readUnsignedShort(bsmIndex), c); |
| bsmIndex += 2; |
| } |
| cpIndex = items[readUnsignedShort(cpIndex + 2)]; |
| iname = readUTF8(cpIndex, c); |
| idesc = readUTF8(cpIndex + 2, c); |
| mv.visitInvokeDynamicInsn(iname, idesc, bsm, bsmArgs); |
| v += 5; |
| 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) { |
| if (pmv != null) |
| pmv.visitCurrentPosition(codeEnd - codeStart); |
| mv.visitLabel(l); |
| } |
| |
| // visits the local variable tables |
| if (!skipDebug && varTable != 0) { |
| int[] typeTable = null; |
| if (varTypeTable != 0) { |
| w = varTypeTable; |
| k = readUnsignedShort(w) * 3; |
| w += 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; |
| } |
| } |
| w = varTable; |
| k = readUnsignedShort(w); |
| w += 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 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 char[] buf, |
| final boolean visible, |
| final MethodVisitor mv) |
| { |
| int n = b[v++] & 0xFF; |
| for (int i = 0; i < n; ++i) { |
| int j = readUnsignedShort(v); |
| v += 2; |
| for (; j > 0; --j) { |
| String desc = readUTF8(v, buf); |
| v += 2; |
| AnnotationVisitor av = mv.visitParameterAnnotation(i, |
| desc, |
| visible); |
| v = readAnnotationValues(v, buf, 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 av the visitor that must visit the values. |
| * @return the end offset of the annotations values. |
| */ |
| private int readAnnotationValues( |
| int v, |
| final char[] buf, |
| final AnnotationVisitor av) |
| { |
| int i = readUnsignedShort(v); |
| v += 2; |
| for (; i > 0; --i) { |
| String name = readUTF8(v, buf); |
| v += 2; |
| v = readAnnotationValue(v, buf, name, av); |
| } |
| av.visitEnd(); |
| return v; |
| } |
| /** |
| * Reads the values and reference info of an extended 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 mv the visitor to generate the visitor that must visit the values. |
| * @param visible {@code true} if the annotation is visible at runtime. |
| * @return the end offset of the annotations values. |
| * @author jaimeq |
| */ |
| private int readTypeAnnotationValues( |
| int v, |
| final char[] buf, |
| final MemberVisitor mv, |
| final boolean visible) |
| { |
| // first handle |
| // |
| // u1 target_type |
| // { ... |
| // } reference_info |
| // |
| |
| int target_type_value = readByte(v); |
| v += 1; |
| |
| Integer offset = null; |
| Integer location_length = null; |
| List<TypePathEntry> locations = new ArrayList<TypePathEntry>(); |
| Integer start_pc = null; |
| Integer length = null; |
| Integer index = null; |
| Integer param_index = null; |
| Integer bound_index = null; |
| Integer type_index = null; |
| Integer exception_index = null; |
| Integer table_length = null; |
| |
| TargetType target_type = TargetType.fromTargetTypeValue(target_type_value); |
| |
| switch(target_type) { |
| // type test (instanceof) |
| // object creation |
| // constructor/method reference receiver |
| // { |
| // u2 offset; |
| // } reference_info; |
| case INSTANCEOF: |
| case NEW: |
| case CONSTRUCTOR_REFERENCE: |
| case METHOD_REFERENCE: |
| offset = readUnsignedShort(v); |
| v += 2; |
| break; |
| |
| // method receiver |
| // { |
| // } reference_info; |
| case METHOD_RECEIVER: |
| break; |
| |
| // local variable |
| // u2 table_length; |
| // { |
| // u2 start_pc; |
| // u2 length; |
| // u2 index; |
| // } reference_info; |
| case LOCAL_VARIABLE: |
| // resource variable |
| case RESOURCE_VARIABLE: |
| table_length = readUnsignedShort(v); |
| v += 2; |
| assert table_length == 1; // FIXME |
| start_pc = readUnsignedShort(v); |
| v += 2; |
| length = readUnsignedShort(v); |
| v += 2; |
| index = readUnsignedShort(v); |
| v += 2; |
| break; |
| |
| // method return type |
| // { |
| // } reference_info; |
| case METHOD_RETURN: |
| break; |
| |
| // method parameter |
| // { |
| // u1 param; |
| // } reference_info; |
| case METHOD_FORMAL_PARAMETER: |
| param_index = readByte(v); |
| v++; |
| break; |
| |
| // field |
| // { |
| // } reference_info; |
| case FIELD: |
| break; |
| |
| // class type parameter bound |
| // method type parameter bound |
| // { |
| // u1 param_index; |
| // u1 bound_index; |
| // } reference_info; |
| case CLASS_TYPE_PARAMETER_BOUND: |
| case METHOD_TYPE_PARAMETER_BOUND: |
| param_index = readByte(v); |
| v++; |
| bound_index = readByte(v); |
| v++; |
| break; |
| |
| // class extends/implements |
| // exception type in throws |
| // { |
| // u1 type_index; |
| // } reference_info; |
| case CLASS_EXTENDS: |
| type_index = readUnsignedShort(v); |
| if (type_index == 0xFFFF) type_index = -1; |
| v += 2; |
| break; |
| case THROWS: |
| type_index = readUnsignedShort(v); |
| v += 2; |
| break; |
| case EXCEPTION_PARAMETER: |
| exception_index = readUnsignedShort(v); |
| v += 2; |
| break; |
| |
| // typecast |
| // type argument in constructor call |
| // type argument in method call |
| // type argument in constructor reference |
| // type argument in method reference |
| // { |
| // u2 offset; |
| // u1 type_index; |
| // } reference_info; |
| case CAST: |
| case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: |
| case METHOD_INVOCATION_TYPE_ARGUMENT: |
| case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: |
| case METHOD_REFERENCE_TYPE_ARGUMENT: |
| offset = readUnsignedShort(v); |
| v += 2; |
| |
| type_index = readByte(v); |
| v++; |
| break; |
| |
| // method type parameter |
| // { |
| // u1 param_index; |
| // } reference_info; |
| case CLASS_TYPE_PARAMETER: |
| case METHOD_TYPE_PARAMETER: |
| param_index = readByte(v); |
| v++; |
| break; |
| |
| default: throw new RuntimeException( |
| "Unrecognized target type: " + target_type); |
| } |
| |
| // now read in the location information |
| { |
| location_length = readByte(v); |
| v += 1; |
| for (int m = 0; m < location_length; m++) { |
| int loctag = readByte(v); |
| int locarg = readByte(v + 1); |
| v += TypePathEntry.bytesPerEntry; |
| locations.add(TypePathEntry.fromBinary(loctag, locarg)); |
| } |
| } |
| |
| String desc = readUTF8(v, buf); |
| v += 2; |
| TypeAnnotationVisitor xav = mv.visitTypeAnnotation(desc, visible, false); |
| |
| xav.visitXTargetType(target_type_value); |
| if (start_pc != null) { |
| xav.visitXStartPc(start_pc); |
| } |
| if (length != null) { |
| xav.visitXLength(length); |
| } |
| if (index != null) { |
| xav.visitXIndex(index); |
| } |
| if (offset != null) { |
| xav.visitXOffset(offset); |
| } |
| if (type_index != null) { |
| xav.visitXTypeIndex(type_index); |
| } |
| if (param_index != null) { |
| xav.visitXParamIndex(param_index); |
| } |
| if (bound_index != null) { |
| xav.visitXBoundIndex(bound_index); |
| } |
| if (exception_index != null) { |
| xav.visitXExceptionIndex(exception_index); |
| } |
| if (location_length != null) { |
| xav.visitXLocationLength(location_length); |
| } |
| for (TypePathEntry location : locations) { |
| xav.visitXLocation(location); |
| } |
| // Visit the annotation name and save space for the values count. |
| xav.visitXNameAndArgsSize(); |
| |
| // then read annotation values |
| int i = readUnsignedShort(v); |
| v += 2; |
| for (; i > 0; --i) { |
| String name = readUTF8(v, buf); |
| v += 2; |
| // can use the same method as for declaration annotations because |
| // the first part of an extended annotation matches the normal |
| // annotations |
| v = readAnnotationValue(v, buf, name, xav); |
| } |
| |
| xav.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; |
| switch (readByte(v++)) { |
| 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 |
| boolean b = readInt(items[readUnsignedShort(v)]) == 0; |
| av.visit(name, b ? 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 |
| String desc = readUTF8(v, buf); |
| v += 2; |
| v = readAnnotationValues(v, buf, av.visitAnnotation(name, desc)); |
| break; |
| case '[': // array_value |
| int size = readUnsignedShort(v); |
| v += 2; |
| if (size == 0) { |
| av.visitArray(name).visitEnd(); |
| return v; |
| } |
| switch (readByte(v++)) { |
| 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--; |
| AnnotationVisitor aav = av.visitArray(name); |
| for (i = size; i > 0; --i) { |
| v = readAnnotationValue(v, buf, null, aav); |
| } |
| aav.visitEnd(); |
| } |
| } |
| return v; |
| } |
| |
| /** |
| * 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 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, int utfLen, 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: |
| String s = readUTF8(index, buf); |
| return Type.getType(s.charAt(0) == '[' ? s : "L" + s + ";"); |
| 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); |
| } |
| } |
| } |