mirror of
https://github.com/2006-Scape/Parabot.git
synced 2026-07-03 08:39:09 +00:00
1398 lines
45 KiB
Java
1398 lines
45 KiB
Java
/***
|
|
* ASM: a very small and fast Java bytecode manipulation framework
|
|
* Copyright (c) 2000-2011 INRIA, France Telecom
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the copyright holders nor the names of its
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
package org.objectweb.asm.util;
|
|
|
|
import java.io.PrintWriter;
|
|
import java.io.StringWriter;
|
|
import java.lang.reflect.Field;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import org.objectweb.asm.AnnotationVisitor;
|
|
import org.objectweb.asm.Attribute;
|
|
import org.objectweb.asm.Handle;
|
|
import org.objectweb.asm.Label;
|
|
import org.objectweb.asm.MethodVisitor;
|
|
import org.objectweb.asm.Opcodes;
|
|
import org.objectweb.asm.Type;
|
|
import org.objectweb.asm.tree.MethodNode;
|
|
import org.objectweb.asm.tree.analysis.Analyzer;
|
|
import org.objectweb.asm.tree.analysis.BasicValue;
|
|
import org.objectweb.asm.tree.analysis.BasicVerifier;
|
|
|
|
/**
|
|
* A {@link org.objectweb.asm.MethodVisitor} that checks that its methods are properly used. More
|
|
* precisely this method 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. This adapter can also perform some basic data flow checks (more
|
|
* precisely those that can be performed without the full class hierarchy - see
|
|
* {@link org.objectweb.asm.tree.analysis.BasicVerifier}). For instance in a
|
|
* method whose signature is <tt>void m ()</tt>, the invalid instruction
|
|
* IRETURN, or the invalid sequence IADD L2I will be detected if the data flow
|
|
* checks are enabled. These checks are enabled by using the
|
|
* {@link #CheckMethodAdapter(int,String,String, org.objectweb.asm.MethodVisitor, java.util.Map)} constructor.
|
|
* They are not performed if any other constructor is used.
|
|
*
|
|
* @author Eric Bruneton
|
|
*/
|
|
public class CheckMethodAdapter extends MethodVisitor {
|
|
|
|
/**
|
|
* The class version number.
|
|
*/
|
|
public int version;
|
|
|
|
/**
|
|
* The access flags of the method.
|
|
*/
|
|
private int access;
|
|
|
|
/**
|
|
* <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;
|
|
|
|
/**
|
|
* Number of visited instructions.
|
|
*/
|
|
private int insnCount;
|
|
|
|
/**
|
|
* The already visited labels. This map associate Integer values to pseudo
|
|
* code offsets.
|
|
*/
|
|
private final Map<Label, Integer> labels;
|
|
|
|
/**
|
|
* The labels used in this method. Every used label must be visited with
|
|
* visitLabel before the end of the method (i.e. should be in #labels).
|
|
*/
|
|
private Set<Label> usedLabels;
|
|
|
|
/**
|
|
* Number of visited frames in expanded form.
|
|
*/
|
|
private int expandedFrames;
|
|
|
|
/**
|
|
* Number of visited frames in compressed form.
|
|
*/
|
|
private int compressedFrames;
|
|
|
|
/**
|
|
* Number of instructions before the last visited frame.
|
|
*/
|
|
private int lastFrame = -1;
|
|
|
|
/**
|
|
* The exception handler ranges. Each pair of list element contains the
|
|
* start and end labels of an exception handler block.
|
|
*/
|
|
private List<Label> handlers;
|
|
|
|
/**
|
|
* Code of the visit method to be used for each opcode.
|
|
*/
|
|
private static final int[] TYPE;
|
|
|
|
/**
|
|
* The Label.status field.
|
|
*/
|
|
private static Field labelStatusField;
|
|
|
|
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, //INVOKEDYNAMIC
|
|
// 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 org.objectweb.asm.util.CheckMethodAdapter} object. This method adapter
|
|
* will not perform any data flow check (see
|
|
* {@link #CheckMethodAdapter(int,String,String, org.objectweb.asm.MethodVisitor, java.util.Map)}).
|
|
* <i>Subclasses must not use this constructor</i>. Instead, they must use
|
|
* the {@link #CheckMethodAdapter(int, org.objectweb.asm.MethodVisitor, java.util.Map)} version.
|
|
*
|
|
* @param mv
|
|
* the method visitor to which this adapter must delegate calls.
|
|
*/
|
|
public CheckMethodAdapter(final MethodVisitor mv) {
|
|
this(mv, new HashMap<Label, Integer>());
|
|
}
|
|
|
|
/**
|
|
* Constructs a new {@link org.objectweb.asm.util.CheckMethodAdapter} object. This method adapter
|
|
* will not perform any data flow check (see
|
|
* {@link #CheckMethodAdapter(int,String,String, org.objectweb.asm.MethodVisitor, java.util.Map)}).
|
|
* <i>Subclasses must not use this constructor</i>. Instead, they must use
|
|
* the {@link #CheckMethodAdapter(int, org.objectweb.asm.MethodVisitor, java.util.Map)} version.
|
|
*
|
|
* @param mv
|
|
* the method visitor to which this adapter must delegate calls.
|
|
* @param labels
|
|
* a map of already visited labels (in other methods).
|
|
*/
|
|
public CheckMethodAdapter(final MethodVisitor mv,
|
|
final Map<Label, Integer> labels) {
|
|
this(Opcodes.ASM4, mv, labels);
|
|
}
|
|
|
|
/**
|
|
* Constructs a new {@link org.objectweb.asm.util.CheckMethodAdapter} object. This method adapter
|
|
* will not perform any data flow check (see
|
|
* {@link #CheckMethodAdapter(int,String,String, org.objectweb.asm.MethodVisitor, java.util.Map)}).
|
|
*
|
|
* @param mv
|
|
* the method visitor to which this adapter must delegate calls.
|
|
* @param labels
|
|
* a map of already visited labels (in other methods).
|
|
*/
|
|
protected CheckMethodAdapter(final int api, final MethodVisitor mv,
|
|
final Map<Label, Integer> labels) {
|
|
super(api, mv);
|
|
this.labels = labels;
|
|
this.usedLabels = new HashSet<Label>();
|
|
this.handlers = new ArrayList<Label>();
|
|
}
|
|
|
|
/**
|
|
* Constructs a new {@link org.objectweb.asm.util.CheckMethodAdapter} object. This method adapter
|
|
* will perform basic data flow checks. For instance in a method whose
|
|
* signature is <tt>void m ()</tt>, the invalid instruction IRETURN, or the
|
|
* invalid sequence IADD L2I will be detected.
|
|
*
|
|
* @param access
|
|
* the method's access flags.
|
|
* @param name
|
|
* the method's name.
|
|
* @param desc
|
|
* the method's descriptor (see {@link org.objectweb.asm.Type Type}).
|
|
* @param cmv
|
|
* the method visitor to which this adapter must delegate calls.
|
|
* @param labels
|
|
* a map of already visited labels (in other methods).
|
|
*/
|
|
public CheckMethodAdapter(final int access, final String name,
|
|
final String desc, final MethodVisitor cmv,
|
|
final Map<Label, Integer> labels) {
|
|
this(new MethodNode(access, name, desc, null, null) {
|
|
@Override
|
|
public void visitEnd() {
|
|
Analyzer<BasicValue> a = new Analyzer<BasicValue>(
|
|
new BasicVerifier());
|
|
try {
|
|
a.analyze("dummy", this);
|
|
} catch (Exception e) {
|
|
if (e instanceof IndexOutOfBoundsException
|
|
&& maxLocals == 0 && maxStack == 0) {
|
|
throw new RuntimeException(
|
|
"Data flow checking option requires valid, non zero maxLocals and maxStack values.");
|
|
}
|
|
e.printStackTrace();
|
|
StringWriter sw = new StringWriter();
|
|
PrintWriter pw = new PrintWriter(sw, true);
|
|
CheckClassAdapter.printAnalyzerResult(this, a, pw);
|
|
pw.close();
|
|
throw new RuntimeException(e.getMessage() + ' '
|
|
+ sw.toString());
|
|
}
|
|
accept(cmv);
|
|
}
|
|
}, labels);
|
|
this.access = access;
|
|
}
|
|
|
|
@Override
|
|
public AnnotationVisitor visitAnnotation(final String desc,
|
|
final boolean visible) {
|
|
checkEndMethod();
|
|
checkDesc(desc, false);
|
|
return new CheckAnnotationAdapter(super.visitAnnotation(desc, visible));
|
|
}
|
|
|
|
@Override
|
|
public AnnotationVisitor visitAnnotationDefault() {
|
|
checkEndMethod();
|
|
return new CheckAnnotationAdapter(super.visitAnnotationDefault(), false);
|
|
}
|
|
|
|
@Override
|
|
public AnnotationVisitor visitParameterAnnotation(final int parameter,
|
|
final String desc, final boolean visible) {
|
|
checkEndMethod();
|
|
checkDesc(desc, false);
|
|
return new CheckAnnotationAdapter(super.visitParameterAnnotation(
|
|
parameter, desc, visible));
|
|
}
|
|
|
|
@Override
|
|
public void visitAttribute(final Attribute attr) {
|
|
checkEndMethod();
|
|
if (attr == null) {
|
|
throw new IllegalArgumentException(
|
|
"Invalid attribute (must not be null)");
|
|
}
|
|
super.visitAttribute(attr);
|
|
}
|
|
|
|
@Override
|
|
public void visitCode() {
|
|
if ((access & Opcodes.ACC_ABSTRACT) != 0) {
|
|
throw new RuntimeException("Abstract methods cannot have code");
|
|
}
|
|
startCode = true;
|
|
super.visitCode();
|
|
}
|
|
|
|
@Override
|
|
public void visitFrame(final int type, final int nLocal,
|
|
final Object[] local, final int nStack, final Object[] stack) {
|
|
if (insnCount == lastFrame) {
|
|
throw new IllegalStateException(
|
|
"At most one frame can be visited at a given code location.");
|
|
}
|
|
lastFrame = insnCount;
|
|
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]);
|
|
}
|
|
if (type == Opcodes.F_NEW) {
|
|
++expandedFrames;
|
|
} else {
|
|
++compressedFrames;
|
|
}
|
|
if (expandedFrames > 0 && compressedFrames > 0) {
|
|
throw new RuntimeException(
|
|
"Expanded and compressed frames must not be mixed.");
|
|
}
|
|
super.visitFrame(type, nLocal, local, nStack, stack);
|
|
}
|
|
|
|
@Override
|
|
public void visitInsn(final int opcode) {
|
|
checkStartCode();
|
|
checkEndCode();
|
|
checkOpcode(opcode, 0);
|
|
super.visitInsn(opcode);
|
|
++insnCount;
|
|
}
|
|
|
|
@Override
|
|
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);
|
|
}
|
|
}
|
|
super.visitIntInsn(opcode, operand);
|
|
++insnCount;
|
|
}
|
|
|
|
@Override
|
|
public void visitVarInsn(final int opcode, final int var) {
|
|
checkStartCode();
|
|
checkEndCode();
|
|
checkOpcode(opcode, 2);
|
|
checkUnsignedShort(var, "Invalid variable index");
|
|
super.visitVarInsn(opcode, var);
|
|
++insnCount;
|
|
}
|
|
|
|
@Override
|
|
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);
|
|
}
|
|
super.visitTypeInsn(opcode, type);
|
|
++insnCount;
|
|
}
|
|
|
|
@Override
|
|
public void visitFieldInsn(final int opcode, final String owner,
|
|
final String name, final String desc) {
|
|
checkStartCode();
|
|
checkEndCode();
|
|
checkOpcode(opcode, 4);
|
|
checkInternalName(owner, "owner");
|
|
checkUnqualifiedName(version, name, "name");
|
|
checkDesc(desc, false);
|
|
super.visitFieldInsn(opcode, owner, name, desc);
|
|
++insnCount;
|
|
}
|
|
|
|
@Override
|
|
public void visitMethodInsn(final int opcode, final String owner,
|
|
final String name, final String desc) {
|
|
checkStartCode();
|
|
checkEndCode();
|
|
checkOpcode(opcode, 5);
|
|
if (opcode != Opcodes.INVOKESPECIAL || !"<init>".equals(name)) {
|
|
checkMethodIdentifier(version, name, "name");
|
|
}
|
|
checkInternalName(owner, "owner");
|
|
checkMethodDesc(desc);
|
|
super.visitMethodInsn(opcode, owner, name, desc);
|
|
++insnCount;
|
|
}
|
|
|
|
@Override
|
|
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm,
|
|
Object... bsmArgs) {
|
|
checkStartCode();
|
|
checkEndCode();
|
|
checkMethodIdentifier(version, name, "name");
|
|
checkMethodDesc(desc);
|
|
if (bsm.getTag() != Opcodes.H_INVOKESTATIC
|
|
&& bsm.getTag() != Opcodes.H_NEWINVOKESPECIAL) {
|
|
throw new IllegalArgumentException("invalid handle tag "
|
|
+ bsm.getTag());
|
|
}
|
|
for (int i = 0; i < bsmArgs.length; i++) {
|
|
checkLDCConstant(bsmArgs[i]);
|
|
}
|
|
super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
|
|
++insnCount;
|
|
}
|
|
|
|
@Override
|
|
public void visitJumpInsn(final int opcode, final Label label) {
|
|
checkStartCode();
|
|
checkEndCode();
|
|
checkOpcode(opcode, 6);
|
|
checkLabel(label, false, "label");
|
|
checkNonDebugLabel(label);
|
|
super.visitJumpInsn(opcode, label);
|
|
usedLabels.add(label);
|
|
++insnCount;
|
|
}
|
|
|
|
@Override
|
|
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(insnCount));
|
|
super.visitLabel(label);
|
|
}
|
|
|
|
@Override
|
|
public void visitLdcInsn(final Object cst) {
|
|
checkStartCode();
|
|
checkEndCode();
|
|
checkLDCConstant(cst);
|
|
super.visitLdcInsn(cst);
|
|
++insnCount;
|
|
}
|
|
|
|
@Override
|
|
public void visitIincInsn(final int var, final int increment) {
|
|
checkStartCode();
|
|
checkEndCode();
|
|
checkUnsignedShort(var, "Invalid variable index");
|
|
checkSignedShort(increment, "Invalid increment");
|
|
super.visitIincInsn(var, increment);
|
|
++insnCount;
|
|
}
|
|
|
|
@Override
|
|
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");
|
|
checkNonDebugLabel(dflt);
|
|
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);
|
|
checkNonDebugLabel(labels[i]);
|
|
}
|
|
super.visitTableSwitchInsn(min, max, dflt, labels);
|
|
for (int i = 0; i < labels.length; ++i) {
|
|
usedLabels.add(labels[i]);
|
|
}
|
|
++insnCount;
|
|
}
|
|
|
|
@Override
|
|
public void visitLookupSwitchInsn(final Label dflt, final int[] keys,
|
|
final Label[] labels) {
|
|
checkEndCode();
|
|
checkStartCode();
|
|
checkLabel(dflt, false, "default label");
|
|
checkNonDebugLabel(dflt);
|
|
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);
|
|
checkNonDebugLabel(labels[i]);
|
|
}
|
|
super.visitLookupSwitchInsn(dflt, keys, labels);
|
|
usedLabels.add(dflt);
|
|
for (int i = 0; i < labels.length; ++i) {
|
|
usedLabels.add(labels[i]);
|
|
}
|
|
++insnCount;
|
|
}
|
|
|
|
@Override
|
|
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);
|
|
}
|
|
super.visitMultiANewArrayInsn(desc, dims);
|
|
++insnCount;
|
|
}
|
|
|
|
@Override
|
|
public void visitTryCatchBlock(final Label start, final Label end,
|
|
final Label handler, final String type) {
|
|
checkStartCode();
|
|
checkEndCode();
|
|
checkLabel(start, false, "start label");
|
|
checkLabel(end, false, "end label");
|
|
checkLabel(handler, false, "handler label");
|
|
checkNonDebugLabel(start);
|
|
checkNonDebugLabel(end);
|
|
checkNonDebugLabel(handler);
|
|
if (labels.get(start) != null || labels.get(end) != null
|
|
|| labels.get(handler) != null) {
|
|
throw new IllegalStateException(
|
|
"Try catch blocks must be visited before their labels");
|
|
}
|
|
if (type != null) {
|
|
checkInternalName(type, "type");
|
|
}
|
|
super.visitTryCatchBlock(start, end, handler, type);
|
|
handlers.add(start);
|
|
handlers.add(end);
|
|
}
|
|
|
|
@Override
|
|
public void visitLocalVariable(final String name, final String desc,
|
|
final String signature, final Label start, final Label end,
|
|
final int index) {
|
|
checkStartCode();
|
|
checkEndCode();
|
|
checkUnqualifiedName(version, name, "name");
|
|
checkDesc(desc, false);
|
|
checkLabel(start, true, "start label");
|
|
checkLabel(end, true, "end label");
|
|
checkUnsignedShort(index, "Invalid variable index");
|
|
int s = labels.get(start).intValue();
|
|
int e = labels.get(end).intValue();
|
|
if (e < s) {
|
|
throw new IllegalArgumentException(
|
|
"Invalid start and end labels (end must be greater than start)");
|
|
}
|
|
super.visitLocalVariable(name, desc, signature, start, end, index);
|
|
}
|
|
|
|
@Override
|
|
public void visitLineNumber(final int line, final Label start) {
|
|
checkStartCode();
|
|
checkEndCode();
|
|
checkUnsignedShort(line, "Invalid line number");
|
|
checkLabel(start, true, "start label");
|
|
super.visitLineNumber(line, start);
|
|
}
|
|
|
|
@Override
|
|
public void visitMaxs(final int maxStack, final int maxLocals) {
|
|
checkStartCode();
|
|
checkEndCode();
|
|
endCode = true;
|
|
for (Label l : usedLabels) {
|
|
if (labels.get(l) == null) {
|
|
throw new IllegalStateException("Undefined label used");
|
|
}
|
|
}
|
|
for (int i = 0; i < handlers.size();) {
|
|
Integer start = labels.get(handlers.get(i++));
|
|
Integer end = labels.get(handlers.get(i++));
|
|
if (start == null || end == null) {
|
|
throw new IllegalStateException(
|
|
"Undefined try catch block labels");
|
|
}
|
|
if (end.intValue() <= start.intValue()) {
|
|
throw new IllegalStateException(
|
|
"Emty try catch block handler range");
|
|
}
|
|
}
|
|
checkUnsignedShort(maxStack, "Invalid max stack");
|
|
checkUnsignedShort(maxLocals, "Invalid max locals");
|
|
super.visitMaxs(maxStack, maxLocals);
|
|
}
|
|
|
|
@Override
|
|
public void visitEnd() {
|
|
checkEndMethod();
|
|
endMethod = true;
|
|
super.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.
|
|
*/
|
|
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);
|
|
} else {
|
|
usedLabels.add((Label) 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);
|
|
}
|
|
}
|
|
|
|
void checkLDCConstant(final Object cst) {
|
|
if (cst instanceof Type) {
|
|
int s = ((Type) cst).getSort();
|
|
if (s != Type.OBJECT && s != Type.ARRAY && s != Type.METHOD) {
|
|
throw new IllegalArgumentException("Illegal LDC constant value");
|
|
}
|
|
if (s != Type.METHOD && (version & 0xFFFF) < Opcodes.V1_5) {
|
|
throw new IllegalArgumentException(
|
|
"ldc of a constant class requires at least version 1.5");
|
|
}
|
|
if (s == Type.METHOD && (version & 0xFFFF) < Opcodes.V1_7) {
|
|
throw new IllegalArgumentException(
|
|
"ldc of a method type requires at least version 1.7");
|
|
}
|
|
} else if (cst instanceof Handle) {
|
|
if ((version & 0xFFFF) < Opcodes.V1_7) {
|
|
throw new IllegalArgumentException(
|
|
"ldc of a handle requires at least version 1.7");
|
|
}
|
|
int tag = ((Handle) cst).getTag();
|
|
if (tag < Opcodes.H_GETFIELD || tag > Opcodes.H_INVOKEINTERFACE) {
|
|
throw new IllegalArgumentException("invalid handle tag " + tag);
|
|
}
|
|
} else {
|
|
checkConstant(cst);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks that the given string is a valid unqualified name.
|
|
*
|
|
* @param version
|
|
* the class version.
|
|
* @param name
|
|
* the string to be checked.
|
|
* @param msg
|
|
* a message to be used in case of error.
|
|
*/
|
|
static void checkUnqualifiedName(int version, final String name,
|
|
final String msg) {
|
|
if ((version & 0xFFFF) < Opcodes.V1_5) {
|
|
checkIdentifier(name, msg);
|
|
} else {
|
|
for (int i = 0; i < name.length(); ++i) {
|
|
if (".;[/".indexOf(name.charAt(i)) != -1) {
|
|
throw new IllegalArgumentException("Invalid " + msg
|
|
+ " (must be a valid unqualified name): " + name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @param version
|
|
* the class version.
|
|
* @param name
|
|
* the string to be checked.
|
|
* @param msg
|
|
* a message to be used in case of error.
|
|
*/
|
|
static void checkMethodIdentifier(int version, final String name,
|
|
final String msg) {
|
|
if (name == null || name.length() == 0) {
|
|
throw new IllegalArgumentException("Invalid " + msg
|
|
+ " (must not be null or empty)");
|
|
}
|
|
if ((version & 0xFFFF) >= Opcodes.V1_5) {
|
|
for (int i = 0; i < name.length(); ++i) {
|
|
if (".;[/<>".indexOf(name.charAt(i)) != -1) {
|
|
throw new IllegalArgumentException("Invalid " + msg
|
|
+ " (must be a valid unqualified name): " + 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 ignored) {
|
|
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 ignored) {
|
|
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 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)");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks that the given label is not a label used only for debug purposes.
|
|
*
|
|
* @param label
|
|
* the label to be checked.
|
|
*/
|
|
private static void checkNonDebugLabel(final Label label) {
|
|
Field f = getLabelStatusField();
|
|
int status = 0;
|
|
try {
|
|
status = f == null ? 0 : ((Integer) f.get(label)).intValue();
|
|
} catch (IllegalAccessException e) {
|
|
throw new Error("Internal error");
|
|
}
|
|
if ((status & 0x01) != 0) {
|
|
throw new IllegalArgumentException(
|
|
"Labels used for debug info cannot be reused for control flow");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the Field object corresponding to the Label.status field.
|
|
*
|
|
* @return the Field object corresponding to the Label.status field.
|
|
*/
|
|
private static Field getLabelStatusField() {
|
|
if (labelStatusField == null) {
|
|
labelStatusField = getLabelField("a");
|
|
if (labelStatusField == null) {
|
|
labelStatusField = getLabelField("status");
|
|
}
|
|
}
|
|
return labelStatusField;
|
|
}
|
|
|
|
/**
|
|
* Returns the field of the Label class whose name is given.
|
|
*
|
|
* @param name
|
|
* a field name.
|
|
* @return the field of the Label class whose name is given, or null.
|
|
*/
|
|
private static Field getLabelField(final String name) {
|
|
try {
|
|
Field f = Label.class.getDeclaredField(name);
|
|
f.setAccessible(true);
|
|
return f;
|
|
} catch (NoSuchFieldException e) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|