/*** | |
* 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 <i>only</i> on its arguments - | |
* such as the fact that the given opcode is correct for a given visit method), | |
* but does <i>not</i> check the <i>sequence</i> of instructions. For example, | |
* in a method whose signature is <tt>void m ()</tt>, the invalid instruction | |
* IRETURN, or the invalid sequence IADD L2I will <i>not</i> be detected by | |
* this code adapter. | |
* | |
* @author Eric Bruneton | |
*/ | |
public class CheckMethodAdapter extends MethodAdapter { | |
/** | |
* <tt>true</tt> if the visitCode method has been called. | |
*/ | |
private boolean startCode; | |
/** | |
* <tt>true</tt> if the visitMaxs method has been called. | |
*/ | |
private boolean endCode; | |
/** | |
* <tt>true</tt> 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 <tt>name.length()</tt> if name is not | |
* <tt>null</tt>. | |
* @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 ("<init>".equals(name) || "<clinit>".equals(name)) { | |
return; | |
} | |
if (!Character.isJavaIdentifierStart(name.charAt(0))) { | |
throw new IllegalArgumentException("Invalid " | |
+ msg | |
+ " (must be a '<init>', '<clinit>' 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 '<init>' or '<clinit>' 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 <tt>name.length()</tt> if name is not | |
* <tt>null</tt>. | |
* @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 <tt>true</tt> if <tt>V</tt> 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 <tt>true</tt> if <tt>V</tt> 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 <tt>true</tt> 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)"); | |
} | |
} | |
} |