AsmProxyMaker.java

package space.sunqian.common.dynamic.proxy.asm;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import space.sunqian.annotations.Nonnull;
import space.sunqian.annotations.Nullable;
import space.sunqian.annotations.RetainedParam;
import space.sunqian.annotations.ThreadSafe;
import space.sunqian.common.Fs;
import space.sunqian.common.base.system.JvmKit;
import space.sunqian.common.base.value.IntVar;
import space.sunqian.common.dynamic.proxy.ProxyException;
import space.sunqian.common.dynamic.proxy.ProxyHandler;
import space.sunqian.common.dynamic.proxy.ProxyInvoker;
import space.sunqian.common.dynamic.proxy.ProxyKit;
import space.sunqian.common.dynamic.proxy.ProxyMaker;
import space.sunqian.common.dynamic.proxy.ProxySpec;
import space.sunqian.common.reflect.BytesClassLoader;
import space.sunqian.common.reflect.ClassKit;
import space.sunqian.common.third.asm.AsmKit;

import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

/**
 * The <a href="https://asm.ow2.io/">ASM</a> implementation for {@link ProxyMaker}. The runtime environment must have
 * asm package {@code org.objectweb.asm}.
 * <p>
 * This implementation uses inheritance to implement proxy, just like the keywords: {@code extends} and
 * {@code implements}. That means the superclass, which is the proxied class, cannot be {@code final} and must be
 * inheritable, and must have an empty constructor to ensure that the {@link ProxySpec#newInstance()} can execute
 * correctly.
 * <p>
 * When the {@link #make(Class, List, ProxyHandler)} is called, and if there are methods with the same name and JVM
 * descriptor, this implementation only passes the first one encountered to the
 * {@link ProxyHandler#needsProxy(Method)}.
 * <p>
 * Note the generated proxy class is {@code final}.
 *
 * @author sunqian
 */
@ThreadSafe
public class AsmProxyMaker implements ProxyMaker {

    private static final @Nonnull String INVOKER_NAME = JvmKit.toInternalName(ProxyInvoker.class);
    private static final @Nonnull String INVOKERS_DESCRIPTOR = JvmKit.toDescriptor(ProxyInvoker[].class);
    private static final @Nonnull String HANDLER_NAME = JvmKit.toInternalName(ProxyHandler.class);
    private static final @Nonnull String HANDLER_DESCRIPTOR = JvmKit.toDescriptor(ProxyHandler.class);
    private static final @Nonnull String METHODS_DESCRIPTOR = JvmKit.toDescriptor(Method[].class);
    private static final @Nonnull String INVOKER_SIMPLE_NAME = "AsmInvoker";
    private static final @Nonnull String SUPER_INVOKER_NAME_PREFIX = "access$super$";
    private static final @Nonnull Method HANDLER_INVOKE = Fs.uncheck(
        () -> ProxyHandler.class.getMethod("invoke", Object.class, Method.class, ProxyInvoker.class, Object[].class),
        AsmProxyException::new
    );
    private static final @Nonnull String HANDLER_INVOKE_DESCRIPTOR = JvmKit.toDescriptor(HANDLER_INVOKE);
    private static final @Nonnull Method INVOKER_INVOKE = Fs.uncheck(
        () -> ProxyInvoker.class.getMethod("invoke", Object.class, Object[].class),
        AsmProxyException::new
    );
    private static final @Nonnull String INVOKER_INVOKE_DESCRIPTOR = JvmKit.toDescriptor(INVOKER_INVOKE);
    private static final @Nonnull String @Nullable [] INVOKER_INVOKE_EXCEPTIONS = AsmKit.getExceptions(INVOKER_INVOKE);
    private static final @Nonnull Method INVOKER_INVOKE_SUPER = Fs.uncheck(
        () -> ProxyInvoker.class.getMethod("invokeSuper", Object.class, Object[].class),
        AsmProxyException::new
    );
    private static final @Nonnull String INVOKER_INVOKE_SUPER_DESCRIPTOR = JvmKit.toDescriptor(INVOKER_INVOKE_SUPER);
    private static final @Nonnull String @Nullable [] INVOKER_INVOKE_SUPER_EXCEPTIONS = AsmKit.getExceptions(INVOKER_INVOKE_SUPER);

    private static final @Nonnull AtomicLong classCounter = new AtomicLong();

    @Override
    public @Nonnull ProxySpec make(
        @Nullable Class<?> proxiedClass,
        @Nonnull @RetainedParam List<@Nonnull Class<?>> interfaces,
        @Nonnull ProxyHandler proxyHandler
    ) throws AsmProxyException {
        try {
            Package pkg = AsmProxyMaker.class.getPackage();
            // proxy class internal name
            String proxyName = pkg.getName().replace('.', '/')
                + "/" + AsmKit.generateClassSimpleName(classCounter.incrementAndGet());
            // proxy class descriptor
            String proxyDescriptor = "L" + proxyName + ";";
            // proxy class's superclass, which is the proxied class
            Class<?> proxySuperClass = proxiedClass == null ? Object.class : proxiedClass;
            String proxySuperName = JvmKit.toInternalName(proxySuperClass);
            // proxy class's interfaces, which is the proxied interfaces
            String[] proxyInterfaces = {};
            if (!interfaces.isEmpty()) {
                proxyInterfaces = interfaces.stream().map(JvmKit::toInternalName).toArray(String[]::new);
            }
            // ProxyInvoker's class internal name (inner class's simple name)
            String invokerSimpleName = INVOKER_SIMPLE_NAME;
            String invokerName = proxyName + "$" + invokerSimpleName;
            // proxied methods
            Map<Method, ProxyMethodInfo> proxiedMethodMap = new LinkedHashMap<>();
            Map<Class<?>, List<Method>> proxiableMethods = ProxyKit.getProxiableMethods(
                proxiedClass,
                interfaces,
                proxyHandler
            );
            IntVar methodCount = IntVar.of(0);
            proxiableMethods.forEach((type, methods) -> {
                String ownerName = JvmKit.toInternalName(type);
                for (Method method : methods) {
                    proxiedMethodMap.put(
                        method,
                        buildProxyMethodInfo(
                            method,
                            ownerName,
                            proxyDescriptor,
                            type.isInterface(),
                            methodCount.getAndIncrement()
                        )
                    );
                }
            });
            ProxyClassInfo pcInfo = new ProxyClassInfo(
                proxyName,
                proxyDescriptor,
                proxySuperName,
                proxyInterfaces,
                invokerSimpleName,
                invokerName,
                new ArrayList<>(proxiedMethodMap.values())
            );
            byte[] proxyClassBytes = generateProxyClass(pcInfo);
            byte[] invokerClassBytes = generateInvokerClass(pcInfo);
            // using new class loader to help collect unused classes
            BytesClassLoader loader = ClassKit.newClassLoader();
            Class<?> proxyClass = loader.loadClass(null, proxyClassBytes);
            loader.loadClass(null, invokerClassBytes);
            return new AsmProxySpec(
                proxyClass,
                proxySuperClass,
                interfaces,
                proxyHandler,
                proxiedMethodMap.keySet().toArray(new Method[0])
            );
        } catch (Exception e) {
            throw new AsmProxyException(e);
        }
    }

    private @Nonnull ProxyMethodInfo buildProxyMethodInfo(
        @Nonnull Method method,
        @Nonnull String ownerName,
        @Nonnull String proxyDescriptor,
        boolean isInterface,
        int methodIndex
    ) {
        String descriptor = JvmKit.toDescriptor(method);
        String signature = JvmKit.toSignature(method);
        String[] exceptions = AsmKit.getExceptions(method);
        String superInvokerName = SUPER_INVOKER_NAME_PREFIX + methodIndex;// access$001
        String superInvokerDescriptor = descriptor.replace("(", "(" + proxyDescriptor);
        String superInvokerSignature =
            signature == null ? null : signature.replace("(", "(" + proxyDescriptor);
        return new ProxyMethodInfo(
            method,
            ownerName,
            descriptor,
            signature,
            exceptions,
            superInvokerName,
            superInvokerDescriptor,
            superInvokerSignature,
            isInterface
        );
    }

    private byte @Nonnull [] generateProxyClass(@Nonnull ProxyClassInfo pcInfo) throws Exception {
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        classWriter.visit(
            Opcodes.V1_8,
            Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER,
            pcInfo.proxyName,
            null,
            pcInfo.proxySuperName,
            pcInfo.proxyInterfaces
        );
        classWriter.visitInnerClass(
            pcInfo.innerName,
            pcInfo.proxyName,
            pcInfo.innerSimpleName,
            Opcodes.ACC_PRIVATE
        );
        {
            FieldVisitor visitor = classWriter.visitField(
                Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL,
                "handler",
                HANDLER_DESCRIPTOR,
                null,
                null
            );
            visitor.visitEnd();
        }
        {
            FieldVisitor visitor = classWriter.visitField(
                Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL,
                "methods",
                METHODS_DESCRIPTOR,
                null,
                null
            );
            visitor.visitEnd();
        }
        {
            FieldVisitor visitor = classWriter.visitField(
                Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL,
                "invokers",
                INVOKERS_DESCRIPTOR,
                null,
                null
            );
            visitor.visitEnd();
        }
        {
            MethodVisitor visitor = classWriter.visitMethod(
                Opcodes.ACC_PUBLIC,
                AsmKit.CONSTRUCTOR_NAME,
                "(" + HANDLER_DESCRIPTOR + METHODS_DESCRIPTOR + ")V",
                null,
                null
            );
            visitor.visitVarInsn(Opcodes.ALOAD, 0);
            visitor.visitMethodInsn(
                Opcodes.INVOKESPECIAL,
                pcInfo.proxySuperName,
                AsmKit.CONSTRUCTOR_NAME,
                AsmKit.EMPTY_CONSTRUCTOR_DESCRIPTOR,
                false
            );
            visitor.visitVarInsn(Opcodes.ALOAD, 0);
            visitor.visitVarInsn(Opcodes.ALOAD, 1);
            visitor.visitFieldInsn(Opcodes.PUTFIELD, pcInfo.proxyName, "handler", HANDLER_DESCRIPTOR);
            visitor.visitVarInsn(Opcodes.ALOAD, 0);
            visitor.visitVarInsn(Opcodes.ALOAD, 2);
            visitor.visitFieldInsn(Opcodes.PUTFIELD, pcInfo.proxyName, "methods", METHODS_DESCRIPTOR);
            visitor.visitVarInsn(Opcodes.ALOAD, 0);
            visitor.visitVarInsn(Opcodes.ALOAD, 2);
            visitor.visitInsn(Opcodes.ARRAYLENGTH);
            visitor.visitTypeInsn(Opcodes.ANEWARRAY, INVOKER_NAME);
            visitor.visitFieldInsn(Opcodes.PUTFIELD, pcInfo.proxyName, "invokers", INVOKERS_DESCRIPTOR);
            visitor.visitInsn(Opcodes.RETURN);
            visitor.visitMaxs(0, 0);
            visitor.visitEnd();
        }
        int i = 0;
        for (ProxyMethodInfo pmInfo : pcInfo.methods) {
            generateProxyMethod(classWriter, pcInfo, pmInfo, i);
            generateSuperInvoker(classWriter, pmInfo);
            i++;
        }
        classWriter.visitEnd();
        return classWriter.toByteArray();
    }

    private void generateProxyMethod(
        @Nonnull ClassWriter classWriter,
        @Nonnull ProxyClassInfo pcInfo,
        @Nonnull ProxyMethodInfo pmInfo,
        int i
    ) throws Exception {
        MethodVisitor visitor = classWriter.visitMethod(
            Opcodes.ACC_PUBLIC,
            pmInfo.method.getName(),
            pmInfo.descriptor,
            pmInfo.signature,
            pmInfo.exceptions
        );
        // ProxyInvoker invoker = invokers[i];
        visitor.visitVarInsn(Opcodes.ALOAD, 0);
        visitor.visitFieldInsn(Opcodes.GETFIELD, pcInfo.proxyName, "invokers", INVOKERS_DESCRIPTOR);
        AsmKit.visitConst(visitor, i);
        visitor.visitInsn(Opcodes.AALOAD);
        int invokerPos = AsmKit.paramSize(pmInfo.method.getParameters()) + 1;
        visitor.visitVarInsn(Opcodes.ASTORE, invokerPos);
        visitor.visitVarInsn(Opcodes.ALOAD, invokerPos);
        // if (invoker == null)
        Label ifNonnull = new Label();
        visitor.visitJumpInsn(Opcodes.IFNONNULL, ifNonnull);
        // new Invoker1(i);
        visitor.visitTypeInsn(Opcodes.NEW, pcInfo.innerName);
        // new Invoker1(i).init();
        visitor.visitInsn(Opcodes.DUP);
        visitor.visitVarInsn(Opcodes.ALOAD, 0);
        AsmKit.visitConst(visitor, i);
        visitor.visitMethodInsn(
            Opcodes.INVOKESPECIAL,
            pcInfo.innerName,
            AsmKit.CONSTRUCTOR_NAME,
            "(" + pcInfo.proxyDescriptor + "I)V",
            false
        );
        visitor.visitVarInsn(Opcodes.ASTORE, invokerPos);
        // get invokers
        visitor.visitVarInsn(Opcodes.ALOAD, 0);
        visitor.visitFieldInsn(Opcodes.GETFIELD, pcInfo.proxyName, "invokers", INVOKERS_DESCRIPTOR);
        // invokers[i] = invoker;
        AsmKit.visitConst(visitor, i);
        visitor.visitVarInsn(Opcodes.ALOAD, invokerPos);
        visitor.visitInsn(Opcodes.AASTORE);
        // endif
        visitor.visitLabel(ifNonnull);
        visitor.visitFrame(Opcodes.F_APPEND, 1, new String[]{INVOKER_NAME}, 0, null);
        // get handler
        visitor.visitVarInsn(Opcodes.ALOAD, 0);
        visitor.visitFieldInsn(Opcodes.GETFIELD, pcInfo.proxyName, "handler", HANDLER_DESCRIPTOR);
        // get this
        visitor.visitVarInsn(Opcodes.ALOAD, 0);
        // get methods[i]
        visitor.visitVarInsn(Opcodes.ALOAD, 0);
        visitor.visitFieldInsn(Opcodes.GETFIELD, pcInfo.proxyName, "methods", METHODS_DESCRIPTOR);
        AsmKit.visitConst(visitor, i);
        visitor.visitInsn(Opcodes.AALOAD);
        // get invoker
        visitor.visitVarInsn(Opcodes.ALOAD, invokerPos);
        // new Object[params.size]
        AsmKit.visitConst(visitor, pmInfo.method.getParameterCount());
        visitor.visitTypeInsn(Opcodes.ANEWARRAY, AsmKit.OBJECT_NAME);
        // set array
        int aIndex = 0;
        int pIndex = 1;
        for (Parameter parameter : pmInfo.method.getParameters()) {
            // array[i] = param[i]
            visitor.visitInsn(Opcodes.DUP);
            AsmKit.visitConst(visitor, aIndex++);
            AsmKit.visitLoad(visitor, parameter.getType(), pIndex);
            AsmKit.wrapToObject(visitor, parameter.getType());
            visitor.visitInsn(Opcodes.AASTORE);
            pIndex += AsmKit.varSize(parameter.getType());
        }
        // return handler.invoke(this, methods[0], invoker, args);
        visitor.visitMethodInsn(
            Opcodes.INVOKEINTERFACE,
            HANDLER_NAME,
            HANDLER_INVOKE.getName(),
            HANDLER_INVOKE_DESCRIPTOR,
            true
        );
        // object -> primitive/object
        AsmKit.convertObjectTo(visitor, pmInfo.method.getReturnType());
        AsmKit.visitReturn(visitor, pmInfo.method.getReturnType(), true, false);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    private void generateSuperInvoker(
        @Nonnull ClassWriter classWriter,
        @Nonnull ProxyMethodInfo pmInfo
    ) {
        MethodVisitor visitor = classWriter.visitMethod(
            Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC,
            pmInfo.superInvokerName,
            pmInfo.superInvokerDescriptor,
            pmInfo.superInvokerSignature,
            pmInfo.exceptions
        );
        visitor.visitVarInsn(Opcodes.ALOAD, 0);
        int pIndex = 1;
        for (Parameter parameter : pmInfo.method.getParameters()) {
            AsmKit.visitLoad(visitor, parameter.getType(), pIndex);
            pIndex += AsmKit.varSize(parameter.getType());
        }
        visitor.visitMethodInsn(
            Opcodes.INVOKESPECIAL,
            pmInfo.ownerName,
            pmInfo.method.getName(),
            pmInfo.descriptor,
            pmInfo.isInterface
        );
        AsmKit.visitReturn(visitor, pmInfo.method.getReturnType(), false, false);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    private byte @Nonnull [] generateInvokerClass(@Nonnull ProxyClassInfo pcInfo) {
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        classWriter.visit(
            Opcodes.V1_8,
            Opcodes.ACC_FINAL | Opcodes.ACC_SUPER,
            pcInfo.innerName,
            null,
            AsmKit.OBJECT_NAME,
            new String[]{INVOKER_NAME}
        );
        classWriter.visitInnerClass(
            pcInfo.innerName,
            pcInfo.proxyName,
            pcInfo.innerSimpleName,
            Opcodes.ACC_PRIVATE
        );
        {
            // int index;
            FieldVisitor visitor = classWriter.visitField(
                Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL,
                "index",
                "I",
                null,
                null
            );
            visitor.visitEnd();
        }
        {
            // outer class, which is the proxy class;
            FieldVisitor visitor = classWriter.visitField(
                Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC,
                "this$0",
                pcInfo.proxyDescriptor,
                null,
                null);
            visitor.visitEnd();
        }
        {
            // constructor
            MethodVisitor visitor = classWriter.visitMethod(
                0,
                AsmKit.CONSTRUCTOR_NAME,
                "(" + pcInfo.proxyDescriptor + "I)V",
                null,
                null
            );
            visitor.visitVarInsn(Opcodes.ALOAD, 0);
            visitor.visitVarInsn(Opcodes.ALOAD, 1);
            visitor.visitFieldInsn(Opcodes.PUTFIELD, pcInfo.innerName, "this$0", pcInfo.proxyDescriptor);
            visitor.visitVarInsn(Opcodes.ALOAD, 0);
            visitor.visitMethodInsn(
                Opcodes.INVOKESPECIAL,
                AsmKit.OBJECT_NAME,
                AsmKit.CONSTRUCTOR_NAME,
                AsmKit.EMPTY_CONSTRUCTOR_DESCRIPTOR,
                false
            );
            visitor.visitVarInsn(Opcodes.ALOAD, 0);
            visitor.visitVarInsn(Opcodes.ILOAD, 2);
            visitor.visitFieldInsn(Opcodes.PUTFIELD, pcInfo.innerName, "index", "I");
            visitor.visitInsn(Opcodes.RETURN);
            visitor.visitMaxs(0, 0);
            visitor.visitEnd();
        }
        {
            // invoke(Object inst, Object... args);
            MethodVisitor visitor = classWriter.visitMethod(
                Opcodes.ACC_PUBLIC | Opcodes.ACC_VARARGS,
                INVOKER_INVOKE.getName(),
                INVOKER_INVOKE_DESCRIPTOR,
                null,
                INVOKER_INVOKE_EXCEPTIONS
            );
            // get index;
            visitor.visitVarInsn(Opcodes.ALOAD, 0);
            visitor.visitFieldInsn(Opcodes.GETFIELD, pcInfo.innerName, "index", "I");
            // switch (index) {}
            Label[] labels = new Label[pcInfo.methods.size()];
            for (int i = 0; i < labels.length; i++) {
                labels[i] = new Label();
            }
            Label labelLast = new Label();
            visitor.visitTableSwitchInsn(0, pcInfo.methods.size() - 1, labelLast, labels);
            int i = 0;
            for (ProxyMethodInfo pmInfo : pcInfo.methods) {
                Method method = pmInfo.method;
                String methodOwnerName = pmInfo.ownerName;
                if (ClassKit.isProtected(method)) {
                    methodOwnerName = pcInfo.proxyName;
                }
                visitor.visitLabel(labels[i++]);
                visitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
                // get outer
                visitor.visitVarInsn(Opcodes.ALOAD, 1);
                visitor.visitTypeInsn(Opcodes.CHECKCAST, methodOwnerName);
                // loads args
                loadParameters(visitor, pmInfo.method);
                // return outer.invoke(args0, args1...);
                AsmKit.invokeVirtual(
                    visitor, methodOwnerName, method.getName(), pmInfo.descriptor, pmInfo.isInterface
                );
                Class<?> returnType = AsmKit.wrapToObject(visitor, method.getReturnType());
                AsmKit.visitReturn(visitor, returnType, false, true);
            }
            visitor.visitLabel(labelLast);
            visitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
            visitor.visitInsn(Opcodes.ACONST_NULL);
            visitor.visitInsn(Opcodes.ARETURN);
            visitor.visitMaxs(0, 0);
            visitor.visitEnd();
        }
        {
            // invokeSuper(Object inst, Object... args);
            MethodVisitor visitor = classWriter.visitMethod(
                Opcodes.ACC_PUBLIC | Opcodes.ACC_VARARGS,
                INVOKER_INVOKE_SUPER.getName(),
                INVOKER_INVOKE_SUPER_DESCRIPTOR,
                null,
                INVOKER_INVOKE_SUPER_EXCEPTIONS
            );
            // get index;
            visitor.visitVarInsn(Opcodes.ALOAD, 0);
            visitor.visitFieldInsn(Opcodes.GETFIELD, pcInfo.innerName, "index", "I");
            // switch (index) {}
            Label[] labels = new Label[pcInfo.methods.size()];
            for (int i = 0; i < labels.length; i++) {
                labels[i] = new Label();
            }
            Label labelLast = new Label();
            visitor.visitTableSwitchInsn(0, pcInfo.methods.size() - 1, labelLast, labels);
            int i = 0;
            for (ProxyMethodInfo pmInfo : pcInfo.methods) {
                visitor.visitLabel(labels[i++]);
                visitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
                // get outer
                visitor.visitVarInsn(Opcodes.ALOAD, 1);
                visitor.visitTypeInsn(Opcodes.CHECKCAST, pcInfo.proxyName);
                // loads args
                loadParameters(visitor, pmInfo.method);
                // return outer.invoke(inst, args0, args1...);
                visitor.visitMethodInsn(
                    Opcodes.INVOKESTATIC,
                    pcInfo.proxyName,
                    pmInfo.superInvokerName,//"access$001",
                    pmInfo.superInvokerDescriptor,
                    false
                );
                Class<?> returnType = AsmKit.wrapToObject(visitor, pmInfo.method.getReturnType());
                AsmKit.visitReturn(visitor, returnType, false, true);
            }
            visitor.visitLabel(labelLast);
            visitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
            visitor.visitInsn(Opcodes.ACONST_NULL);
            visitor.visitInsn(Opcodes.ARETURN);
            visitor.visitMaxs(0, 0);
            visitor.visitEnd();
        }
        classWriter.visitEnd();
        return classWriter.toByteArray();
    }

    private void loadParameters(MethodVisitor visitor, Executable executable) {
        int pIndex = 0;
        for (Parameter parameter : executable.getParameters()) {
            // get args
            visitor.visitVarInsn(Opcodes.ALOAD, 2);
            AsmKit.visitConst(visitor, pIndex++);
            // get args[pIndex]
            visitor.visitInsn(Opcodes.AALOAD);
            AsmKit.convertObjectTo(visitor, parameter.getType());
        }
    }

    private static final class ProxyClassInfo {

        private final @Nonnull String proxyName;
        private final @Nonnull String proxyDescriptor;
        private final @Nonnull String proxySuperName;
        private final @Nonnull String @Nullable [] proxyInterfaces;
        private final @Nonnull String innerSimpleName;
        private final @Nonnull String innerName;
        private final @Nonnull List<ProxyMethodInfo> methods;

        private ProxyClassInfo(
            @Nonnull String proxyName,
            @Nonnull String proxyDescriptor,
            @Nonnull String proxySuperName,
            @Nonnull String @Nullable [] proxyInterfaces,
            @Nonnull String innerSimpleName,
            @Nonnull String innerName,
            @Nonnull List<ProxyMethodInfo> methods
        ) {
            this.proxyName = proxyName;
            this.proxyDescriptor = proxyDescriptor;
            this.proxySuperName = proxySuperName;
            this.proxyInterfaces = proxyInterfaces;
            this.innerSimpleName = innerSimpleName;
            this.innerName = innerName;
            this.methods = methods;
        }
    }

    private static final class ProxyMethodInfo {

        private final @Nonnull Method method;
        private final @Nonnull String ownerName;
        private final @Nonnull String descriptor;
        private final @Nullable String signature;
        private final @Nonnull String @Nullable [] exceptions;
        private final @Nonnull String superInvokerName;
        private final @Nonnull String superInvokerDescriptor;
        private final @Nullable String superInvokerSignature;
        private final boolean isInterface;

        private ProxyMethodInfo(
            @Nonnull Method method,
            @Nonnull String ownerName,
            @Nonnull String descriptor,
            @Nullable String signature,
            @Nonnull String @Nullable [] exceptions,
            @Nonnull String superInvokerName,
            @Nonnull String superInvokerDescriptor,
            @Nullable String superInvokerSignature,
            boolean isInterface
        ) {
            this.method = method;
            this.ownerName = ownerName;
            this.descriptor = descriptor;
            this.signature = signature;
            this.exceptions = exceptions;
            this.superInvokerName = superInvokerName;
            this.superInvokerDescriptor = superInvokerDescriptor;
            this.superInvokerSignature = superInvokerSignature;
            this.isInterface = isInterface;
        }
    }

    private static final class AsmProxySpec implements ProxySpec {

        private final @Nonnull Class<?> proxyClass;
        private final @Nonnull Class<?> proxiedClass;
        private final @Nonnull List<@Nonnull Class<?>> proxiedInterfaces;
        private final @Nonnull ProxyHandler proxyHandler;
        private final @Nonnull Method @Nonnull [] methods;

        private AsmProxySpec(
            @Nonnull Class<?> proxyClass,
            @Nonnull Class<?> proxiedClass,
            @Nonnull List<@Nonnull Class<?>> proxiedInterfaces,
            @Nonnull ProxyHandler proxyHandler,
            @Nonnull Method @Nonnull [] methods
        ) {
            this.proxyClass = proxyClass;
            this.proxiedClass = proxiedClass;
            this.proxiedInterfaces = proxiedInterfaces;
            this.proxyHandler = proxyHandler;
            this.methods = methods;
        }

        @Override
        public <T> @Nonnull T newInstance() throws AsmProxyException {
            return Fs.uncheck(() -> {
                Constructor<?> constructor = proxyClass.getConstructor(ProxyHandler.class, Method[].class);
                return Fs.as(constructor.newInstance(proxyHandler, methods));
            }, AsmProxyException::new);
        }

        @Override
        public @Nonnull Class<?> proxyClass() {
            return proxyClass;
        }

        @Override
        public @Nonnull Class<?> proxiedClass() {
            return proxiedClass;
        }

        @Override
        public @Nonnull List<@Nonnull Class<?>> proxiedInterfaces() {
            return proxiedInterfaces;
        }

        @Override
        public @Nonnull ProxyHandler proxyHandler() {
            return proxyHandler;
        }
    }

    /**
     * This exception is the sub-exception of {@link ProxyException} for <a href="https://asm.ow2.io/">ASM</a> proxy
     * implementation.
     *
     * @author sunqian
     */
    public static class AsmProxyException extends ProxyException {
        /**
         * Constructs with the cause.
         *
         * @param cause the cause
         */
        public AsmProxyException(@Nullable Throwable cause) {
            super(cause);
        }
    }
}