Java tutorial
/*** * 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 com.google.gwt.dev.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); } } }