| /*** |
| * 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.eclipse.persistence.internal.libraries.asm.util; |
| |
| import org.eclipse.persistence.internal.libraries.asm.AnnotationVisitor; |
| import org.eclipse.persistence.internal.libraries.asm.Attribute; |
| import org.eclipse.persistence.internal.libraries.asm.Label; |
| import org.eclipse.persistence.internal.libraries.asm.MethodAdapter; |
| import org.eclipse.persistence.internal.libraries.asm.MethodVisitor; |
| import org.eclipse.persistence.internal.libraries.asm.Opcodes; |
| import org.eclipse.persistence.internal.libraries.asm.Type; |
| import org.eclipse.persistence.internal.libraries.asm.tree.MethodNode; |
| import org.eclipse.persistence.internal.libraries.asm.tree.analysis.Analyzer; |
| import org.eclipse.persistence.internal.libraries.asm.tree.analysis.BasicVerifier; |
| |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.lang.reflect.Field; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| /** |
| * A {@link MethodAdapter} 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.eclipse.persistence.internal.libraries.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,MethodVisitor,Map)} constructor. They |
| * are not performed if any other constructor is used. |
| * |
| * @author Eric Bruneton |
| */ |
| public class CheckMethodAdapter extends MethodAdapter { |
| |
| /** |
| * The class version number. |
| */ |
| public int version; |
| |
| /** |
| * <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; |
| |
| /** |
| * The Label.status field. |
| */ |
| private static Field labelStatusField; |
| |
| static { |
| String s = "BBBBBBBBBBBBBBBBCCIAADDDDDAAAAAAAAAAAAAAAAAAAABBBBBBBBDD" |
| + "DDDAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" |
| + "BBBBBBBBBBBBBBBBBBBJBBBBBBBBBBBBBBBBBBBBHHHHHHHHHHHHHHHHD" |
| + "KLBBBBBBFFFFGGGGGECEBBEEBBAMHHAA"; |
| 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 |
| // 5, //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 CheckMethodAdapter} object. This method adapter |
| * will not perform any data flow check (see {@link |
| * CheckMethodAdapter(int,String,String,MethodVisitor,Map)}). |
| * |
| * @param mv the method visitor to which this adapter must delegate calls. |
| */ |
| public CheckMethodAdapter(final MethodVisitor mv) { |
| this(mv, new HashMap()); |
| } |
| |
| /** |
| * Constructs a new {@link CheckMethodAdapter} object. This method adapter |
| * will not perform any data flow check (see {@link |
| * CheckMethodAdapter(int,String,String,MethodVisitor,Map)}). |
| * |
| * @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 labels) { |
| super(mv); |
| this.labels = labels; |
| } |
| |
| /** |
| * Constructs a new {@link 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 Type Type}). |
| * @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 int access, |
| final String name, |
| final String desc, |
| final MethodVisitor mv, |
| final Map labels) |
| { |
| this(new MethodNode(access, name, desc, null, null) { |
| public void visitEnd() { |
| Analyzer a = new Analyzer(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(mv); |
| } |
| }, labels); |
| } |
| |
| 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"); |
| checkUnqualifiedName(version, 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(version, name, "name"); |
| checkInternalName(owner, "owner"); |
| checkMethodDesc(desc); |
| if (opcode == Opcodes.INVOKEDYNAMIC && owner != Opcodes.INVOKEDYNAMIC_OWNER) { |
| throw new IllegalArgumentException("INVOKEDYNAMIC cannot be used with another owner than INVOKEDYNAMIC_OWNER"); |
| } |
| mv.visitMethodInsn(opcode, owner, name, desc); |
| } |
| |
| public void visitJumpInsn(final int opcode, final Label label) { |
| checkStartCode(); |
| checkEndCode(); |
| checkOpcode(opcode, 6); |
| checkLabel(label, false, "label"); |
| checkNonDebugLabel(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"); |
| 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]); |
| } |
| 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"); |
| 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]); |
| } |
| 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(); |
| 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"); |
| } |
| 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(); |
| checkUnqualifiedName(version, 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 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 or is equal to |
| * '<init>' or '<clinit>'. |
| * |
| * @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 ("<init>".equals(name) || "<clinit>".equals(name)) { |
| return; |
| } |
| 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 _) { |
| 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)"); |
| } |
| } |
| |
| /** |
| * Checks that the given lavel 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; |
| } |
| } |
| } |