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;
@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;
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;
}
public boolean isSupportsInterceptedProxy() {
return interceptedProxyClassName != null;
}
public boolean isAbstract() {
return isAbstract;
}
public boolean isInterface() {
return isInterface;
}
public boolean isDefault() {
return isDefault;
}
public boolean isSuspend() {
return isSuspend;
}
public String getClassName() {
return className;
}
public String getInternalName() {
return internalName;
}
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);
if (!(annotationMetadata instanceof AnnotationMetadataReference)) {
writeAnnotationMetadataStaticInitializer(classWriter);
}
writeGetAnnotationMetadataMethod(classWriter);
MethodVisitor executorMethodConstructor;
GeneratorAdapter constructorWriter;
if (interceptedProxyBridgeMethodName != null) {
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);
}
constructorWriter.loadThis();
constructorWriter.loadThis();
constructorWriter.push(declaringTypeObject);
constructorWriter.push(methodName);
if (genericReturnType instanceof Class && ((Class) genericReturnType).isPrimitive()) {
Class javaType = (Class) genericReturnType;
String constantName = javaType.getName().toUpperCase(Locale.ENGLISH);
Type type = Type.getType(Argument.class);
constructorWriter.getStatic(type, constantName, type);
} else {
buildArgumentWithGenerics(
constructorWriter,
methodName,
Collections.singletonMap(genericReturnType, returnTypeGenericTypes)
);
}
if (hasArgs) {
pushBuildArgumentsForMethod(
getTypeReferenceForName(getClassName()),
classWriter,
constructorWriter,
genericArgumentTypes,
argumentAnnotationMetadata,
genericTypes,
loadTypeMethods);
for (AnnotationMetadata value : argumentAnnotationMetadata.values()) {
DefaultAnnotationMetadata.contributeDefaults(this.annotationMetadata, value);
}
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);
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();
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();
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());
}
protected void buildInvokeMethod(
Type declaringTypeObject,
String methodName,
Object returnType,
Collection<Object> argumentTypes,
GeneratorAdapter invokeMethodVisitor) {
Type returnTypeObject = getTypeReference(returnType);
invokeMethodVisitor.visitVarInsn(ALOAD, 1);
invokeMethodVisitor.dup();
if (interceptedProxyClassName != null) {
Label invokeTargetBlock = new Label();
Type interceptedProxyType = getObjectType(interceptedProxyClassName);
invokeMethodVisitor.loadThis();
invokeMethodVisitor.getField(Type.getObjectType(internalName), FIELD_INTERCEPTABLE, Type.getType(boolean.class));
invokeMethodVisitor.push(true);
invokeMethodVisitor.ifCmp(Type.BOOLEAN_TYPE, GeneratorAdapter.NE, invokeTargetBlock);
invokeMethodVisitor.loadArg(0);
invokeMethodVisitor.instanceOf(interceptedProxyType);
invokeMethodVisitor.push(true);
invokeMethodVisitor.ifCmp(Type.BOOLEAN_TYPE, GeneratorAdapter.NE, invokeTargetBlock);
pushCastToType(invokeMethodVisitor, interceptedProxyType.getClassName());
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);
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);
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();
}
}