CheckMethodAdapter.java revision 422c005785a1a2bf1dbf1ae1e695d7b9d26a2158
1/***
2 * ASM: a very small and fast Java bytecode manipulation framework
3 * Copyright (c) 2000-2005 INRIA, France Telecom
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the copyright holders nor the names of its
15 *    contributors may be used to endorse or promote products derived from
16 *    this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
28 * THE POSSIBILITY OF SUCH DAMAGE.
29 */
30package org.objectweb.asm.util;
31
32import org.objectweb.asm.AnnotationVisitor;
33import org.objectweb.asm.Label;
34import org.objectweb.asm.MethodAdapter;
35import org.objectweb.asm.MethodVisitor;
36import org.objectweb.asm.Opcodes;
37import org.objectweb.asm.Attribute;
38import org.objectweb.asm.Type;
39
40import java.util.HashMap;
41
42/**
43 * A {@link MethodAdapter} that checks that its methods are properly used. More
44 * precisely this code adapter checks each instruction individually (i.e., each
45 * visit method checks some preconditions based <i>only</i> on its arguments -
46 * such as the fact that the given opcode is correct for a given visit method),
47 * but does <i>not</i> check the <i>sequence</i> of instructions. For example,
48 * in a method whose signature is <tt>void m ()</tt>, the invalid instruction
49 * IRETURN, or the invalid sequence IADD L2I will <i>not</i> be detected by
50 * this code adapter.
51 *
52 * @author Eric Bruneton
53 */
54public class CheckMethodAdapter extends MethodAdapter {
55
56    /**
57     * <tt>true</tt> if the visitCode method has been called.
58     */
59    private boolean startCode;
60
61    /**
62     * <tt>true</tt> if the visitMaxs method has been called.
63     */
64    private boolean endCode;
65
66    /**
67     * <tt>true</tt> if the visitEnd method has been called.
68     */
69    private boolean endMethod;
70
71    /**
72     * The already visited labels. This map associate Integer values to Label
73     * keys.
74     */
75    private HashMap labels;
76
77    /**
78     * Code of the visit method to be used for each opcode.
79     */
80    private final static int[] TYPE;
81
82    static {
83        String s = "BBBBBBBBBBBBBBBBCCIAADDDDDAAAAAAAAAAAAAAAAAAAABBBBBBBBDD"
84                + "DDDAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
85                + "BBBBBBBBBBBBBBBBBBBJBBBBBBBBBBBBBBBBBBBBHHHHHHHHHHHHHHHHD"
86                + "KLBBBBBBFFFFGGGGAECEBBEEBBAMHHAA";
87        TYPE = new int[s.length()];
88        for (int i = 0; i < TYPE.length; ++i) {
89            TYPE[i] = (s.charAt(i) - 'A' - 1);
90        }
91    }
92
93    // code to generate the above string
94    // public static void main (String[] args) {
95    // int[] TYPE = new int[] {
96    // 0, //NOP
97    // 0, //ACONST_NULL
98    // 0, //ICONST_M1
99    // 0, //ICONST_0
100    // 0, //ICONST_1
101    // 0, //ICONST_2
102    // 0, //ICONST_3
103    // 0, //ICONST_4
104    // 0, //ICONST_5
105    // 0, //LCONST_0
106    // 0, //LCONST_1
107    // 0, //FCONST_0
108    // 0, //FCONST_1
109    // 0, //FCONST_2
110    // 0, //DCONST_0
111    // 0, //DCONST_1
112    // 1, //BIPUSH
113    // 1, //SIPUSH
114    // 7, //LDC
115    // -1, //LDC_W
116    // -1, //LDC2_W
117    // 2, //ILOAD
118    // 2, //LLOAD
119    // 2, //FLOAD
120    // 2, //DLOAD
121    // 2, //ALOAD
122    // -1, //ILOAD_0
123    // -1, //ILOAD_1
124    // -1, //ILOAD_2
125    // -1, //ILOAD_3
126    // -1, //LLOAD_0
127    // -1, //LLOAD_1
128    // -1, //LLOAD_2
129    // -1, //LLOAD_3
130    // -1, //FLOAD_0
131    // -1, //FLOAD_1
132    // -1, //FLOAD_2
133    // -1, //FLOAD_3
134    // -1, //DLOAD_0
135    // -1, //DLOAD_1
136    // -1, //DLOAD_2
137    // -1, //DLOAD_3
138    // -1, //ALOAD_0
139    // -1, //ALOAD_1
140    // -1, //ALOAD_2
141    // -1, //ALOAD_3
142    // 0, //IALOAD
143    // 0, //LALOAD
144    // 0, //FALOAD
145    // 0, //DALOAD
146    // 0, //AALOAD
147    // 0, //BALOAD
148    // 0, //CALOAD
149    // 0, //SALOAD
150    // 2, //ISTORE
151    // 2, //LSTORE
152    // 2, //FSTORE
153    // 2, //DSTORE
154    // 2, //ASTORE
155    // -1, //ISTORE_0
156    // -1, //ISTORE_1
157    // -1, //ISTORE_2
158    // -1, //ISTORE_3
159    // -1, //LSTORE_0
160    // -1, //LSTORE_1
161    // -1, //LSTORE_2
162    // -1, //LSTORE_3
163    // -1, //FSTORE_0
164    // -1, //FSTORE_1
165    // -1, //FSTORE_2
166    // -1, //FSTORE_3
167    // -1, //DSTORE_0
168    // -1, //DSTORE_1
169    // -1, //DSTORE_2
170    // -1, //DSTORE_3
171    // -1, //ASTORE_0
172    // -1, //ASTORE_1
173    // -1, //ASTORE_2
174    // -1, //ASTORE_3
175    // 0, //IASTORE
176    // 0, //LASTORE
177    // 0, //FASTORE
178    // 0, //DASTORE
179    // 0, //AASTORE
180    // 0, //BASTORE
181    // 0, //CASTORE
182    // 0, //SASTORE
183    // 0, //POP
184    // 0, //POP2
185    // 0, //DUP
186    // 0, //DUP_X1
187    // 0, //DUP_X2
188    // 0, //DUP2
189    // 0, //DUP2_X1
190    // 0, //DUP2_X2
191    // 0, //SWAP
192    // 0, //IADD
193    // 0, //LADD
194    // 0, //FADD
195    // 0, //DADD
196    // 0, //ISUB
197    // 0, //LSUB
198    // 0, //FSUB
199    // 0, //DSUB
200    // 0, //IMUL
201    // 0, //LMUL
202    // 0, //FMUL
203    // 0, //DMUL
204    // 0, //IDIV
205    // 0, //LDIV
206    // 0, //FDIV
207    // 0, //DDIV
208    // 0, //IREM
209    // 0, //LREM
210    // 0, //FREM
211    // 0, //DREM
212    // 0, //INEG
213    // 0, //LNEG
214    // 0, //FNEG
215    // 0, //DNEG
216    // 0, //ISHL
217    // 0, //LSHL
218    // 0, //ISHR
219    // 0, //LSHR
220    // 0, //IUSHR
221    // 0, //LUSHR
222    // 0, //IAND
223    // 0, //LAND
224    // 0, //IOR
225    // 0, //LOR
226    // 0, //IXOR
227    // 0, //LXOR
228    // 8, //IINC
229    // 0, //I2L
230    // 0, //I2F
231    // 0, //I2D
232    // 0, //L2I
233    // 0, //L2F
234    // 0, //L2D
235    // 0, //F2I
236    // 0, //F2L
237    // 0, //F2D
238    // 0, //D2I
239    // 0, //D2L
240    // 0, //D2F
241    // 0, //I2B
242    // 0, //I2C
243    // 0, //I2S
244    // 0, //LCMP
245    // 0, //FCMPL
246    // 0, //FCMPG
247    // 0, //DCMPL
248    // 0, //DCMPG
249    // 6, //IFEQ
250    // 6, //IFNE
251    // 6, //IFLT
252    // 6, //IFGE
253    // 6, //IFGT
254    // 6, //IFLE
255    // 6, //IF_ICMPEQ
256    // 6, //IF_ICMPNE
257    // 6, //IF_ICMPLT
258    // 6, //IF_ICMPGE
259    // 6, //IF_ICMPGT
260    // 6, //IF_ICMPLE
261    // 6, //IF_ACMPEQ
262    // 6, //IF_ACMPNE
263    // 6, //GOTO
264    // 6, //JSR
265    // 2, //RET
266    // 9, //TABLESWITCH
267    // 10, //LOOKUPSWITCH
268    // 0, //IRETURN
269    // 0, //LRETURN
270    // 0, //FRETURN
271    // 0, //DRETURN
272    // 0, //ARETURN
273    // 0, //RETURN
274    // 4, //GETSTATIC
275    // 4, //PUTSTATIC
276    // 4, //GETFIELD
277    // 4, //PUTFIELD
278    // 5, //INVOKEVIRTUAL
279    // 5, //INVOKESPECIAL
280    // 5, //INVOKESTATIC
281    // 5, //INVOKEINTERFACE
282    // -1, //UNUSED
283    // 3, //NEW
284    // 1, //NEWARRAY
285    // 3, //ANEWARRAY
286    // 0, //ARRAYLENGTH
287    // 0, //ATHROW
288    // 3, //CHECKCAST
289    // 3, //INSTANCEOF
290    // 0, //MONITORENTER
291    // 0, //MONITOREXIT
292    // -1, //WIDE
293    // 11, //MULTIANEWARRAY
294    // 6, //IFNULL
295    // 6, //IFNONNULL
296    // -1, //GOTO_W
297    // -1 //JSR_W
298    // };
299    // for (int i = 0; i < TYPE.length; ++i) {
300    // System.out.print((char)(TYPE[i] + 1 + 'A'));
301    // }
302    // System.out.println();
303    // }
304
305    /**
306     * Constructs a new {@link CheckMethodAdapter} object.
307     *
308     * @param cv the code visitor to which this adapter must delegate calls.
309     */
310    public CheckMethodAdapter(final MethodVisitor cv) {
311        super(cv);
312        this.labels = new HashMap();
313    }
314
315    public AnnotationVisitor visitAnnotation(
316        final String desc,
317        final boolean visible)
318    {
319        checkEndMethod();
320        checkDesc(desc, false);
321        return new CheckAnnotationAdapter(mv.visitAnnotation(desc, visible));
322    }
323
324    public AnnotationVisitor visitAnnotationDefault() {
325        checkEndMethod();
326        return new CheckAnnotationAdapter(mv.visitAnnotationDefault(), false);
327    }
328
329    public AnnotationVisitor visitParameterAnnotation(
330        final int parameter,
331        final String desc,
332        final boolean visible)
333    {
334        checkEndMethod();
335        checkDesc(desc, false);
336        return new CheckAnnotationAdapter(mv.visitParameterAnnotation(parameter,
337                desc,
338                visible));
339    }
340
341    public void visitAttribute(final Attribute attr) {
342        checkEndMethod();
343        if (attr == null) {
344            throw new IllegalArgumentException("Invalid attribute (must not be null)");
345        }
346        mv.visitAttribute(attr);
347    }
348
349    public void visitCode() {
350        startCode = true;
351        mv.visitCode();
352    }
353
354    public void visitInsn(final int opcode) {
355        checkStartCode();
356        checkEndCode();
357        checkOpcode(opcode, 0);
358        mv.visitInsn(opcode);
359    }
360
361    public void visitIntInsn(final int opcode, final int operand) {
362        checkStartCode();
363        checkEndCode();
364        checkOpcode(opcode, 1);
365        switch (opcode) {
366            case Opcodes.BIPUSH:
367                checkSignedByte(operand, "Invalid operand");
368                break;
369            case Opcodes.SIPUSH:
370                checkSignedShort(operand, "Invalid operand");
371                break;
372            // case Constants.NEWARRAY:
373            default:
374                if (operand < Opcodes.T_BOOLEAN || operand > Opcodes.T_LONG) {
375                    throw new IllegalArgumentException("Invalid operand (must be an array type code T_...): "
376                            + operand);
377                }
378        }
379        mv.visitIntInsn(opcode, operand);
380    }
381
382    public void visitVarInsn(final int opcode, final int var) {
383        checkStartCode();
384        checkEndCode();
385        checkOpcode(opcode, 2);
386        checkUnsignedShort(var, "Invalid variable index");
387        mv.visitVarInsn(opcode, var);
388    }
389
390    public void visitTypeInsn(final int opcode, final String desc) {
391        checkStartCode();
392        checkEndCode();
393        checkOpcode(opcode, 3);
394        if (desc != null && desc.length() > 0 && desc.charAt(0) == '[') {
395            checkDesc(desc, false);
396        } else {
397            checkInternalName(desc, "type");
398        }
399        if (opcode == Opcodes.NEW && desc.charAt(0) == '[') {
400            throw new IllegalArgumentException("NEW cannot be used to create arrays: "
401                    + desc);
402        }
403        mv.visitTypeInsn(opcode, desc);
404    }
405
406    public void visitFieldInsn(
407        final int opcode,
408        final String owner,
409        final String name,
410        final String desc)
411    {
412        checkStartCode();
413        checkEndCode();
414        checkOpcode(opcode, 4);
415        checkInternalName(owner, "owner");
416        checkIdentifier(name, "name");
417        checkDesc(desc, false);
418        mv.visitFieldInsn(opcode, owner, name, desc);
419    }
420
421    public void visitMethodInsn(
422        final int opcode,
423        final String owner,
424        final String name,
425        final String desc)
426    {
427        checkStartCode();
428        checkEndCode();
429        checkOpcode(opcode, 5);
430        checkMethodIdentifier(name, "name");
431        if (!name.equals("clone")) {
432            // In JDK1.5, clone method can be called on array class descriptors
433            checkInternalName(owner, "owner");
434        }
435        checkMethodDesc(desc);
436        mv.visitMethodInsn(opcode, owner, name, desc);
437    }
438
439    public void visitJumpInsn(final int opcode, final Label label) {
440        checkStartCode();
441        checkEndCode();
442        checkOpcode(opcode, 6);
443        checkLabel(label, false, "label");
444        mv.visitJumpInsn(opcode, label);
445    }
446
447    public void visitLabel(final Label label) {
448        checkStartCode();
449        checkEndCode();
450        checkLabel(label, false, "label");
451        if (labels.get(label) != null) {
452            throw new IllegalArgumentException("Already visited label");
453        } else {
454            labels.put(label, new Integer(labels.size()));
455        }
456        mv.visitLabel(label);
457    }
458
459    public void visitLdcInsn(final Object cst) {
460        checkStartCode();
461        checkEndCode();
462        if (!(cst instanceof Type)) {
463            checkConstant(cst);
464        }
465        mv.visitLdcInsn(cst);
466    }
467
468    public void visitIincInsn(final int var, final int increment) {
469        checkStartCode();
470        checkEndCode();
471        checkUnsignedShort(var, "Invalid variable index");
472        checkSignedShort(increment, "Invalid increment");
473        mv.visitIincInsn(var, increment);
474    }
475
476    public void visitTableSwitchInsn(
477        final int min,
478        final int max,
479        final Label dflt,
480        final Label labels[])
481    {
482        checkStartCode();
483        checkEndCode();
484        if (max < min) {
485            throw new IllegalArgumentException("Max = " + max
486                    + " must be greater than or equal to min = " + min);
487        }
488        checkLabel(dflt, false, "default label");
489        if (labels == null || labels.length != max - min + 1) {
490            throw new IllegalArgumentException("There must be max - min + 1 labels");
491        }
492        for (int i = 0; i < labels.length; ++i) {
493            checkLabel(labels[i], false, "label at index " + i);
494        }
495        mv.visitTableSwitchInsn(min, max, dflt, labels);
496    }
497
498    public void visitLookupSwitchInsn(
499        final Label dflt,
500        final int keys[],
501        final Label labels[])
502    {
503        checkEndCode();
504        checkStartCode();
505        checkLabel(dflt, false, "default label");
506        if (keys == null || labels == null || keys.length != labels.length) {
507            throw new IllegalArgumentException("There must be the same number of keys and labels");
508       }
509        for (int i = 0; i < labels.length; ++i) {
510            checkLabel(labels[i], false, "label at index " + i);
511        }
512        mv.visitLookupSwitchInsn(dflt, keys, labels);
513    }
514
515    public void visitMultiANewArrayInsn(final String desc, final int dims) {
516        checkStartCode();
517        checkEndCode();
518        checkDesc(desc, false);
519        if (desc.charAt(0) != '[') {
520            throw new IllegalArgumentException("Invalid descriptor (must be an array type descriptor): "
521                    + desc);
522        }
523        if (dims < 1) {
524            throw new IllegalArgumentException("Invalid dimensions (must be greater than 0): "
525                    + dims);
526        }
527        if (dims > desc.lastIndexOf('[') + 1) {
528            throw new IllegalArgumentException("Invalid dimensions (must not be greater than dims(desc)): "
529                    + dims);
530        }
531        mv.visitMultiANewArrayInsn(desc, dims);
532    }
533
534    public void visitTryCatchBlock(
535        final Label start,
536        final Label end,
537        final Label handler,
538        final String type)
539    {
540        checkStartCode();
541        checkEndCode();
542        if (type != null) {
543            checkInternalName(type, "type");
544        }
545        mv.visitTryCatchBlock(start, end, handler, type);
546    }
547
548    public void visitLocalVariable(
549        final String name,
550        final String desc,
551        final String signature,
552        final Label start,
553        final Label end,
554        final int index)
555    {
556        checkStartCode();
557        checkEndCode();
558        checkIdentifier(name, "name");
559        checkDesc(desc, false);
560        checkLabel(start, true, "start label");
561        checkLabel(end, true, "end label");
562        checkUnsignedShort(index, "Invalid variable index");
563        int s = ((Integer) labels.get(start)).intValue();
564        int e = ((Integer) labels.get(end)).intValue();
565        if (e < s) {
566            throw new IllegalArgumentException("Invalid start and end labels (end must be greater than start)");
567        }
568        mv.visitLocalVariable(name, desc, signature, start, end, index);
569    }
570
571    public void visitLineNumber(final int line, final Label start) {
572        checkStartCode();
573        checkEndCode();
574        checkUnsignedShort(line, "Invalid line number");
575        checkLabel(start, true, "start label");
576        mv.visitLineNumber(line, start);
577    }
578
579    public void visitMaxs(final int maxStack, final int maxLocals) {
580        checkStartCode();
581        checkEndCode();
582        endCode = true;
583        checkUnsignedShort(maxStack, "Invalid max stack");
584        checkUnsignedShort(maxLocals, "Invalid max locals");
585        mv.visitMaxs(maxStack, maxLocals);
586    }
587
588    public void visitEnd() {
589        checkEndMethod();
590        endMethod = true;
591        mv.visitEnd();
592    }
593
594    // -------------------------------------------------------------------------
595
596    /**
597     * Checks that the visitCode method has been called.
598     */
599    void checkStartCode() {
600        if (!startCode) {
601            throw new IllegalStateException("Cannot visit instructions before visitCode has been called.");
602        }
603    }
604
605    /**
606     * Checks that the visitMaxs method has not been called.
607     */
608    void checkEndCode() {
609        if (endCode) {
610            throw new IllegalStateException("Cannot visit instructions after visitMaxs has been called.");
611        }
612    }
613
614    /**
615     * Checks that the visitEnd method has not been called.
616     */
617    void checkEndMethod() {
618        if (endMethod) {
619            throw new IllegalStateException("Cannot visit elements after visitEnd has been called.");
620        }
621    }
622
623    /**
624     * Checks that the type of the given opcode is equal to the given type.
625     *
626     * @param opcode the opcode to be checked.
627     * @param type the expected opcode type.
628     */
629    static void checkOpcode(final int opcode, final int type) {
630        if (opcode < 0 || opcode > 199 || TYPE[opcode] != type) {
631            throw new IllegalArgumentException("Invalid opcode: " + opcode);
632        }
633    }
634
635    /**
636     * Checks that the given value is a signed byte.
637     *
638     * @param value the value to be checked.
639     * @param msg an message to be used in case of error.
640     */
641    static void checkSignedByte(final int value, final String msg) {
642        if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
643            throw new IllegalArgumentException(msg
644                    + " (must be a signed byte): " + value);
645        }
646    }
647
648    /**
649     * Checks that the given value is a signed short.
650     *
651     * @param value the value to be checked.
652     * @param msg an message to be used in case of error.
653     */
654    static void checkSignedShort(final int value, final String msg) {
655        if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
656            throw new IllegalArgumentException(msg
657                    + " (must be a signed short): " + value);
658        }
659    }
660
661    /**
662     * Checks that the given value is an unsigned short.
663     *
664     * @param value the value to be checked.
665     * @param msg an message to be used in case of error.
666     */
667    static void checkUnsignedShort(final int value, final String msg) {
668        if (value < 0 || value > 65535) {
669            throw new IllegalArgumentException(msg
670                    + " (must be an unsigned short): " + value);
671        }
672    }
673
674    /**
675     * Checks that the given value is an {@link Integer}, a{@link Float}, a
676     * {@link Long}, a {@link Double} or a {@link String}.
677     *
678     * @param cst the value to be checked.
679     */
680    static void checkConstant(final Object cst) {
681        if (!(cst instanceof Integer) && !(cst instanceof Float)
682                && !(cst instanceof Long) && !(cst instanceof Double)
683                && !(cst instanceof String))
684        {
685            throw new IllegalArgumentException("Invalid constant: " + cst);
686        }
687    }
688
689    /**
690     * Checks that the given string is a valid Java identifier.
691     *
692     * @param name the string to be checked.
693     * @param msg a message to be used in case of error.
694     */
695    static void checkIdentifier(final String name, final String msg) {
696        checkIdentifier(name, 0, -1, msg);
697    }
698
699    /**
700     * Checks that the given substring is a valid Java identifier.
701     *
702     * @param name the string to be checked.
703     * @param start index of the first character of the identifier (inclusive).
704     * @param end index of the last character of the identifier (exclusive). -1
705     *        is equivalent to <tt>name.length()</tt> if name is not
706     *        <tt>null</tt>.
707     * @param msg a message to be used in case of error.
708     */
709    static void checkIdentifier(
710        final String name,
711        final int start,
712        final int end,
713        final String msg)
714    {
715        if (name == null || (end == -1 ? name.length() <= start : end <= start))
716        {
717            throw new IllegalArgumentException("Invalid " + msg
718                    + " (must not be null or empty)");
719        }
720        if (!Character.isJavaIdentifierStart(name.charAt(start))) {
721            throw new IllegalArgumentException("Invalid " + msg
722                    + " (must be a valid Java identifier): " + name);
723        }
724        int max = (end == -1 ? name.length() : end);
725        for (int i = start + 1; i < max; ++i) {
726            if (!Character.isJavaIdentifierPart(name.charAt(i))) {
727                throw new IllegalArgumentException("Invalid " + msg
728                        + " (must be a valid Java identifier): " + name);
729            }
730        }
731    }
732
733    /**
734     * Checks that the given string is a valid Java identifier or is equal to
735     * '&lt;init&gt;' or '&lt;clinit&gt;'.
736     *
737     * @param name the string to be checked.
738     * @param msg a message to be used in case of error.
739     */
740    static void checkMethodIdentifier(final String name, final String msg) {
741        if (name == null || name.length() == 0) {
742            throw new IllegalArgumentException("Invalid " + msg
743                    + " (must not be null or empty)");
744        }
745        if (name.equals("<init>") || name.equals("<clinit>")) {
746            return;
747        }
748        if (!Character.isJavaIdentifierStart(name.charAt(0))) {
749            throw new IllegalArgumentException("Invalid "
750                    + msg
751                    + " (must be a '<init>', '<clinit>' or a valid Java identifier): "
752                    + name);
753        }
754        for (int i = 1; i < name.length(); ++i) {
755            if (!Character.isJavaIdentifierPart(name.charAt(i))) {
756                throw new IllegalArgumentException("Invalid "
757                        + msg
758                        + " (must be '<init>' or '<clinit>' or a valid Java identifier): "
759                        + name);
760            }
761        }
762    }
763
764    /**
765     * Checks that the given string is a valid internal class name.
766     *
767     * @param name the string to be checked.
768     * @param msg a message to be used in case of error.
769     */
770    static void checkInternalName(final String name, final String msg) {
771        checkInternalName(name, 0, -1, msg);
772    }
773
774    /**
775     * Checks that the given substring is a valid internal class name.
776     *
777     * @param name the string to be checked.
778     * @param start index of the first character of the identifier (inclusive).
779     * @param end index of the last character of the identifier (exclusive). -1
780     *        is equivalent to <tt>name.length()</tt> if name is not
781     *        <tt>null</tt>.
782     * @param msg a message to be used in case of error.
783     */
784    static void checkInternalName(
785        final String name,
786        final int start,
787        final int end,
788        final String msg)
789    {
790        if (name == null || name.length() == 0) {
791            throw new IllegalArgumentException("Invalid " + msg
792                    + " (must not be null or empty)");
793        }
794        int max = (end == -1 ? name.length() : end);
795        try {
796            int begin = start;
797            int slash;
798            do {
799                slash = name.indexOf('/', begin + 1);
800                if (slash == -1 || slash > max) {
801                    slash = max;
802                }
803                checkIdentifier(name, begin, slash, null);
804                begin = slash + 1;
805            } while (slash != max);
806        } catch (IllegalArgumentException ex) {
807            throw new IllegalArgumentException("Invalid "
808                    + msg
809                    + " (must be a fully qualified class name in internal form): "
810                    + name);
811        }
812    }
813
814    /**
815     * Checks that the given string is a valid type descriptor.
816     *
817     * @param desc the string to be checked.
818     * @param canBeVoid <tt>true</tt> if <tt>V</tt> can be considered valid.
819     */
820    static void checkDesc(final String desc, final boolean canBeVoid) {
821        int end = checkDesc(desc, 0, canBeVoid);
822        if (end != desc.length()) {
823            throw new IllegalArgumentException("Invalid descriptor: " + desc);
824        }
825    }
826
827    /**
828     * Checks that a the given substring is a valid type descriptor.
829     *
830     * @param desc the string to be checked.
831     * @param start index of the first character of the identifier (inclusive).
832     * @param canBeVoid <tt>true</tt> if <tt>V</tt> can be considered valid.
833     * @return the index of the last character of the type decriptor, plus one.
834     */
835    static int checkDesc(
836        final String desc,
837        final int start,
838        final boolean canBeVoid)
839    {
840        if (desc == null || start >= desc.length()) {
841            throw new IllegalArgumentException("Invalid type descriptor (must not be null or empty)");
842        }
843        int index;
844        switch (desc.charAt(start)) {
845            case 'V':
846                if (canBeVoid) {
847                    return start + 1;
848                } else {
849                    throw new IllegalArgumentException("Invalid descriptor: "
850                            + desc);
851                }
852            case 'Z':
853            case 'C':
854            case 'B':
855            case 'S':
856            case 'I':
857            case 'F':
858            case 'J':
859            case 'D':
860                return start + 1;
861            case '[':
862                index = start + 1;
863                while (index < desc.length() && desc.charAt(index) == '[') {
864                    ++index;
865                }
866                if (index < desc.length()) {
867                    return checkDesc(desc, index, false);
868                } else {
869                    throw new IllegalArgumentException("Invalid descriptor: "
870                            + desc);
871                }
872            case 'L':
873                index = desc.indexOf(';', start);
874                if (index == -1 || index - start < 2) {
875                    throw new IllegalArgumentException("Invalid descriptor: "
876                            + desc);
877                }
878                try {
879                    checkInternalName(desc, start + 1, index, null);
880                } catch (IllegalArgumentException ex) {
881                    throw new IllegalArgumentException("Invalid descriptor: "
882                            + desc);
883                }
884                return index + 1;
885            default:
886                throw new IllegalArgumentException("Invalid descriptor: "
887                        + desc);
888        }
889    }
890
891    /**
892     * Checks that the given string is a valid method descriptor.
893     *
894     * @param desc the string to be checked.
895     */
896    static void checkMethodDesc(final String desc) {
897        if (desc == null || desc.length() == 0) {
898            throw new IllegalArgumentException("Invalid method descriptor (must not be null or empty)");
899        }
900        if (desc.charAt(0) != '(' || desc.length() < 3) {
901            throw new IllegalArgumentException("Invalid descriptor: " + desc);
902        }
903        int start = 1;
904        if (desc.charAt(start) != ')') {
905            do {
906                if (desc.charAt(start) == 'V') {
907                    throw new IllegalArgumentException("Invalid descriptor: "
908                            + desc);
909                }
910                start = checkDesc(desc, start, false);
911            } while (start < desc.length() && desc.charAt(start) != ')');
912        }
913        start = checkDesc(desc, start + 1, true);
914        if (start != desc.length()) {
915            throw new IllegalArgumentException("Invalid descriptor: " + desc);
916        }
917    }
918
919    /**
920     * Checks that the given label is not null. This method can also check that
921     * the label has been visited.
922     *
923     * @param label the label to be checked.
924     * @param checkVisited <tt>true</tt> to check that the label has been
925     *        visited.
926     * @param msg a message to be used in case of error.
927     */
928    void checkLabel(
929        final Label label,
930        final boolean checkVisited,
931        final String msg)
932    {
933        if (label == null) {
934            throw new IllegalArgumentException("Invalid " + msg
935                    + " (must not be null)");
936        }
937        if (checkVisited && labels.get(label) == null) {
938            throw new IllegalArgumentException("Invalid " + msg
939                    + " (must be visited first)");
940        }
941    }
942}
943