/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * This file is available under and governed by the GNU General Public
 * License version 2 only, as published by the Free Software Foundation.
 * However, the following notice accompanied the original version of this
 * file:
 *
 * 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 jdk.internal.org.objectweb.asm.util;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jdk.internal.org.objectweb.asm.AnnotationVisitor;
import jdk.internal.org.objectweb.asm.Attribute;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.ConstantDynamic;
import jdk.internal.org.objectweb.asm.Handle;
import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.TypePath;
import jdk.internal.org.objectweb.asm.TypeReference;
import jdk.internal.org.objectweb.asm.tree.MethodNode;
import jdk.internal.org.objectweb.asm.tree.analysis.Analyzer;
import jdk.internal.org.objectweb.asm.tree.analysis.AnalyzerException;
import jdk.internal.org.objectweb.asm.tree.analysis.BasicValue;
import jdk.internal.org.objectweb.asm.tree.analysis.BasicVerifier;

/**
 * A {@link 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
 * jdk.internal.org.objectweb.asm.tree.analysis.BasicVerifier}). For instance in a method whose signature is
 * {@code void m ()}, 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 MethodVisitor {

    /** The 'generic' instruction visit methods (i.e. those that take an opcode argument). */
    private enum Method {
        VISIT_INSN,
        VISIT_INT_INSN,
        VISIT_VAR_INSN,
        VISIT_TYPE_INSN,
        VISIT_FIELD_INSN,
        VISIT_METHOD_INSN,
        VISIT_JUMP_INSN
    }

    /** The method to use to visit each instruction. Only generic methods are represented here. */
    private static final Method[] OPCODE_METHODS = {
        Method.VISIT_INSN, // NOP
        Method.VISIT_INSN, // ACONST_NULL
        Method.VISIT_INSN, // ICONST_M1
        Method.VISIT_INSN, // ICONST_0
        Method.VISIT_INSN, // ICONST_1
        Method.VISIT_INSN, // ICONST_2
        Method.VISIT_INSN, // ICONST_3
        Method.VISIT_INSN, // ICONST_4
        Method.VISIT_INSN, // ICONST_5
        Method.VISIT_INSN, // LCONST_0
        Method.VISIT_INSN, // LCONST_1
        Method.VISIT_INSN, // FCONST_0
        Method.VISIT_INSN, // FCONST_1
        Method.VISIT_INSN, // FCONST_2
        Method.VISIT_INSN, // DCONST_0
        Method.VISIT_INSN, // DCONST_1
        Method.VISIT_INT_INSN, // BIPUSH
        Method.VISIT_INT_INSN, // SIPUSH
        null, // LDC
        null, // LDC_W
        null, // LDC2_W
        Method.VISIT_VAR_INSN, // ILOAD
        Method.VISIT_VAR_INSN, // LLOAD
        Method.VISIT_VAR_INSN, // FLOAD
        Method.VISIT_VAR_INSN, // DLOAD
        Method.VISIT_VAR_INSN, // ALOAD
        null, // ILOAD_0
        null, // ILOAD_1
        null, // ILOAD_2
        null, // ILOAD_3
        null, // LLOAD_0
        null, // LLOAD_1
        null, // LLOAD_2
        null, // LLOAD_3
        null, // FLOAD_0
        null, // FLOAD_1
        null, // FLOAD_2
        null, // FLOAD_3
        null, // DLOAD_0
        null, // DLOAD_1
        null, // DLOAD_2
        null, // DLOAD_3
        null, // ALOAD_0
        null, // ALOAD_1
        null, // ALOAD_2
        null, // ALOAD_3
        Method.VISIT_INSN, // IALOAD
        Method.VISIT_INSN, // LALOAD
        Method.VISIT_INSN, // FALOAD
        Method.VISIT_INSN, // DALOAD
        Method.VISIT_INSN, // AALOAD
        Method.VISIT_INSN, // BALOAD
        Method.VISIT_INSN, // CALOAD
        Method.VISIT_INSN, // SALOAD
        Method.VISIT_VAR_INSN, // ISTORE
        Method.VISIT_VAR_INSN, // LSTORE
        Method.VISIT_VAR_INSN, // FSTORE
        Method.VISIT_VAR_INSN, // DSTORE
        Method.VISIT_VAR_INSN, // ASTORE
        null, // ISTORE_0
        null, // ISTORE_1
        null, // ISTORE_2
        null, // ISTORE_3
        null, // LSTORE_0
        null, // LSTORE_1
        null, // LSTORE_2
        null, // LSTORE_3
        null, // FSTORE_0
        null, // FSTORE_1
        null, // FSTORE_2
        null, // FSTORE_3
        null, // DSTORE_0
        null, // DSTORE_1
        null, // DSTORE_2
        null, // DSTORE_3
        null, // ASTORE_0
        null, // ASTORE_1
        null, // ASTORE_2
        null, // ASTORE_3
        Method.VISIT_INSN, // IASTORE
        Method.VISIT_INSN, // LASTORE
        Method.VISIT_INSN, // FASTORE
        Method.VISIT_INSN, // DASTORE
        Method.VISIT_INSN, // AASTORE
        Method.VISIT_INSN, // BASTORE
        Method.VISIT_INSN, // CASTORE
        Method.VISIT_INSN, // SASTORE
        Method.VISIT_INSN, // POP
        Method.VISIT_INSN, // POP2
        Method.VISIT_INSN, // DUP
        Method.VISIT_INSN, // DUP_X1
        Method.VISIT_INSN, // DUP_X2
        Method.VISIT_INSN, // DUP2
        Method.VISIT_INSN, // DUP2_X1
        Method.VISIT_INSN, // DUP2_X2
        Method.VISIT_INSN, // SWAP
        Method.VISIT_INSN, // IADD
        Method.VISIT_INSN, // LADD
        Method.VISIT_INSN, // FADD
        Method.VISIT_INSN, // DADD
        Method.VISIT_INSN, // ISUB
        Method.VISIT_INSN, // LSUB
        Method.VISIT_INSN, // FSUB
        Method.VISIT_INSN, // DSUB
        Method.VISIT_INSN, // IMUL
        Method.VISIT_INSN, // LMUL
        Method.VISIT_INSN, // FMUL
        Method.VISIT_INSN, // DMUL
        Method.VISIT_INSN, // IDIV
        Method.VISIT_INSN, // LDIV
        Method.VISIT_INSN, // FDIV
        Method.VISIT_INSN, // DDIV
        Method.VISIT_INSN, // IREM
        Method.VISIT_INSN, // LREM
        Method.VISIT_INSN, // FREM
        Method.VISIT_INSN, // DREM
        Method.VISIT_INSN, // INEG
        Method.VISIT_INSN, // LNEG
        Method.VISIT_INSN, // FNEG
        Method.VISIT_INSN, // DNEG
        Method.VISIT_INSN, // ISHL
        Method.VISIT_INSN, // LSHL
        Method.VISIT_INSN, // ISHR
        Method.VISIT_INSN, // LSHR
        Method.VISIT_INSN, // IUSHR
        Method.VISIT_INSN, // LUSHR
        Method.VISIT_INSN, // IAND
        Method.VISIT_INSN, // LAND
        Method.VISIT_INSN, // IOR
        Method.VISIT_INSN, // LOR
        Method.VISIT_INSN, // IXOR
        Method.VISIT_INSN, // LXOR
        null, // IINC
        Method.VISIT_INSN, // I2L
        Method.VISIT_INSN, // I2F
        Method.VISIT_INSN, // I2D
        Method.VISIT_INSN, // L2I
        Method.VISIT_INSN, // L2F
        Method.VISIT_INSN, // L2D
        Method.VISIT_INSN, // F2I
        Method.VISIT_INSN, // F2L
        Method.VISIT_INSN, // F2D
        Method.VISIT_INSN, // D2I
        Method.VISIT_INSN, // D2L
        Method.VISIT_INSN, // D2F
        Method.VISIT_INSN, // I2B
        Method.VISIT_INSN, // I2C
        Method.VISIT_INSN, // I2S
        Method.VISIT_INSN, // LCMP
        Method.VISIT_INSN, // FCMPL
        Method.VISIT_INSN, // FCMPG
        Method.VISIT_INSN, // DCMPL
        Method.VISIT_INSN, // DCMPG
        Method.VISIT_JUMP_INSN, // IFEQ
        Method.VISIT_JUMP_INSN, // IFNE
        Method.VISIT_JUMP_INSN, // IFLT
        Method.VISIT_JUMP_INSN, // IFGE
        Method.VISIT_JUMP_INSN, // IFGT
        Method.VISIT_JUMP_INSN, // IFLE
        Method.VISIT_JUMP_INSN, // IF_ICMPEQ
        Method.VISIT_JUMP_INSN, // IF_ICMPNE
        Method.VISIT_JUMP_INSN, // IF_ICMPLT
        Method.VISIT_JUMP_INSN, // IF_ICMPGE
        Method.VISIT_JUMP_INSN, // IF_ICMPGT
        Method.VISIT_JUMP_INSN, // IF_ICMPLE
        Method.VISIT_JUMP_INSN, // IF_ACMPEQ
        Method.VISIT_JUMP_INSN, // IF_ACMPNE
        Method.VISIT_JUMP_INSN, // GOTO
        Method.VISIT_JUMP_INSN, // JSR
        Method.VISIT_VAR_INSN, // RET
        null, // TABLESWITCH
        null, // LOOKUPSWITCH
        Method.VISIT_INSN, // IRETURN
        Method.VISIT_INSN, // LRETURN
        Method.VISIT_INSN, // FRETURN
        Method.VISIT_INSN, // DRETURN
        Method.VISIT_INSN, // ARETURN
        Method.VISIT_INSN, // RETURN
        Method.VISIT_FIELD_INSN, // GETSTATIC
        Method.VISIT_FIELD_INSN, // PUTSTATIC
        Method.VISIT_FIELD_INSN, // GETFIELD
        Method.VISIT_FIELD_INSN, // PUTFIELD
        Method.VISIT_METHOD_INSN, // INVOKEVIRTUAL
        Method.VISIT_METHOD_INSN, // INVOKESPECIAL
        Method.VISIT_METHOD_INSN, // INVOKESTATIC
        Method.VISIT_METHOD_INSN, // INVOKEINTERFACE
        null, // INVOKEDYNAMIC
        Method.VISIT_TYPE_INSN, // NEW
        Method.VISIT_INT_INSN, // NEWARRAY
        Method.VISIT_TYPE_INSN, // ANEWARRAY
        Method.VISIT_INSN, // ARRAYLENGTH
        Method.VISIT_INSN, // ATHROW
        Method.VISIT_TYPE_INSN, // CHECKCAST
        Method.VISIT_TYPE_INSN, // INSTANCEOF
        Method.VISIT_INSN, // MONITORENTER
        Method.VISIT_INSN, // MONITOREXIT
        null, // WIDE
        null, // MULTIANEWARRAY
        Method.VISIT_JUMP_INSN, // IFNULL
        Method.VISIT_JUMP_INSN // IFNONNULL
    };

    private static final String INVALID = "Invalid ";
    private static final String INVALID_DESCRIPTOR = "Invalid descriptor: ";
    private static final String INVALID_TYPE_REFERENCE = "Invalid type reference sort 0x";
    private static final String INVALID_LOCAL_VARIABLE_INDEX = "Invalid local variable index";
    private static final String MUST_NOT_BE_NULL_OR_EMPTY = " (must not be null or empty)";
    private static final String START_LABEL = "start label";
    private static final String END_LABEL = "end label";

    /** The class version number. */
    public int version;

    /** The access flags of the visited method. */
    private int access;

    /**
      * The number of method parameters that can have runtime visible annotations. 0 means that all the
      * parameters from the method descriptor can have annotations.
      */
    private int visibleAnnotableParameterCount;

    /**
      * The number of method parameters that can have runtime invisible annotations. 0 means that all
      * the parameters from the method descriptor can have annotations.
      */
    private int invisibleAnnotableParameterCount;

    /** Whether the {@link #visitCode} method has been called. */
    private boolean visitCodeCalled;

    /** Whether the {@link #visitMaxs} method has been called. */
    private boolean visitMaxCalled;

    /** Whether the {@link #visitEnd} method has been called. */
    private boolean visitEndCalled;

    /** The number of visited instructions so far. */
    private int insnCount;

    /** The index of the instruction designated by each visited label. */
    private final Map<Label, Integer> labelInsnIndices;

    /** The labels referenced by the visited method. */
    private Set<Label> referencedLabels;

    /** The index of the instruction corresponding to the last visited stack map frame. */
    private int lastFrameInsnIndex = -1;

    /** The number of visited frames in expanded form. */
    private int numExpandedFrames;

    /** The number of visited frames in compressed form. */
    private int numCompressedFrames;

    /**
      * The exception handler ranges. Each pair of list element contains the start and end labels of an
      * exception handler block.
      */
    private List<Label> handlers;

    /**
      * Constructs a new {@link CheckMethodAdapter} object. This method adapter will not perform any
      * data flow check (see {@link #CheckMethodAdapter(int,String,String,MethodVisitor,Map)}).
      * <i>Subclasses must not use this constructor</i>. Instead, they must use the {@link
      * #CheckMethodAdapter(int, MethodVisitor, Map)} version.
      *
      * @param methodvisitor the method visitor to which this adapter must delegate calls.
      */
    public CheckMethodAdapter(final MethodVisitor methodvisitor) {
        this(methodvisitor, 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)}).
      * <i>Subclasses must not use this constructor</i>. Instead, they must use the {@link
      * #CheckMethodAdapter(int, MethodVisitor, Map)} version.
      *
      * @param methodVisitor the method visitor to which this adapter must delegate calls.
      * @param labelInsnIndices the index of the instruction designated by each visited label so far
      *     (in other methods). This map is updated with the labels from the visited method.
      * @throws IllegalStateException If a subclass calls this constructor.
      */
    public CheckMethodAdapter(
            final MethodVisitor methodVisitor, final Map<Label, Integer> labelInsnIndices) {
        this(/* latest api = */ Opcodes.ASM9, methodVisitor, labelInsnIndices);
        if (getClass() != CheckMethodAdapter.class) {
            throw new IllegalStateException();
        }
    }

    /**
      * 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 api the ASM API version implemented by this CheckMethodAdapter. Must be one of the
      *     {@code ASM}<i>x</i> values in {@link Opcodes}.
      * @param methodVisitor the method visitor to which this adapter must delegate calls.
      * @param labelInsnIndices the index of the instruction designated by each visited label so far
      *     (in other methods). This map is updated with the labels from the visited method.
      */
    protected CheckMethodAdapter(
            final int api,
            final MethodVisitor methodVisitor,
            final Map<Label, Integer> labelInsnIndices) {
        super(api, methodVisitor);
        this.labelInsnIndices = labelInsnIndices;
        this.referencedLabels = new HashSet<>();
        this.handlers = new ArrayList<>();
    }

    /**
      * Constructs a new {@link CheckMethodAdapter} object. This method adapter will perform basic data
      * flow checks. For instance in a method whose signature is {@code void m ()}, the invalid
      * instruction IRETURN, or the invalid sequence IADD L2I will be detected. <i>Subclasses must not
      * use this constructor</i>. Instead, they must use the {@link
      * #CheckMethodAdapter(int,int,String,String,MethodVisitor,Map)} version.
      *
      * @param access the method's access flags.
      * @param name the method's name.
      * @param descriptor the method's descriptor (see {@link Type}).
      * @param methodVisitor the method visitor to which this adapter must delegate calls.
      * @param labelInsnIndices the index of the instruction designated by each visited label so far
      *     (in other methods). This map is updated with the labels from the visited method.
      */
    public CheckMethodAdapter(
            final int access,
            final String name,
            final String descriptor,
            final MethodVisitor methodVisitor,
            final Map<Label, Integer> labelInsnIndices) {
        this(
                /* latest api = */ Opcodes.ASM9, access, name, descriptor, methodVisitor, labelInsnIndices);
        if (getClass() != CheckMethodAdapter.class) {
            throw new IllegalStateException();
        }
    }

    /**
      * Constructs a new {@link CheckMethodAdapter} object. This method adapter will perform basic data
      * flow checks. For instance in a method whose signature is {@code void m ()}, the invalid
      * instruction IRETURN, or the invalid sequence IADD L2I will be detected.
      *
      * @param api the ASM API version implemented by this CheckMethodAdapter. Must be one of the
      *     {@code ASM}<i>x</i> values in {@link Opcodes}.
      * @param access the method's access flags.
      * @param name the method's name.
      * @param descriptor the method's descriptor (see {@link Type}).
      * @param methodVisitor the method visitor to which this adapter must delegate calls.
      * @param labelInsnIndices the index of the instruction designated by each visited label so far
      *     (in other methods). This map is updated with the labels from the visited method.
      */
    protected CheckMethodAdapter(
            final int api,
            final int access,
            final String name,
            final String descriptor,
            final MethodVisitor methodVisitor,
            final Map<Label, Integer> labelInsnIndices) {
        this(
                api,
                new MethodNode(api, access, name, descriptor, null, null) {
                    @Override
                    public void visitEnd() {
                        Analyzer<BasicValue> analyzer = new Analyzer<>(new BasicVerifier());
                        try {
                            // If 'methodVisitor' is a MethodWriter of a ClassWriter with no flags to compute the
                            // max stack and locals nor the stack map frames, we know that valid max stack and
                            // locals must be provided. Otherwise we assume they are not needed at this stage.
                            // TODO(ebruneton): similarly, check that valid stack map frames are provided if the
                            // class writer has no flags to compute them, and the class version is V1_7 or more.
                            boolean checkMaxStackAndLocals =
                                    (methodVisitor instanceof MethodWriterWrapper)
                                            && !((MethodWriterWrapper) methodVisitor).computesMaxs();
                            if (checkMaxStackAndLocals) {
                                analyzer.analyze("dummy", this);
                            } else {
                                analyzer.analyzeAndComputeMaxs("dummy", this);
                            }
                        } catch (IndexOutOfBoundsException | AnalyzerException e) {
                            throwError(analyzer, e);
                        }
                        if (methodVisitor != null) {
                            accept(methodVisitor);
                        }
                    }

                    private void throwError(final Analyzer<BasicValue> analyzer, final Exception e) {
                        StringWriter stringWriter = new StringWriter();
                        PrintWriter printWriter = new PrintWriter(stringWriter, true);
                        CheckClassAdapter.printAnalyzerResult(this, analyzer, printWriter);
                        printWriter.close();
                        throw new IllegalArgumentException(e.getMessage() + ' ' + stringWriter.toString(), e);
                    }
                },
                labelInsnIndices);
        this.access = access;
    }

    @Override
    public void visitParameter(final String name, final int access) {
        if (name != null) {
            checkUnqualifiedName(version, name, "name");
        }
        CheckClassAdapter.checkAccess(
                access, Opcodes.ACC_FINAL | Opcodes.ACC_MANDATED | Opcodes.ACC_SYNTHETIC);
        super.visitParameter(name, access);
    }

    @Override
    public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
        checkVisitEndNotCalled();
        checkDescriptor(version, descriptor, false);
        return new CheckAnnotationAdapter(super.visitAnnotation(descriptor, visible));
    }

    @Override
    public AnnotationVisitor visitTypeAnnotation(
            final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
        checkVisitEndNotCalled();
        int sort = new TypeReference(typeRef).getSort();
        if (sort != TypeReference.METHOD_TYPE_PARAMETER
                && sort != TypeReference.METHOD_TYPE_PARAMETER_BOUND
                && sort != TypeReference.METHOD_RETURN
                && sort != TypeReference.METHOD_RECEIVER
                && sort != TypeReference.METHOD_FORMAL_PARAMETER
                && sort != TypeReference.THROWS) {
            throw new IllegalArgumentException(INVALID_TYPE_REFERENCE + Integer.toHexString(sort));
        }
        CheckClassAdapter.checkTypeRef(typeRef);
        CheckMethodAdapter.checkDescriptor(version, descriptor, false);
        return new CheckAnnotationAdapter(
                super.visitTypeAnnotation(typeRef, typePath, descriptor, visible));
    }

    @Override
    public AnnotationVisitor visitAnnotationDefault() {
        checkVisitEndNotCalled();
        return new CheckAnnotationAdapter(super.visitAnnotationDefault(), false);
    }

    @Override
    public void visitAnnotableParameterCount(final int parameterCount, final boolean visible) {
        checkVisitEndNotCalled();
        if (visible) {
            visibleAnnotableParameterCount = parameterCount;
        } else {
            invisibleAnnotableParameterCount = parameterCount;
        }
        super.visitAnnotableParameterCount(parameterCount, visible);
    }

    @Override
    public AnnotationVisitor visitParameterAnnotation(
            final int parameter, final String descriptor, final boolean visible) {
        checkVisitEndNotCalled();
        if ((visible
                        && visibleAnnotableParameterCount > 0
                        && parameter >= visibleAnnotableParameterCount)
                || (!visible
                        && invisibleAnnotableParameterCount > 0
                        && parameter >= invisibleAnnotableParameterCount)) {
            throw new IllegalArgumentException("Invalid parameter index");
        }
        checkDescriptor(version, descriptor, false);
        return new CheckAnnotationAdapter(
                super.visitParameterAnnotation(parameter, descriptor, visible));
    }

    @Override
    public void visitAttribute(final Attribute attribute) {
        checkVisitEndNotCalled();
        if (attribute == null) {
            throw new IllegalArgumentException("Invalid attribute (must not be null)");
        }
        super.visitAttribute(attribute);
    }

    @Override
    public void visitCode() {
        if ((access & Opcodes.ACC_ABSTRACT) != 0) {
            throw new UnsupportedOperationException("Abstract methods cannot have code");
        }
        visitCodeCalled = true;
        super.visitCode();
    }

    @Override
    public void visitFrame(
            final int type,
            final int numLocal,
            final Object[] local,
            final int numStack,
            final Object[] stack) {
        if (insnCount == lastFrameInsnIndex) {
            throw new IllegalStateException("At most one frame can be visited at a given code location.");
        }
        lastFrameInsnIndex = insnCount;
        int maxNumLocal;
        int maxNumStack;
        switch (type) {
            case Opcodes.F_NEW:
            case Opcodes.F_FULL:
                maxNumLocal = Integer.MAX_VALUE;
                maxNumStack = Integer.MAX_VALUE;
                break;

            case Opcodes.F_SAME:
                maxNumLocal = 0;
                maxNumStack = 0;
                break;

            case Opcodes.F_SAME1:
                maxNumLocal = 0;
                maxNumStack = 1;
                break;

            case Opcodes.F_APPEND:
            case Opcodes.F_CHOP:
                maxNumLocal = 3;
                maxNumStack = 0;
                break;

            default:
                throw new IllegalArgumentException("Invalid frame type " + type);
        }

        if (numLocal > maxNumLocal) {
            throw new IllegalArgumentException(
                    "Invalid numLocal=" + numLocal + " for frame type " + type);
        }
        if (numStack > maxNumStack) {
            throw new IllegalArgumentException(
                    "Invalid numStack=" + numStack + " for frame type " + type);
        }

        if (type != Opcodes.F_CHOP) {
            if (numLocal > 0 && (local == null || local.length < numLocal)) {
                throw new IllegalArgumentException("Array local[] is shorter than numLocal");
            }
            for (int i = 0; i < numLocal; ++i) {
                checkFrameValue(local[i]);
            }
        }
        if (numStack > 0 && (stack == null || stack.length < numStack)) {
            throw new IllegalArgumentException("Array stack[] is shorter than numStack");
        }
        for (int i = 0; i < numStack; ++i) {
            checkFrameValue(stack[i]);
        }
        if (type == Opcodes.F_NEW) {
            ++numExpandedFrames;
        } else {
            ++numCompressedFrames;
        }
        if (numExpandedFrames > 0 && numCompressedFrames > 0) {
            throw new IllegalArgumentException("Expanded and compressed frames must not be mixed.");
        }
        super.visitFrame(type, numLocal, local, numStack, stack);
    }

    @Override
    public void visitInsn(final int opcode) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        checkOpcodeMethod(opcode, Method.VISIT_INSN);
        super.visitInsn(opcode);
        ++insnCount;
    }

    @Override
    public void visitIntInsn(final int opcode, final int operand) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        checkOpcodeMethod(opcode, Method.VISIT_INT_INSN);
        switch (opcode) {
            case Opcodes.BIPUSH:
                checkSignedByte(operand, "Invalid operand");
                break;
            case Opcodes.SIPUSH:
                checkSignedShort(operand, "Invalid operand");
                break;
            case Opcodes.NEWARRAY:
                if (operand < Opcodes.T_BOOLEAN || operand > Opcodes.T_LONG) {
                    throw new IllegalArgumentException(
                            "Invalid operand (must be an array type code T_...): " + operand);
                }
                break;
            default:
                throw new AssertionError();
        }
        super.visitIntInsn(opcode, operand);
        ++insnCount;
    }

    @Override
    public void visitVarInsn(final int opcode, final int varIndex) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        checkOpcodeMethod(opcode, Method.VISIT_VAR_INSN);
        checkUnsignedShort(varIndex, INVALID_LOCAL_VARIABLE_INDEX);
        super.visitVarInsn(opcode, varIndex);
        ++insnCount;
    }

    @Override
    public void visitTypeInsn(final int opcode, final String type) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        checkOpcodeMethod(opcode, Method.VISIT_TYPE_INSN);
        checkInternalName(version, 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 descriptor) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        checkOpcodeMethod(opcode, Method.VISIT_FIELD_INSN);
        checkInternalName(version, owner, "owner");
        checkUnqualifiedName(version, name, "name");
        checkDescriptor(version, descriptor, false);
        super.visitFieldInsn(opcode, owner, name, descriptor);
        ++insnCount;
    }

    @Override
    public void visitMethodInsn(
            final int opcodeAndSource,
            final String owner,
            final String name,
            final String descriptor,
            final boolean isInterface) {
        if (api < Opcodes.ASM5 && (opcodeAndSource & Opcodes.SOURCE_DEPRECATED) == 0) {
            // Redirect the call to the deprecated version of this method.
            super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface);
            return;
        }
        int opcode = opcodeAndSource & ~Opcodes.SOURCE_MASK;

        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        checkOpcodeMethod(opcode, Method.VISIT_METHOD_INSN);
        if (opcode != Opcodes.INVOKESPECIAL || !"<init>".equals(name)) {
            checkMethodIdentifier(version, name, "name");
        }
        checkInternalName(version, owner, "owner");
        checkMethodDescriptor(version, descriptor);
        if (opcode == Opcodes.INVOKEVIRTUAL && isInterface) {
            throw new IllegalArgumentException("INVOKEVIRTUAL can't be used with interfaces");
        }
        if (opcode == Opcodes.INVOKEINTERFACE && !isInterface) {
            throw new IllegalArgumentException("INVOKEINTERFACE can't be used with classes");
        }
        if (opcode == Opcodes.INVOKESPECIAL && isInterface && (version & 0xFFFF) < Opcodes.V1_8) {
            throw new IllegalArgumentException(
                    "INVOKESPECIAL can't be used with interfaces prior to Java 8");
        }
        super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface);
        ++insnCount;
    }

    @Override
    public void visitInvokeDynamicInsn(
            final String name,
            final String descriptor,
            final Handle bootstrapMethodHandle,
            final Object... bootstrapMethodArguments) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        checkMethodIdentifier(version, name, "name");
        checkMethodDescriptor(version, descriptor);
        if (bootstrapMethodHandle.getTag() != Opcodes.H_INVOKESTATIC
                && bootstrapMethodHandle.getTag() != Opcodes.H_NEWINVOKESPECIAL) {
            throw new IllegalArgumentException("invalid handle tag " + bootstrapMethodHandle.getTag());
        }
        for (Object bootstrapMethodArgument : bootstrapMethodArguments) {
            checkLdcConstant(bootstrapMethodArgument);
        }
        super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
        ++insnCount;
    }

    @Override
    public void visitJumpInsn(final int opcode, final Label label) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        checkOpcodeMethod(opcode, Method.VISIT_JUMP_INSN);
        checkLabel(label, false, "label");
        super.visitJumpInsn(opcode, label);
        referencedLabels.add(label);
        ++insnCount;
    }

    @Override
    public void visitLabel(final Label label) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        checkLabel(label, false, "label");
        if (labelInsnIndices.get(label) != null) {
            throw new IllegalStateException("Already visited label");
        }
        labelInsnIndices.put(label, insnCount);
        super.visitLabel(label);
    }

    @Override
    public void visitLdcInsn(final Object value) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        checkLdcConstant(value);
        super.visitLdcInsn(value);
        ++insnCount;
    }

    @Override
    public void visitIincInsn(final int varIndex, final int increment) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        checkUnsignedShort(varIndex, INVALID_LOCAL_VARIABLE_INDEX);
        checkSignedShort(increment, "Invalid increment");
        super.visitIincInsn(varIndex, increment);
        ++insnCount;
    }

    @Override
    public void visitTableSwitchInsn(
            final int min, final int max, final Label dflt, final Label... labels) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        if (max < min) {
            throw new IllegalArgumentException(
                    "Max = " + max + " must be greater than or equal to min = " + min);
        }
        checkLabel(dflt, false, "default label");
        if (labels == null || labels.length != max - min + 1) {
            throw new IllegalArgumentException("There must be max - min + 1 labels");
        }
        for (int i = 0; i < labels.length; ++i) {
            checkLabel(labels[i], false, "label at index " + i);
        }
        super.visitTableSwitchInsn(min, max, dflt, labels);
        Collections.addAll(referencedLabels, labels);
        ++insnCount;
    }

    @Override
    public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
        checkVisitMaxsNotCalled();
        checkVisitCodeCalled();
        checkLabel(dflt, false, "default label");
        if (keys == null || labels == null || keys.length != labels.length) {
            throw new IllegalArgumentException("There must be the same number of keys and labels");
        }
        for (int i = 0; i < labels.length; ++i) {
            checkLabel(labels[i], false, "label at index " + i);
        }
        super.visitLookupSwitchInsn(dflt, keys, labels);
        referencedLabels.add(dflt);
        Collections.addAll(referencedLabels, labels);
        ++insnCount;
    }

    @Override
    public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        checkDescriptor(version, descriptor, false);
        if (descriptor.charAt(0) != '[') {
            throw new IllegalArgumentException(
                    "Invalid descriptor (must be an array type descriptor): " + descriptor);
        }
        if (numDimensions < 1) {
            throw new IllegalArgumentException(
                    "Invalid dimensions (must be greater than 0): " + numDimensions);
        }
        if (numDimensions > descriptor.lastIndexOf('[') + 1) {
            throw new IllegalArgumentException(
                    "Invalid dimensions (must not be greater than numDimensions(descriptor)): "
                            + numDimensions);
        }
        super.visitMultiANewArrayInsn(descriptor, numDimensions);
        ++insnCount;
    }

    @Override
    public AnnotationVisitor visitInsnAnnotation(
            final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        int sort = new TypeReference(typeRef).getSort();
        if (sort != TypeReference.INSTANCEOF
                && sort != TypeReference.NEW
                && sort != TypeReference.CONSTRUCTOR_REFERENCE
                && sort != TypeReference.METHOD_REFERENCE
                && sort != TypeReference.CAST
                && sort != TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
                && sort != TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT
                && sort != TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
                && sort != TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT) {
            throw new IllegalArgumentException(INVALID_TYPE_REFERENCE + Integer.toHexString(sort));
        }
        CheckClassAdapter.checkTypeRef(typeRef);
        CheckMethodAdapter.checkDescriptor(version, descriptor, false);
        return new CheckAnnotationAdapter(
                super.visitInsnAnnotation(typeRef, typePath, descriptor, visible));
    }

    @Override
    public void visitTryCatchBlock(
            final Label start, final Label end, final Label handler, final String type) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        checkLabel(start, false, START_LABEL);
        checkLabel(end, false, END_LABEL);
        checkLabel(handler, false, "handler label");
        if (labelInsnIndices.get(start) != null
                || labelInsnIndices.get(end) != null
                || labelInsnIndices.get(handler) != null) {
            throw new IllegalStateException("Try catch blocks must be visited before their labels");
        }
        if (type != null) {
            checkInternalName(version, type, "type");
        }
        super.visitTryCatchBlock(start, end, handler, type);
        handlers.add(start);
        handlers.add(end);
    }

    @Override
    public AnnotationVisitor visitTryCatchAnnotation(
            final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        int sort = new TypeReference(typeRef).getSort();
        if (sort != TypeReference.EXCEPTION_PARAMETER) {
            throw new IllegalArgumentException(INVALID_TYPE_REFERENCE + Integer.toHexString(sort));
        }
        CheckClassAdapter.checkTypeRef(typeRef);
        CheckMethodAdapter.checkDescriptor(version, descriptor, false);
        return new CheckAnnotationAdapter(
                super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible));
    }

    @Override
    public void visitLocalVariable(
            final String name,
            final String descriptor,
            final String signature,
            final Label start,
            final Label end,
            final int index) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        checkUnqualifiedName(version, name, "name");
        checkDescriptor(version, descriptor, false);
        if (signature != null) {
            CheckClassAdapter.checkFieldSignature(signature);
        }
        checkLabel(start, true, START_LABEL);
        checkLabel(end, true, END_LABEL);
        checkUnsignedShort(index, INVALID_LOCAL_VARIABLE_INDEX);
        int startInsnIndex = labelInsnIndices.get(start).intValue();
        int endInsnIndex = labelInsnIndices.get(end).intValue();
        if (endInsnIndex < startInsnIndex) {
            throw new IllegalArgumentException(
                    "Invalid start and end labels (end must be greater than start)");
        }
        super.visitLocalVariable(name, descriptor, signature, start, end, index);
    }

    @Override
    public AnnotationVisitor visitLocalVariableAnnotation(
            final int typeRef,
            final TypePath typePath,
            final Label[] start,
            final Label[] end,
            final int[] index,
            final String descriptor,
            final boolean visible) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        int sort = new TypeReference(typeRef).getSort();
        if (sort != TypeReference.LOCAL_VARIABLE && sort != TypeReference.RESOURCE_VARIABLE) {
            throw new IllegalArgumentException(INVALID_TYPE_REFERENCE + Integer.toHexString(sort));
        }
        CheckClassAdapter.checkTypeRef(typeRef);
        checkDescriptor(version, descriptor, false);
        if (start == null
                || end == null
                || index == null
                || end.length != start.length
                || index.length != start.length) {
            throw new IllegalArgumentException(
                    "Invalid start, end and index arrays (must be non null and of identical length");
        }
        for (int i = 0; i < start.length; ++i) {
            checkLabel(start[i], true, START_LABEL);
            checkLabel(end[i], true, END_LABEL);
            checkUnsignedShort(index[i], INVALID_LOCAL_VARIABLE_INDEX);
            int startInsnIndex = labelInsnIndices.get(start[i]).intValue();
            int endInsnIndex = labelInsnIndices.get(end[i]).intValue();
            if (endInsnIndex < startInsnIndex) {
                throw new IllegalArgumentException(
                        "Invalid start and end labels (end must be greater than start)");
            }
        }
        return super.visitLocalVariableAnnotation(
                typeRef, typePath, start, end, index, descriptor, visible);
    }

    @Override
    public void visitLineNumber(final int line, final Label start) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        checkUnsignedShort(line, "Invalid line number");
        checkLabel(start, true, START_LABEL);
        super.visitLineNumber(line, start);
    }

    @Override
    public void visitMaxs(final int maxStack, final int maxLocals) {
        checkVisitCodeCalled();
        checkVisitMaxsNotCalled();
        visitMaxCalled = true;
        for (Label l : referencedLabels) {
            if (labelInsnIndices.get(l) == null) {
                throw new IllegalStateException("Undefined label used");
            }
        }
        for (int i = 0; i < handlers.size(); i += 2) {
            Integer startInsnIndex = labelInsnIndices.get(handlers.get(i));
            Integer endInsnIndex = labelInsnIndices.get(handlers.get(i + 1));
            if (startInsnIndex == null || endInsnIndex == null) {
                throw new IllegalStateException("Undefined try catch block labels");
            }
            if (endInsnIndex.intValue() <= startInsnIndex.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() {
        checkVisitEndNotCalled();
        visitEndCalled = true;
        super.visitEnd();
    }

    // -----------------------------------------------------------------------------------------------
    // Utility methods
    // -----------------------------------------------------------------------------------------------

    /** Checks that the {@link #visitCode} method has been called. */
    private void checkVisitCodeCalled() {
        if (!visitCodeCalled) {
            throw new IllegalStateException(
                    "Cannot visit instructions before visitCode has been called.");
        }
    }

    /** Checks that the {@link #visitMaxs} method has not been called. */
    private void checkVisitMaxsNotCalled() {
        if (visitMaxCalled) {
            throw new IllegalStateException("Cannot visit instructions after visitMaxs has been called.");
        }
    }

    /** Checks that the {@link #visitEnd} method has not been called. */
    private void checkVisitEndNotCalled() {
        if (visitEndCalled) {
            throw new IllegalStateException("Cannot visit elements after visitEnd has been called.");
        }
    }

    /**
      * Checks a stack frame value.
      *
      * @param value the value to be checked.
      */
    private 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(version, (String) value, "Invalid stack frame value");
        } else if (value instanceof Label) {
            referencedLabels.add((Label) value);
        } else {
            throw new IllegalArgumentException("Invalid stack frame value: " + value);
        }
    }

    /**
      * Checks that the method to visit the given opcode is equal to the given method.
      *
      * @param opcode the opcode to be checked.
      * @param method the expected visit method.
      */
    private static void checkOpcodeMethod(final int opcode, final Method method) {
        if (opcode < Opcodes.NOP || opcode > Opcodes.IFNONNULL || OPCODE_METHODS[opcode] != method) {
            throw new IllegalArgumentException("Invalid opcode: " + opcode);
        }
    }

    /**
      * Checks that the given value is a signed byte.
      *
      * @param value the value to be checked.
      * @param message the message to use in case of error.
      */
    private static void checkSignedByte(final int value, final String message) {
        if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
            throw new IllegalArgumentException(message + " (must be a signed byte): " + value);
        }
    }

    /**
      * Checks that the given value is a signed short.
      *
      * @param value the value to be checked.
      * @param message the message to use in case of error.
      */
    private static void checkSignedShort(final int value, final String message) {
        if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
            throw new IllegalArgumentException(message + " (must be a signed short): " + value);
        }
    }

    /**
      * Checks that the given value is an unsigned short.
      *
      * @param value the value to be checked.
      * @param message the message to use in case of error.
      */
    private static void checkUnsignedShort(final int value, final String message) {
        if (value < 0 || value > 65535) {
            throw new IllegalArgumentException(message + " (must be an unsigned short): " + value);
        }
    }

    /**
      * Checks that the given value is an {@link Integer}, {@link Float}, {@link Long}, {@link Double}
      * or {@link String} value.
      *
      * @param value the value to be checked.
      */
    static void checkConstant(final Object value) {
        if (!(value instanceof Integer)
                && !(value instanceof Float)
                && !(value instanceof Long)
                && !(value instanceof Double)
                && !(value instanceof String)) {
            throw new IllegalArgumentException("Invalid constant: " + value);
        }
    }

    /**
      * Checks that the given value is a valid operand for the LDC instruction.
      *
      * @param value the value to be checked.
      */
    private void checkLdcConstant(final Object value) {
        if (value instanceof Type) {
            int sort = ((Type) value).getSort();
            if (sort != Type.OBJECT && sort != Type.ARRAY && sort != Type.METHOD) {
                throw new IllegalArgumentException("Illegal LDC constant value");
            }
            if (sort != Type.METHOD && (version & 0xFFFF) < Opcodes.V1_5) {
                throw new IllegalArgumentException("ldc of a constant class requires at least version 1.5");
            }
            if (sort == Type.METHOD && (version & 0xFFFF) < Opcodes.V1_7) {
                throw new IllegalArgumentException("ldc of a method type requires at least version 1.7");
            }
        } else if (value instanceof Handle) {
            if ((version & 0xFFFF) < Opcodes.V1_7) {
                throw new IllegalArgumentException("ldc of a Handle requires at least version 1.7");
            }
            Handle handle = (Handle) value;
            int tag = handle.getTag();
            if (tag < Opcodes.H_GETFIELD || tag > Opcodes.H_INVOKEINTERFACE) {
                throw new IllegalArgumentException("invalid handle tag " + tag);
            }
            checkInternalName(this.version, handle.getOwner(), "handle owner");
            if (tag <= Opcodes.H_PUTSTATIC) {
                checkDescriptor(this.version, handle.getDesc(), false);
            } else {
                checkMethodDescriptor(this.version, handle.getDesc());
            }
            String handleName = handle.getName();
            if (!("<init>".equals(handleName) && tag == Opcodes.H_NEWINVOKESPECIAL)) {
                checkMethodIdentifier(this.version, handleName, "handle name");
            }
        } else if (value instanceof ConstantDynamic) {
            if ((version & 0xFFFF) < Opcodes.V11) {
                throw new IllegalArgumentException("ldc of a ConstantDynamic requires at least version 11");
            }
            ConstantDynamic constantDynamic = (ConstantDynamic) value;
            checkMethodIdentifier(this.version, constantDynamic.getName(), "constant dynamic name");
            checkDescriptor(this.version, constantDynamic.getDescriptor(), false);
            checkLdcConstant(constantDynamic.getBootstrapMethod());
            int bootstrapMethodArgumentCount = constantDynamic.getBootstrapMethodArgumentCount();
            for (int i = 0; i < bootstrapMethodArgumentCount; ++i) {
                checkLdcConstant(constantDynamic.getBootstrapMethodArgument(i));
            }
        } else {
            checkConstant(value);
        }
    }

    /**
      * Checks that the given string is a valid unqualified name.
      *
      * @param version the class version.
      * @param name the string to be checked.
      * @param message the message to use in case of error.
      */
    static void checkUnqualifiedName(final int version, final String name, final String message) {
        checkIdentifier(version, name, 0, -1, message);
    }

    /**
      * Checks that the given substring is a valid Java identifier.
      *
      * @param version the class version.
      * @param name the string to be checked.
      * @param startPos the index of the first character of the identifier (inclusive).
      * @param endPos the index of the last character of the identifier (exclusive). -1 is equivalent
      *     to {@code name.length()} if name is not {@literal null}.
      * @param message the message to use in case of error.
      */
    static void checkIdentifier(
            final int version,
            final String name,
            final int startPos,
            final int endPos,
            final String message) {
        if (name == null || (endPos == -1 ? name.length() <= startPos : endPos <= startPos)) {
            throw new IllegalArgumentException(INVALID + message + MUST_NOT_BE_NULL_OR_EMPTY);
        }
        int max = endPos == -1 ? name.length() : endPos;
        if ((version & 0xFFFF) >= Opcodes.V1_5) {
            for (int i = startPos; i < max; i = name.offsetByCodePoints(i, 1)) {
                if (".;[/".indexOf(name.codePointAt(i)) != -1) {
                    throw new IllegalArgumentException(
                            INVALID + message + " (must not contain . ; [ or /): " + name);
                }
            }
            return;
        }
        for (int i = startPos; i < max; i = name.offsetByCodePoints(i, 1)) {
            if (i == startPos
                    ? !Character.isJavaIdentifierStart(name.codePointAt(i))
                    : !Character.isJavaIdentifierPart(name.codePointAt(i))) {
                throw new IllegalArgumentException(
                        INVALID + message + " (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 message the message to use in case of error.
      */
    static void checkMethodIdentifier(final int version, final String name, final String message) {
        if (name == null || name.length() == 0) {
            throw new IllegalArgumentException(INVALID + message + MUST_NOT_BE_NULL_OR_EMPTY);
        }
        if ((version & 0xFFFF) >= Opcodes.V1_5) {
            for (int i = 0; i < name.length(); i = name.offsetByCodePoints(i, 1)) {
                if (".;[/<>".indexOf(name.codePointAt(i)) != -1) {
                    throw new IllegalArgumentException(
                            INVALID + message + " (must be a valid unqualified name): " + name);
                }
            }
            return;
        }
        for (int i = 0; i < name.length(); i = name.offsetByCodePoints(i, 1)) {
            if (i == 0
                    ? !Character.isJavaIdentifierStart(name.codePointAt(i))
                    : !Character.isJavaIdentifierPart(name.codePointAt(i))) {
                throw new IllegalArgumentException(
                        INVALID
                                + message
                                + " (must be a '<init>', '<clinit>' or a valid Java identifier): "
                                + name);
            }
        }
    }

    /**
      * Checks that the given string is a valid internal class name or array type descriptor.
      *
      * @param version the class version.
      * @param name the string to be checked.
      * @param message the message to use in case of error.
      */
    static void checkInternalName(final int version, final String name, final String message) {
        if (name == null || name.length() == 0) {
            throw new IllegalArgumentException(INVALID + message + MUST_NOT_BE_NULL_OR_EMPTY);
        }
        if (name.charAt(0) == '[') {
            checkDescriptor(version, name, false);
        } else {
            checkInternalClassName(version, name, message);
        }
    }

    /**
      * Checks that the given string is a valid internal class name.
      *
      * @param version the class version.
      * @param name the string to be checked.
      * @param message the message to use in case of error.
      */
    private static void checkInternalClassName(
            final int version, final String name, final String message) {
        try {
            int startIndex = 0;
            int slashIndex;
            while ((slashIndex = name.indexOf('/', startIndex + 1)) != -1) {
                checkIdentifier(version, name, startIndex, slashIndex, null);
                startIndex = slashIndex + 1;
            }
            checkIdentifier(version, name, startIndex, name.length(), null);
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException(
                    INVALID + message + " (must be an internal class name): " + name, e);
        }
    }

    /**
      * Checks that the given string is a valid type descriptor.
      *
      * @param version the class version.
      * @param descriptor the string to be checked.
      * @param canBeVoid {@literal true} if {@code V} can be considered valid.
      */
    static void checkDescriptor(final int version, final String descriptor, final boolean canBeVoid) {
        int endPos = checkDescriptor(version, descriptor, 0, canBeVoid);
        if (endPos != descriptor.length()) {
            throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor);
        }
    }

    /**
      * Checks that the given substring is a valid type descriptor.
      *
      * @param version the class version.
      * @param descriptor the string to be checked.
      * @param startPos the index of the first character of the type descriptor (inclusive).
      * @param canBeVoid whether {@code V} can be considered valid.
      * @return the index of the last character of the type descriptor, plus one.
      */
    private static int checkDescriptor(
            final int version, final String descriptor, final int startPos, final boolean canBeVoid) {
        if (descriptor == null || startPos >= descriptor.length()) {
            throw new IllegalArgumentException("Invalid type descriptor (must not be null or empty)");
        }
        switch (descriptor.charAt(startPos)) {
            case 'V':
                if (canBeVoid) {
                    return startPos + 1;
                } else {
                    throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor);
                }
            case 'Z':
            case 'C':
            case 'B':
            case 'S':
            case 'I':
            case 'F':
            case 'J':
            case 'D':
                return startPos + 1;
            case '[':
                int pos = startPos + 1;
                while (pos < descriptor.length() && descriptor.charAt(pos) == '[') {
                    ++pos;
                }
                if (pos < descriptor.length()) {
                    return checkDescriptor(version, descriptor, pos, false);
                } else {
                    throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor);
                }
            case 'L':
                int endPos = descriptor.indexOf(';', startPos);
                if (startPos == -1 || endPos - startPos < 2) {
                    throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor);
                }
                try {
                    checkInternalClassName(version, descriptor.substring(startPos + 1, endPos), null);
                } catch (IllegalArgumentException e) {
                    throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor, e);
                }
                return endPos + 1;
            default:
                throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor);
        }
    }

    /**
      * Checks that the given string is a valid method descriptor.
      *
      * @param version the class version.
      * @param descriptor the string to be checked.
      */
    static void checkMethodDescriptor(final int version, final String descriptor) {
        if (descriptor == null || descriptor.length() == 0) {
            throw new IllegalArgumentException("Invalid method descriptor (must not be null or empty)");
        }
        if (descriptor.charAt(0) != '(' || descriptor.length() < 3) {
            throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor);
        }
        int pos = 1;
        if (descriptor.charAt(pos) != ')') {
            do {
                if (descriptor.charAt(pos) == 'V') {
                    throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor);
                }
                pos = checkDescriptor(version, descriptor, pos, false);
            } while (pos < descriptor.length() && descriptor.charAt(pos) != ')');
        }
        pos = checkDescriptor(version, descriptor, pos + 1, true);
        if (pos != descriptor.length()) {
            throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor);
        }
    }

    /**
      * 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 whether to check that the label has been visited.
      * @param message the message to use in case of error.
      */
    private void checkLabel(final Label label, final boolean checkVisited, final String message) {
        if (label == null) {
            throw new IllegalArgumentException(INVALID + message + " (must not be null)");
        }
        if (checkVisited && labelInsnIndices.get(label) == null) {
            throw new IllegalArgumentException(INVALID + message + " (must be visited first)");
        }
    }

    static class MethodWriterWrapper extends MethodVisitor {

        private final ClassWriter owner;

        MethodWriterWrapper(final int api, final ClassWriter owner, final MethodVisitor methodWriter) {
            super(api, methodWriter);
            this.owner = owner;
        }

        boolean computesMaxs() {
            return owner.hasFlags(ClassWriter.COMPUTE_MAXS) || owner.hasFlags(ClassWriter.COMPUTE_FRAMES);
        }
    }
}

