/*** * ASM: a very small and fast Java bytecode manipulation framework * Copyright (c) 2000-2007 INRIA, France Telecom * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ package org.mockito.asm.util; import org.mockito.asm.AnnotationVisitor; import org.mockito.asm.Attribute; import org.mockito.asm.Label; import org.mockito.asm.MethodAdapter; import org.mockito.asm.MethodVisitor; import org.mockito.asm.Opcodes; import org.mockito.asm.Type; import java.util.HashMap; import java.util.Map; /** * A {@link MethodAdapter} that checks that its methods are properly used. More * precisely this code adapter checks each instruction individually (i.e., each * visit method checks some preconditions based only on its arguments - * such as the fact that the given opcode is correct for a given visit method), * but does not check the sequence of instructions. For example, * in a method whose signature is void m (), the invalid instruction * IRETURN, or the invalid sequence IADD L2I will not be detected by * this code adapter. * * @author Eric Bruneton */ public class CheckMethodAdapter extends MethodAdapter { /** * true if the visitCode method has been called. */ private boolean startCode; /** * true if the visitMaxs method has been called. */ private boolean endCode; /** * true if the visitEnd method has been called. */ private boolean endMethod; /** * The already visited labels. This map associate Integer values to Label * keys. */ private final Map labels; /** * Code of the visit method to be used for each opcode. */ private static final int[] TYPE; static { String s = "BBBBBBBBBBBBBBBBCCIAADDDDDAAAAAAAAAAAAAAAAAAAABBBBBBBBDD" + "DDDAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + "BBBBBBBBBBBBBBBBBBBJBBBBBBBBBBBBBBBBBBBBHHHHHHHHHHHHHHHHD" + "KLBBBBBBFFFFGGGGAECEBBEEBBAMHHAA"; TYPE = new int[s.length()]; for (int i = 0; i < TYPE.length; ++i) { TYPE[i] = s.charAt(i) - 'A' - 1; } } // code to generate the above string // public static void main (String[] args) { // int[] TYPE = new int[] { // 0, //NOP // 0, //ACONST_NULL // 0, //ICONST_M1 // 0, //ICONST_0 // 0, //ICONST_1 // 0, //ICONST_2 // 0, //ICONST_3 // 0, //ICONST_4 // 0, //ICONST_5 // 0, //LCONST_0 // 0, //LCONST_1 // 0, //FCONST_0 // 0, //FCONST_1 // 0, //FCONST_2 // 0, //DCONST_0 // 0, //DCONST_1 // 1, //BIPUSH // 1, //SIPUSH // 7, //LDC // -1, //LDC_W // -1, //LDC2_W // 2, //ILOAD // 2, //LLOAD // 2, //FLOAD // 2, //DLOAD // 2, //ALOAD // -1, //ILOAD_0 // -1, //ILOAD_1 // -1, //ILOAD_2 // -1, //ILOAD_3 // -1, //LLOAD_0 // -1, //LLOAD_1 // -1, //LLOAD_2 // -1, //LLOAD_3 // -1, //FLOAD_0 // -1, //FLOAD_1 // -1, //FLOAD_2 // -1, //FLOAD_3 // -1, //DLOAD_0 // -1, //DLOAD_1 // -1, //DLOAD_2 // -1, //DLOAD_3 // -1, //ALOAD_0 // -1, //ALOAD_1 // -1, //ALOAD_2 // -1, //ALOAD_3 // 0, //IALOAD // 0, //LALOAD // 0, //FALOAD // 0, //DALOAD // 0, //AALOAD // 0, //BALOAD // 0, //CALOAD // 0, //SALOAD // 2, //ISTORE // 2, //LSTORE // 2, //FSTORE // 2, //DSTORE // 2, //ASTORE // -1, //ISTORE_0 // -1, //ISTORE_1 // -1, //ISTORE_2 // -1, //ISTORE_3 // -1, //LSTORE_0 // -1, //LSTORE_1 // -1, //LSTORE_2 // -1, //LSTORE_3 // -1, //FSTORE_0 // -1, //FSTORE_1 // -1, //FSTORE_2 // -1, //FSTORE_3 // -1, //DSTORE_0 // -1, //DSTORE_1 // -1, //DSTORE_2 // -1, //DSTORE_3 // -1, //ASTORE_0 // -1, //ASTORE_1 // -1, //ASTORE_2 // -1, //ASTORE_3 // 0, //IASTORE // 0, //LASTORE // 0, //FASTORE // 0, //DASTORE // 0, //AASTORE // 0, //BASTORE // 0, //CASTORE // 0, //SASTORE // 0, //POP // 0, //POP2 // 0, //DUP // 0, //DUP_X1 // 0, //DUP_X2 // 0, //DUP2 // 0, //DUP2_X1 // 0, //DUP2_X2 // 0, //SWAP // 0, //IADD // 0, //LADD // 0, //FADD // 0, //DADD // 0, //ISUB // 0, //LSUB // 0, //FSUB // 0, //DSUB // 0, //IMUL // 0, //LMUL // 0, //FMUL // 0, //DMUL // 0, //IDIV // 0, //LDIV // 0, //FDIV // 0, //DDIV // 0, //IREM // 0, //LREM // 0, //FREM // 0, //DREM // 0, //INEG // 0, //LNEG // 0, //FNEG // 0, //DNEG // 0, //ISHL // 0, //LSHL // 0, //ISHR // 0, //LSHR // 0, //IUSHR // 0, //LUSHR // 0, //IAND // 0, //LAND // 0, //IOR // 0, //LOR // 0, //IXOR // 0, //LXOR // 8, //IINC // 0, //I2L // 0, //I2F // 0, //I2D // 0, //L2I // 0, //L2F // 0, //L2D // 0, //F2I // 0, //F2L // 0, //F2D // 0, //D2I // 0, //D2L // 0, //D2F // 0, //I2B // 0, //I2C // 0, //I2S // 0, //LCMP // 0, //FCMPL // 0, //FCMPG // 0, //DCMPL // 0, //DCMPG // 6, //IFEQ // 6, //IFNE // 6, //IFLT // 6, //IFGE // 6, //IFGT // 6, //IFLE // 6, //IF_ICMPEQ // 6, //IF_ICMPNE // 6, //IF_ICMPLT // 6, //IF_ICMPGE // 6, //IF_ICMPGT // 6, //IF_ICMPLE // 6, //IF_ACMPEQ // 6, //IF_ACMPNE // 6, //GOTO // 6, //JSR // 2, //RET // 9, //TABLESWITCH // 10, //LOOKUPSWITCH // 0, //IRETURN // 0, //LRETURN // 0, //FRETURN // 0, //DRETURN // 0, //ARETURN // 0, //RETURN // 4, //GETSTATIC // 4, //PUTSTATIC // 4, //GETFIELD // 4, //PUTFIELD // 5, //INVOKEVIRTUAL // 5, //INVOKESPECIAL // 5, //INVOKESTATIC // 5, //INVOKEINTERFACE // -1, //UNUSED // 3, //NEW // 1, //NEWARRAY // 3, //ANEWARRAY // 0, //ARRAYLENGTH // 0, //ATHROW // 3, //CHECKCAST // 3, //INSTANCEOF // 0, //MONITORENTER // 0, //MONITOREXIT // -1, //WIDE // 11, //MULTIANEWARRAY // 6, //IFNULL // 6, //IFNONNULL // -1, //GOTO_W // -1 //JSR_W // }; // for (int i = 0; i < TYPE.length; ++i) { // System.out.print((char)(TYPE[i] + 1 + 'A')); // } // System.out.println(); // } /** * Constructs a new {@link CheckMethodAdapter} object. * * @param cv the code visitor to which this adapter must delegate calls. */ public CheckMethodAdapter(final MethodVisitor cv) { super(cv); this.labels = new HashMap(); } public AnnotationVisitor visitAnnotation( final String desc, final boolean visible) { checkEndMethod(); checkDesc(desc, false); return new CheckAnnotationAdapter(mv.visitAnnotation(desc, visible)); } public AnnotationVisitor visitAnnotationDefault() { checkEndMethod(); return new CheckAnnotationAdapter(mv.visitAnnotationDefault(), false); } public AnnotationVisitor visitParameterAnnotation( final int parameter, final String desc, final boolean visible) { checkEndMethod(); checkDesc(desc, false); return new CheckAnnotationAdapter(mv.visitParameterAnnotation(parameter, desc, visible)); } public void visitAttribute(final Attribute attr) { checkEndMethod(); if (attr == null) { throw new IllegalArgumentException("Invalid attribute (must not be null)"); } mv.visitAttribute(attr); } public void visitCode() { startCode = true; mv.visitCode(); } public void visitFrame( final int type, final int nLocal, final Object[] local, final int nStack, final Object[] stack) { int mLocal; int mStack; switch (type) { case Opcodes.F_NEW: case Opcodes.F_FULL: mLocal = Integer.MAX_VALUE; mStack = Integer.MAX_VALUE; break; case Opcodes.F_SAME: mLocal = 0; mStack = 0; break; case Opcodes.F_SAME1: mLocal = 0; mStack = 1; break; case Opcodes.F_APPEND: case Opcodes.F_CHOP: mLocal = 3; mStack = 0; break; default: throw new IllegalArgumentException("Invalid frame type " + type); } if (nLocal > mLocal) { throw new IllegalArgumentException("Invalid nLocal=" + nLocal + " for frame type " + type); } if (nStack > mStack) { throw new IllegalArgumentException("Invalid nStack=" + nStack + " for frame type " + type); } if (type != Opcodes.F_CHOP) { if (nLocal > 0 && (local == null || local.length < nLocal)) { throw new IllegalArgumentException("Array local[] is shorter than nLocal"); } for (int i = 0; i < nLocal; ++i) { checkFrameValue(local[i]); } } if (nStack > 0 && (stack == null || stack.length < nStack)) { throw new IllegalArgumentException("Array stack[] is shorter than nStack"); } for (int i = 0; i < nStack; ++i) { checkFrameValue(stack[i]); } mv.visitFrame(type, nLocal, local, nStack, stack); } public void visitInsn(final int opcode) { checkStartCode(); checkEndCode(); checkOpcode(opcode, 0); mv.visitInsn(opcode); } public void visitIntInsn(final int opcode, final int operand) { checkStartCode(); checkEndCode(); checkOpcode(opcode, 1); switch (opcode) { case Opcodes.BIPUSH: checkSignedByte(operand, "Invalid operand"); break; case Opcodes.SIPUSH: checkSignedShort(operand, "Invalid operand"); break; // case Constants.NEWARRAY: default: if (operand < Opcodes.T_BOOLEAN || operand > Opcodes.T_LONG) { throw new IllegalArgumentException("Invalid operand (must be an array type code T_...): " + operand); } } mv.visitIntInsn(opcode, operand); } public void visitVarInsn(final int opcode, final int var) { checkStartCode(); checkEndCode(); checkOpcode(opcode, 2); checkUnsignedShort(var, "Invalid variable index"); mv.visitVarInsn(opcode, var); } public void visitTypeInsn(final int opcode, final String type) { checkStartCode(); checkEndCode(); checkOpcode(opcode, 3); checkInternalName(type, "type"); if (opcode == Opcodes.NEW && type.charAt(0) == '[') { throw new IllegalArgumentException("NEW cannot be used to create arrays: " + type); } mv.visitTypeInsn(opcode, type); } public void visitFieldInsn( final int opcode, final String owner, final String name, final String desc) { checkStartCode(); checkEndCode(); checkOpcode(opcode, 4); checkInternalName(owner, "owner"); checkIdentifier(name, "name"); checkDesc(desc, false); mv.visitFieldInsn(opcode, owner, name, desc); } public void visitMethodInsn( final int opcode, final String owner, final String name, final String desc) { checkStartCode(); checkEndCode(); checkOpcode(opcode, 5); checkMethodIdentifier(name, "name"); checkInternalName(owner, "owner"); checkMethodDesc(desc); mv.visitMethodInsn(opcode, owner, name, desc); } public void visitJumpInsn(final int opcode, final Label label) { checkStartCode(); checkEndCode(); checkOpcode(opcode, 6); checkLabel(label, false, "label"); mv.visitJumpInsn(opcode, label); } public void visitLabel(final Label label) { checkStartCode(); checkEndCode(); checkLabel(label, false, "label"); if (labels.get(label) != null) { throw new IllegalArgumentException("Already visited label"); } labels.put(label, new Integer(labels.size())); mv.visitLabel(label); } public void visitLdcInsn(final Object cst) { checkStartCode(); checkEndCode(); if (!(cst instanceof Type)) { checkConstant(cst); } mv.visitLdcInsn(cst); } public void visitIincInsn(final int var, final int increment) { checkStartCode(); checkEndCode(); checkUnsignedShort(var, "Invalid variable index"); checkSignedShort(increment, "Invalid increment"); mv.visitIincInsn(var, increment); } public void visitTableSwitchInsn( final int min, final int max, final Label dflt, final Label[] labels) { checkStartCode(); checkEndCode(); if (max < min) { throw new IllegalArgumentException("Max = " + max + " must be greater than or equal to min = " + min); } checkLabel(dflt, false, "default label"); if (labels == null || labels.length != max - min + 1) { throw new IllegalArgumentException("There must be max - min + 1 labels"); } for (int i = 0; i < labels.length; ++i) { checkLabel(labels[i], false, "label at index " + i); } mv.visitTableSwitchInsn(min, max, dflt, labels); } public void visitLookupSwitchInsn( final Label dflt, final int[] keys, final Label[] labels) { checkEndCode(); checkStartCode(); checkLabel(dflt, false, "default label"); if (keys == null || labels == null || keys.length != labels.length) { throw new IllegalArgumentException("There must be the same number of keys and labels"); } for (int i = 0; i < labels.length; ++i) { checkLabel(labels[i], false, "label at index " + i); } mv.visitLookupSwitchInsn(dflt, keys, labels); } public void visitMultiANewArrayInsn(final String desc, final int dims) { checkStartCode(); checkEndCode(); checkDesc(desc, false); if (desc.charAt(0) != '[') { throw new IllegalArgumentException("Invalid descriptor (must be an array type descriptor): " + desc); } if (dims < 1) { throw new IllegalArgumentException("Invalid dimensions (must be greater than 0): " + dims); } if (dims > desc.lastIndexOf('[') + 1) { throw new IllegalArgumentException("Invalid dimensions (must not be greater than dims(desc)): " + dims); } mv.visitMultiANewArrayInsn(desc, dims); } public void visitTryCatchBlock( final Label start, final Label end, final Label handler, final String type) { checkStartCode(); checkEndCode(); if (type != null) { checkInternalName(type, "type"); } mv.visitTryCatchBlock(start, end, handler, type); } public void visitLocalVariable( final String name, final String desc, final String signature, final Label start, final Label end, final int index) { checkStartCode(); checkEndCode(); checkIdentifier(name, "name"); checkDesc(desc, false); checkLabel(start, true, "start label"); checkLabel(end, true, "end label"); checkUnsignedShort(index, "Invalid variable index"); int s = ((Integer) labels.get(start)).intValue(); int e = ((Integer) labels.get(end)).intValue(); if (e < s) { throw new IllegalArgumentException("Invalid start and end labels (end must be greater than start)"); } mv.visitLocalVariable(name, desc, signature, start, end, index); } public void visitLineNumber(final int line, final Label start) { checkStartCode(); checkEndCode(); checkUnsignedShort(line, "Invalid line number"); checkLabel(start, true, "start label"); mv.visitLineNumber(line, start); } public void visitMaxs(final int maxStack, final int maxLocals) { checkStartCode(); checkEndCode(); endCode = true; checkUnsignedShort(maxStack, "Invalid max stack"); checkUnsignedShort(maxLocals, "Invalid max locals"); mv.visitMaxs(maxStack, maxLocals); } public void visitEnd() { checkEndMethod(); endMethod = true; mv.visitEnd(); } // ------------------------------------------------------------------------- /** * Checks that the visitCode method has been called. */ void checkStartCode() { if (!startCode) { throw new IllegalStateException("Cannot visit instructions before visitCode has been called."); } } /** * Checks that the visitMaxs method has not been called. */ void checkEndCode() { if (endCode) { throw new IllegalStateException("Cannot visit instructions after visitMaxs has been called."); } } /** * Checks that the visitEnd method has not been called. */ void checkEndMethod() { if (endMethod) { throw new IllegalStateException("Cannot visit elements after visitEnd has been called."); } } /** * Checks a stack frame value. * * @param value the value to be checked. */ static void checkFrameValue(final Object value) { if (value == Opcodes.TOP || value == Opcodes.INTEGER || value == Opcodes.FLOAT || value == Opcodes.LONG || value == Opcodes.DOUBLE || value == Opcodes.NULL || value == Opcodes.UNINITIALIZED_THIS) { return; } if (value instanceof String) { checkInternalName((String) value, "Invalid stack frame value"); return; } if (!(value instanceof Label)) { throw new IllegalArgumentException("Invalid stack frame value: " + value); } } /** * Checks that the type of the given opcode is equal to the given type. * * @param opcode the opcode to be checked. * @param type the expected opcode type. */ static void checkOpcode(final int opcode, final int type) { if (opcode < 0 || opcode > 199 || TYPE[opcode] != type) { throw new IllegalArgumentException("Invalid opcode: " + opcode); } } /** * Checks that the given value is a signed byte. * * @param value the value to be checked. * @param msg an message to be used in case of error. */ static void checkSignedByte(final int value, final String msg) { if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) { throw new IllegalArgumentException(msg + " (must be a signed byte): " + value); } } /** * Checks that the given value is a signed short. * * @param value the value to be checked. * @param msg an message to be used in case of error. */ static void checkSignedShort(final int value, final String msg) { if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) { throw new IllegalArgumentException(msg + " (must be a signed short): " + value); } } /** * Checks that the given value is an unsigned short. * * @param value the value to be checked. * @param msg an message to be used in case of error. */ static void checkUnsignedShort(final int value, final String msg) { if (value < 0 || value > 65535) { throw new IllegalArgumentException(msg + " (must be an unsigned short): " + value); } } /** * Checks that the given value is an {@link Integer}, a{@link Float}, a * {@link Long}, a {@link Double} or a {@link String}. * * @param cst the value to be checked. */ static void checkConstant(final Object cst) { if (!(cst instanceof Integer) && !(cst instanceof Float) && !(cst instanceof Long) && !(cst instanceof Double) && !(cst instanceof String)) { throw new IllegalArgumentException("Invalid constant: " + cst); } } /** * Checks that the given string is a valid Java identifier. * * @param name the string to be checked. * @param msg a message to be used in case of error. */ static void checkIdentifier(final String name, final String msg) { checkIdentifier(name, 0, -1, msg); } /** * Checks that the given substring is a valid Java identifier. * * @param name the string to be checked. * @param start index of the first character of the identifier (inclusive). * @param end index of the last character of the identifier (exclusive). -1 * is equivalent to name.length() if name is not * null. * @param msg a message to be used in case of error. */ static void checkIdentifier( final String name, final int start, final int end, final String msg) { if (name == null || (end == -1 ? name.length() <= start : end <= start)) { throw new IllegalArgumentException("Invalid " + msg + " (must not be null or empty)"); } if (!Character.isJavaIdentifierStart(name.charAt(start))) { throw new IllegalArgumentException("Invalid " + msg + " (must be a valid Java identifier): " + name); } int max = end == -1 ? name.length() : end; for (int i = start + 1; i < max; ++i) { if (!Character.isJavaIdentifierPart(name.charAt(i))) { throw new IllegalArgumentException("Invalid " + msg + " (must be a valid Java identifier): " + name); } } } /** * Checks that the given string is a valid Java identifier or is equal to * '<init>' or '<clinit>'. * * @param name the string to be checked. * @param msg a message to be used in case of error. */ static void checkMethodIdentifier(final String name, final String msg) { if (name == null || name.length() == 0) { throw new IllegalArgumentException("Invalid " + msg + " (must not be null or empty)"); } if ("".equals(name) || "".equals(name)) { return; } if (!Character.isJavaIdentifierStart(name.charAt(0))) { throw new IllegalArgumentException("Invalid " + msg + " (must be a '', '' or a valid Java identifier): " + name); } for (int i = 1; i < name.length(); ++i) { if (!Character.isJavaIdentifierPart(name.charAt(i))) { throw new IllegalArgumentException("Invalid " + msg + " (must be '' or '' or a valid Java identifier): " + name); } } } /** * Checks that the given string is a valid internal class name. * * @param name the string to be checked. * @param msg a message to be used in case of error. */ static void checkInternalName(final String name, final String msg) { if (name == null || name.length() == 0) { throw new IllegalArgumentException("Invalid " + msg + " (must not be null or empty)"); } if (name.charAt(0) == '[') { checkDesc(name, false); } else { checkInternalName(name, 0, -1, msg); } } /** * Checks that the given substring is a valid internal class name. * * @param name the string to be checked. * @param start index of the first character of the identifier (inclusive). * @param end index of the last character of the identifier (exclusive). -1 * is equivalent to name.length() if name is not * null. * @param msg a message to be used in case of error. */ static void checkInternalName( final String name, final int start, final int end, final String msg) { int max = end == -1 ? name.length() : end; try { int begin = start; int slash; do { slash = name.indexOf('/', begin + 1); if (slash == -1 || slash > max) { slash = max; } checkIdentifier(name, begin, slash, null); begin = slash + 1; } while (slash != max); } catch (IllegalArgumentException _) { throw new IllegalArgumentException("Invalid " + msg + " (must be a fully qualified class name in internal form): " + name); } } /** * Checks that the given string is a valid type descriptor. * * @param desc the string to be checked. * @param canBeVoid true if V can be considered valid. */ static void checkDesc(final String desc, final boolean canBeVoid) { int end = checkDesc(desc, 0, canBeVoid); if (end != desc.length()) { throw new IllegalArgumentException("Invalid descriptor: " + desc); } } /** * Checks that a the given substring is a valid type descriptor. * * @param desc the string to be checked. * @param start index of the first character of the identifier (inclusive). * @param canBeVoid true if V can be considered valid. * @return the index of the last character of the type decriptor, plus one. */ static int checkDesc( final String desc, final int start, final boolean canBeVoid) { if (desc == null || start >= desc.length()) { throw new IllegalArgumentException("Invalid type descriptor (must not be null or empty)"); } int index; switch (desc.charAt(start)) { case 'V': if (canBeVoid) { return start + 1; } else { throw new IllegalArgumentException("Invalid descriptor: " + desc); } case 'Z': case 'C': case 'B': case 'S': case 'I': case 'F': case 'J': case 'D': return start + 1; case '[': index = start + 1; while (index < desc.length() && desc.charAt(index) == '[') { ++index; } if (index < desc.length()) { return checkDesc(desc, index, false); } else { throw new IllegalArgumentException("Invalid descriptor: " + desc); } case 'L': index = desc.indexOf(';', start); if (index == -1 || index - start < 2) { throw new IllegalArgumentException("Invalid descriptor: " + desc); } try { checkInternalName(desc, start + 1, index, null); } catch (IllegalArgumentException _) { throw new IllegalArgumentException("Invalid descriptor: " + desc); } return index + 1; default: throw new IllegalArgumentException("Invalid descriptor: " + desc); } } /** * Checks that the given string is a valid method descriptor. * * @param desc the string to be checked. */ static void checkMethodDesc(final String desc) { if (desc == null || desc.length() == 0) { throw new IllegalArgumentException("Invalid method descriptor (must not be null or empty)"); } if (desc.charAt(0) != '(' || desc.length() < 3) { throw new IllegalArgumentException("Invalid descriptor: " + desc); } int start = 1; if (desc.charAt(start) != ')') { do { if (desc.charAt(start) == 'V') { throw new IllegalArgumentException("Invalid descriptor: " + desc); } start = checkDesc(desc, start, false); } while (start < desc.length() && desc.charAt(start) != ')'); } start = checkDesc(desc, start + 1, true); if (start != desc.length()) { throw new IllegalArgumentException("Invalid descriptor: " + desc); } } /** * Checks a class signature. * * @param signature a string containing the signature that must be checked. */ static void checkClassSignature(final String signature) { // ClassSignature: // FormalTypeParameters? ClassTypeSignature ClassTypeSignature* int pos = 0; if (getChar(signature, 0) == '<') { pos = checkFormalTypeParameters(signature, pos); } pos = checkClassTypeSignature(signature, pos); while (getChar(signature, pos) == 'L') { pos = checkClassTypeSignature(signature, pos); } if (pos != signature.length()) { throw new IllegalArgumentException(signature + ": error at index " + pos); } } /** * Checks a method signature. * * @param signature a string containing the signature that must be checked. */ static void checkMethodSignature(final String signature) { // MethodTypeSignature: // FormalTypeParameters? ( TypeSignature* ) ( TypeSignature | V ) ( // ^ClassTypeSignature | ^TypeVariableSignature )* int pos = 0; if (getChar(signature, 0) == '<') { pos = checkFormalTypeParameters(signature, pos); } pos = checkChar('(', signature, pos); while ("ZCBSIFJDL[T".indexOf(getChar(signature, pos)) != -1) { pos = checkTypeSignature(signature, pos); } pos = checkChar(')', signature, pos); if (getChar(signature, pos) == 'V') { ++pos; } else { pos = checkTypeSignature(signature, pos); } while (getChar(signature, pos) == '^') { ++pos; if (getChar(signature, pos) == 'L') { pos = checkClassTypeSignature(signature, pos); } else { pos = checkTypeVariableSignature(signature, pos); } } if (pos != signature.length()) { throw new IllegalArgumentException(signature + ": error at index " + pos); } } /** * Checks a field signature. * * @param signature a string containing the signature that must be checked. */ static void checkFieldSignature(final String signature) { int pos = checkFieldTypeSignature(signature, 0); if (pos != signature.length()) { throw new IllegalArgumentException(signature + ": error at index " + pos); } } /** * Checks the formal type parameters of a class or method signature. * * @param signature a string containing the signature that must be checked. * @param pos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkFormalTypeParameters(final String signature, int pos) { // FormalTypeParameters: // < FormalTypeParameter+ > pos = checkChar('<', signature, pos); pos = checkFormalTypeParameter(signature, pos); while (getChar(signature, pos) != '>') { pos = checkFormalTypeParameter(signature, pos); } return pos + 1; } /** * Checks a formal type parameter of a class or method signature. * * @param signature a string containing the signature that must be checked. * @param pos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkFormalTypeParameter(final String signature, int pos) { // FormalTypeParameter: // Identifier : FieldTypeSignature? (: FieldTypeSignature)* pos = checkIdentifier(signature, pos); pos = checkChar(':', signature, pos); if ("L[T".indexOf(getChar(signature, pos)) != -1) { pos = checkFieldTypeSignature(signature, pos); } while (getChar(signature, pos) == ':') { pos = checkFieldTypeSignature(signature, pos + 1); } return pos; } /** * Checks a field type signature. * * @param signature a string containing the signature that must be checked. * @param pos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkFieldTypeSignature(final String signature, int pos) { // FieldTypeSignature: // ClassTypeSignature | ArrayTypeSignature | TypeVariableSignature // // ArrayTypeSignature: // [ TypeSignature switch (getChar(signature, pos)) { case 'L': return checkClassTypeSignature(signature, pos); case '[': return checkTypeSignature(signature, pos + 1); default: return checkTypeVariableSignature(signature, pos); } } /** * Checks a class type signature. * * @param signature a string containing the signature that must be checked. * @param pos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkClassTypeSignature(final String signature, int pos) { // ClassTypeSignature: // L Identifier ( / Identifier )* TypeArguments? ( . Identifier // TypeArguments? )* ; pos = checkChar('L', signature, pos); pos = checkIdentifier(signature, pos); while (getChar(signature, pos) == '/') { pos = checkIdentifier(signature, pos + 1); } if (getChar(signature, pos) == '<') { pos = checkTypeArguments(signature, pos); } while (getChar(signature, pos) == '.') { pos = checkIdentifier(signature, pos + 1); if (getChar(signature, pos) == '<') { pos = checkTypeArguments(signature, pos); } } return checkChar(';', signature, pos); } /** * Checks the type arguments in a class type signature. * * @param signature a string containing the signature that must be checked. * @param pos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkTypeArguments(final String signature, int pos) { // TypeArguments: // < TypeArgument+ > pos = checkChar('<', signature, pos); pos = checkTypeArgument(signature, pos); while (getChar(signature, pos) != '>') { pos = checkTypeArgument(signature, pos); } return pos + 1; } /** * Checks a type argument in a class type signature. * * @param signature a string containing the signature that must be checked. * @param pos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkTypeArgument(final String signature, int pos) { // TypeArgument: // * | ( ( + | - )? FieldTypeSignature ) char c = getChar(signature, pos); if (c == '*') { return pos + 1; } else if (c == '+' || c == '-') { pos++; } return checkFieldTypeSignature(signature, pos); } /** * Checks a type variable signature. * * @param signature a string containing the signature that must be checked. * @param pos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkTypeVariableSignature( final String signature, int pos) { // TypeVariableSignature: // T Identifier ; pos = checkChar('T', signature, pos); pos = checkIdentifier(signature, pos); return checkChar(';', signature, pos); } /** * Checks a type signature. * * @param signature a string containing the signature that must be checked. * @param pos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkTypeSignature(final String signature, int pos) { // TypeSignature: // Z | C | B | S | I | F | J | D | FieldTypeSignature switch (getChar(signature, pos)) { case 'Z': case 'C': case 'B': case 'S': case 'I': case 'F': case 'J': case 'D': return pos + 1; default: return checkFieldTypeSignature(signature, pos); } } /** * Checks an identifier. * * @param signature a string containing the signature that must be checked. * @param pos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkIdentifier(final String signature, int pos) { if (!Character.isJavaIdentifierStart(getChar(signature, pos))) { throw new IllegalArgumentException(signature + ": identifier expected at index " + pos); } ++pos; while (Character.isJavaIdentifierPart(getChar(signature, pos))) { ++pos; } return pos; } /** * Checks a single character. * * @param signature a string containing the signature that must be checked. * @param pos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkChar(final char c, final String signature, int pos) { if (getChar(signature, pos) == c) { return pos + 1; } throw new IllegalArgumentException(signature + ": '" + c + "' expected at index " + pos); } /** * Returns the signature car at the given index. * * @param signature a signature. * @param pos an index in signature. * @return the character at the given index, or 0 if there is no such * character. */ private static char getChar(final String signature, int pos) { return pos < signature.length() ? signature.charAt(pos) : (char) 0; } /** * Checks that the given label is not null. This method can also check that * the label has been visited. * * @param label the label to be checked. * @param checkVisited true to check that the label has been * visited. * @param msg a message to be used in case of error. */ void checkLabel( final Label label, final boolean checkVisited, final String msg) { if (label == null) { throw new IllegalArgumentException("Invalid " + msg + " (must not be null)"); } if (checkVisited && labels.get(label) == null) { throw new IllegalArgumentException("Invalid " + msg + " (must be visited first)"); } } }