/*
 * Copyright 2017-2020 original authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.micronaut.inject.writer;

import edu.umd.cs.findbugs.annotations.NonNull;
import io.micronaut.context.AbstractExecutableMethod;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.reflect.ReflectionUtils;
import io.micronaut.core.type.Argument;
import io.micronaut.inject.ExecutableMethod;
import io.micronaut.inject.annotation.AnnotationMetadataReference;
import io.micronaut.inject.annotation.DefaultAnnotationMetadata;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;

Writes out ExecutableMethod implementations.
Author:Graeme Rocher
Since:1.0
/** * Writes out {@link io.micronaut.inject.ExecutableMethod} implementations. * * @author Graeme Rocher * @since 1.0 */
@Internal public class ExecutableMethodWriter extends AbstractAnnotationMetadataWriter implements Opcodes { protected static final org.objectweb.asm.commons.Method METHOD_INVOKE_INTERNAL = org.objectweb.asm.commons.Method.getMethod( ReflectionUtils.getRequiredInternalMethod(AbstractExecutableMethod.class, "invokeInternal", Object.class, Object[].class)); protected static final org.objectweb.asm.commons.Method METHOD_IS_ABSTRACT = org.objectweb.asm.commons.Method.getMethod( ReflectionUtils.getRequiredInternalMethod(ExecutableMethod.class, "isAbstract")); protected static final org.objectweb.asm.commons.Method METHOD_IS_SUSPEND = org.objectweb.asm.commons.Method.getMethod( ReflectionUtils.getRequiredInternalMethod(ExecutableMethod.class, "isSuspend")); protected static final Method METHOD_GET_TARGET = Method.getMethod("java.lang.reflect.Method resolveTargetMethod()"); private static final Type TYPE_REFLECTION_UTILS = Type.getType(ReflectionUtils.class); private static final org.objectweb.asm.commons.Method METHOD_GET_REQUIRED_METHOD = org.objectweb.asm.commons.Method.getMethod( ReflectionUtils.getRequiredInternalMethod(ReflectionUtils.class, "getRequiredMethod", Class.class, String.class, Class[].class)); private static final String FIELD_INTERCEPTABLE = "$interceptable"; protected final Type methodType; private final ClassWriter classWriter; private final String className; private final String internalName; private final String beanFullClassName; private final String methodProxyShortName; private final boolean isInterface; private final boolean isAbstract; private final boolean isSuspend; private final boolean isDefault; private final String interceptedProxyClassName; private final String interceptedProxyBridgeMethodName;
Params:
  • beanFullClassName – The bean full class name
  • methodClassName – The method class name
  • methodProxyShortName – The method proxy short name
  • isInterface – Whether is an interface
  • isAbstract – Whether the method is abstract
  • isDefault – Whether the method is a default method
  • isSuspend – Whether the method is Kotlin suspend function
  • originatingElements – The originating elements
  • annotationMetadata – The annotation metadata
  • interceptedProxyClassName – The intercepted proxy class name
  • interceptedProxyBridgeMethodName – The intercepted proxy bridge method name
/** * @param beanFullClassName The bean full class name * @param methodClassName The method class name * @param methodProxyShortName The method proxy short name * @param isInterface Whether is an interface * @param isAbstract Whether the method is abstract * @param isDefault Whether the method is a default method * @param isSuspend Whether the method is Kotlin suspend function * @param originatingElements The originating elements * @param annotationMetadata The annotation metadata * @param interceptedProxyClassName The intercepted proxy class name * @param interceptedProxyBridgeMethodName The intercepted proxy bridge method name */
public ExecutableMethodWriter( String beanFullClassName, String methodClassName, String methodProxyShortName, boolean isInterface, boolean isAbstract, boolean isDefault, boolean isSuspend, OriginatingElements originatingElements, AnnotationMetadata annotationMetadata, String interceptedProxyClassName, String interceptedProxyBridgeMethodName) { super(methodClassName, originatingElements, annotationMetadata, true); this.classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); this.beanFullClassName = beanFullClassName; this.methodProxyShortName = methodProxyShortName; this.className = methodClassName; this.internalName = getInternalName(methodClassName); this.methodType = getObjectType(methodClassName); this.isInterface = isInterface; this.isAbstract = isAbstract; this.isDefault = isDefault; this.isSuspend = isSuspend; this.interceptedProxyClassName = interceptedProxyClassName; this.interceptedProxyBridgeMethodName = interceptedProxyBridgeMethodName; }
Returns:Is supports intercepted proxy.
/** * @return Is supports intercepted proxy. */
public boolean isSupportsInterceptedProxy() { return interceptedProxyClassName != null; }
Returns:Is the method abstract.
/** * @return Is the method abstract. */
public boolean isAbstract() { return isAbstract; }
Returns:Is the method in an interface.
/** * @return Is the method in an interface. */
public boolean isInterface() { return isInterface; }
Returns:Is the method a default method.
/** * @return Is the method a default method. */
public boolean isDefault() { return isDefault; }
Returns:Is the method suspend.
/** * @return Is the method suspend. */
public boolean isSuspend() { return isSuspend; }
Returns:The class name
/** * @return The class name */
public String getClassName() { return className; }
Returns:The internal name
/** * @return The internal name */
public String getInternalName() { return internalName; }
Write the method.
Params:
  • declaringType – The declaring type
  • returnType – The return type
  • genericReturnType – The generic return type
  • returnTypeGenericTypes – The return type generics
  • methodName – The method name
  • argumentTypes – The argument types
  • genericArgumentTypes – The generic argument types
  • argumentAnnotationMetadata – The argument annotation metadata
  • genericTypes – The generic types
/** * Write the method. * * @param declaringType The declaring type * @param returnType The return type * @param genericReturnType The generic return type * @param returnTypeGenericTypes The return type generics * @param methodName The method name * @param argumentTypes The argument types * @param genericArgumentTypes The generic argument types * @param argumentAnnotationMetadata The argument annotation metadata * @param genericTypes The generic types */
public void visitMethod(Object declaringType, Object returnType, Object genericReturnType, Map<String, Object> returnTypeGenericTypes, String methodName, Map<String, Object> argumentTypes, Map<String, Object> genericArgumentTypes, Map<String, AnnotationMetadata> argumentAnnotationMetadata, Map<String, Map<String, Object>> genericTypes) { Type declaringTypeObject = getTypeReference(declaringType); boolean hasArgs = !argumentTypes.isEmpty(); Collection<Object> argumentTypeClasses = hasArgs ? argumentTypes.values() : Collections.emptyList(); classWriter.visit(V1_8, ACC_SYNTHETIC, internalName, null, Type.getInternalName(AbstractExecutableMethod.class), null); classWriter.visitAnnotation(TYPE_GENERATED.getDescriptor(), false); // initialize and write the annotation metadata if (!(annotationMetadata instanceof AnnotationMetadataReference)) { writeAnnotationMetadataStaticInitializer(classWriter); } writeGetAnnotationMetadataMethod(classWriter); MethodVisitor executorMethodConstructor; GeneratorAdapter constructorWriter; if (interceptedProxyBridgeMethodName != null) { // Create default constructor call other one with 'false' String descriptor = Type.getDescriptor(boolean.class); classWriter.visitField(ACC_FINAL | ACC_PRIVATE, FIELD_INTERCEPTABLE, descriptor, null, null); GeneratorAdapter defaultConstructorWriter = new GeneratorAdapter(startConstructor(classWriter), Opcodes.ACC_PUBLIC, CONSTRUCTOR_NAME, DESCRIPTOR_DEFAULT_CONSTRUCTOR); String executorMethodConstructorDescriptor = getConstructorDescriptor(boolean.class); executorMethodConstructor = startConstructor(classWriter, boolean.class); constructorWriter = new GeneratorAdapter(executorMethodConstructor, Opcodes.ACC_PUBLIC, CONSTRUCTOR_NAME, executorMethodConstructorDescriptor); defaultConstructorWriter.loadThis(); defaultConstructorWriter.push(false); defaultConstructorWriter.visitMethodInsn(INVOKESPECIAL, internalName, CONSTRUCTOR_NAME, executorMethodConstructorDescriptor, false); defaultConstructorWriter.visitInsn(RETURN); defaultConstructorWriter.visitMaxs(DEFAULT_MAX_STACK, 1); constructorWriter.loadThis(); constructorWriter.loadArg(0); constructorWriter.putField(Type.getObjectType(internalName), FIELD_INTERCEPTABLE, Type.getType(boolean.class)); } else { executorMethodConstructor = startConstructor(classWriter); constructorWriter = new GeneratorAdapter(executorMethodConstructor, Opcodes.ACC_PUBLIC, CONSTRUCTOR_NAME, DESCRIPTOR_DEFAULT_CONSTRUCTOR); } // ALOAD 0 constructorWriter.loadThis(); // load 'this' constructorWriter.loadThis(); // 1st argument: the declaring class constructorWriter.push(declaringTypeObject); // 2nd argument: the method name constructorWriter.push(methodName); // 3rd argument the generic return type if (genericReturnType instanceof Class && ((Class) genericReturnType).isPrimitive()) { Class javaType = (Class) genericReturnType; String constantName = javaType.getName().toUpperCase(Locale.ENGLISH); // refer to constant for primitives Type type = Type.getType(Argument.class); constructorWriter.getStatic(type, constantName, type); } else { // Argument.of(genericReturnType, returnTypeGenericTypes) buildArgumentWithGenerics( constructorWriter, methodName, Collections.singletonMap(genericReturnType, returnTypeGenericTypes) ); } if (hasArgs) { // 4th argument: the generic types pushBuildArgumentsForMethod( getTypeReferenceForName(getClassName()), classWriter, constructorWriter, genericArgumentTypes, argumentAnnotationMetadata, genericTypes, loadTypeMethods); for (AnnotationMetadata value : argumentAnnotationMetadata.values()) { DefaultAnnotationMetadata.contributeDefaults(this.annotationMetadata, value); } // now invoke super(..) if no arg constructor invokeConstructor( executorMethodConstructor, AbstractExecutableMethod.class, Class.class, String.class, Argument.class, Argument[].class); } else { invokeConstructor( executorMethodConstructor, AbstractExecutableMethod.class, Class.class, String.class, Argument.class); } constructorWriter.visitInsn(RETURN); constructorWriter.visitMaxs(DEFAULT_MAX_STACK, 1); // add isAbstract method GeneratorAdapter isAbstractMethod = new GeneratorAdapter(classWriter.visitMethod( ACC_PUBLIC | ACC_FINAL, METHOD_IS_ABSTRACT.getName(), METHOD_IS_ABSTRACT.getDescriptor(), null, null), ACC_PUBLIC, METHOD_IS_ABSTRACT.getName(), METHOD_IS_ABSTRACT.getDescriptor() ); isAbstractMethod.push(isAbstract()); isAbstractMethod.returnValue(); isAbstractMethod.visitMaxs(1, 1); isAbstractMethod.endMethod(); // add isSuspend method GeneratorAdapter isSuspendMethod = new GeneratorAdapter(classWriter.visitMethod( ACC_PUBLIC | ACC_FINAL, METHOD_IS_SUSPEND.getName(), METHOD_IS_SUSPEND.getDescriptor(), null, null), ACC_PUBLIC, METHOD_IS_SUSPEND.getName(), METHOD_IS_SUSPEND.getDescriptor() ); isSuspendMethod.push(isSuspend()); isSuspendMethod.returnValue(); isSuspendMethod.visitMaxs(1, 1); isSuspendMethod.endMethod(); // invoke the methods with the passed arguments String invokeDescriptor = METHOD_INVOKE_INTERNAL.getDescriptor(); String invokeInternalName = METHOD_INVOKE_INTERNAL.getName(); GeneratorAdapter invokeMethod = new GeneratorAdapter(classWriter.visitMethod( Opcodes.ACC_PUBLIC, invokeInternalName, invokeDescriptor, null, null), ACC_PUBLIC, invokeInternalName, invokeDescriptor ); buildInvokeMethod(declaringTypeObject, methodName, returnType, argumentTypeClasses, invokeMethod); buildResolveTargetMethod(methodName, declaringTypeObject, hasArgs, argumentTypeClasses); for (GeneratorAdapter method : loadTypeMethods.values()) { method.visitMaxs(3, 1); method.visitEnd(); } } @Override public void accept(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOException { try (OutputStream outputStream = classWriterOutputVisitor.visitClass(className, getOriginatingElements())) { outputStream.write(classWriter.toByteArray()); } } @NonNull @Override protected final GeneratorAdapter beginAnnotationMetadataMethod(ClassWriter classWriter) { return startProtectedMethod(classWriter, "resolveAnnotationMetadata", AnnotationMetadata.class.getName()); }
Params:
  • declaringTypeObject – The declaring object type
  • methodName – The method name
  • returnType – The return type
  • argumentTypes – The argument types
  • invokeMethodVisitor – The invoke method visitor
/** * @param declaringTypeObject The declaring object type * @param methodName The method name * @param returnType The return type * @param argumentTypes The argument types * @param invokeMethodVisitor The invoke method visitor */
protected void buildInvokeMethod( Type declaringTypeObject, String methodName, Object returnType, Collection<Object> argumentTypes, GeneratorAdapter invokeMethodVisitor) { Type returnTypeObject = getTypeReference(returnType); // load this invokeMethodVisitor.visitVarInsn(ALOAD, 1); // duplicate target invokeMethodVisitor.dup(); if (interceptedProxyClassName != null) { Label invokeTargetBlock = new Label(); Type interceptedProxyType = getObjectType(interceptedProxyClassName); // load this.$interceptable field value invokeMethodVisitor.loadThis(); invokeMethodVisitor.getField(Type.getObjectType(internalName), FIELD_INTERCEPTABLE, Type.getType(boolean.class)); // check if it equals true invokeMethodVisitor.push(true); invokeMethodVisitor.ifCmp(Type.BOOLEAN_TYPE, GeneratorAdapter.NE, invokeTargetBlock); // target instanceOf intercepted proxy invokeMethodVisitor.loadArg(0); invokeMethodVisitor.instanceOf(interceptedProxyType); // check if instanceOf invokeMethodVisitor.push(true); invokeMethodVisitor.ifCmp(Type.BOOLEAN_TYPE, GeneratorAdapter.NE, invokeTargetBlock); pushCastToType(invokeMethodVisitor, interceptedProxyType.getClassName()); // load arguments Iterator<Object> iterator = argumentTypes.iterator(); for (int i = 0; i < argumentTypes.size(); i++) { invokeMethodVisitor.loadArg(1); invokeMethodVisitor.push(i); invokeMethodVisitor.visitInsn(AALOAD); pushCastToType(invokeMethodVisitor, iterator.next()); } invokeMethodVisitor.visitMethodInsn(INVOKEVIRTUAL, interceptedProxyType.getInternalName(), interceptedProxyBridgeMethodName, getMethodDescriptor(returnType, argumentTypes), false); if (returnTypeObject.equals(Type.VOID_TYPE)) { invokeMethodVisitor.visitInsn(ACONST_NULL); } else { pushBoxPrimitiveIfNecessary(returnType, invokeMethodVisitor); } invokeMethodVisitor.visitInsn(ARETURN); invokeMethodVisitor.visitLabel(invokeTargetBlock); // remove parent invokeMethodVisitor.pop(); } pushCastToType(invokeMethodVisitor, declaringTypeObject.getClassName()); boolean hasArgs = !argumentTypes.isEmpty(); String methodDescriptor; if (hasArgs) { methodDescriptor = getMethodDescriptor(returnType, argumentTypes); int argCount = argumentTypes.size(); Iterator<Object> argIterator = argumentTypes.iterator(); for (int i = 0; i < argCount; i++) { invokeMethodVisitor.visitVarInsn(ALOAD, 2); invokeMethodVisitor.push(i); invokeMethodVisitor.visitInsn(AALOAD); // cast the return value to the correct type pushCastToType(invokeMethodVisitor, argIterator.next()); } } else { methodDescriptor = getMethodDescriptor(returnType, Collections.emptyList()); } invokeMethodVisitor.visitMethodInsn(isInterface ? INVOKEINTERFACE : INVOKEVIRTUAL, declaringTypeObject.getInternalName(), methodName, methodDescriptor, isInterface); if (returnTypeObject.equals(Type.VOID_TYPE)) { invokeMethodVisitor.visitInsn(ACONST_NULL); } else { pushBoxPrimitiveIfNecessary(returnType, invokeMethodVisitor); } invokeMethodVisitor.visitInsn(ARETURN); invokeMethodVisitor.visitMaxs(DEFAULT_MAX_STACK, 1); invokeMethodVisitor.visitEnd(); } private void buildResolveTargetMethod(String methodName, Type declaringTypeObject, boolean hasArgs, Collection<Object> argumentTypeClasses) { String targetMethodInternalName = METHOD_GET_TARGET.getName(); String targetMethodDescriptor = METHOD_GET_TARGET.getDescriptor(); GeneratorAdapter getTargetMethod = new GeneratorAdapter(classWriter.visitMethod( ACC_PUBLIC | ACC_FINAL, targetMethodInternalName, targetMethodDescriptor, null, null), ACC_PUBLIC | ACC_FINAL, targetMethodInternalName, targetMethodDescriptor ); getTargetMethod.push(declaringTypeObject); getTargetMethod.push(methodName); if (hasArgs) { int len = argumentTypeClasses.size(); Iterator<Object> iter = argumentTypeClasses.iterator(); pushNewArray(getTargetMethod, Class.class, len); for (int i = 0; i < len; i++) { Object type = iter.next(); pushStoreInArray( getTargetMethod, i, len, () -> getTargetMethod.push(getTypeReference(type)) ); } } else { getTargetMethod.getStatic(TYPE_REFLECTION_UTILS, "EMPTY_CLASS_ARRAY", Type.getType(Class[].class)); } getTargetMethod.invokeStatic(TYPE_REFLECTION_UTILS, METHOD_GET_REQUIRED_METHOD); getTargetMethod.returnValue(); getTargetMethod.visitMaxs(1, 1); getTargetMethod.endMethod(); } }