package net.bytebuddy.implementation.bytecode.member; import lombok.EqualsAndHashCode; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.description.type.TypeList; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.bytecode.StackManipulation; import net.bytebuddy.implementation.bytecode.assign.TypeCasting; import org.objectweb.asm.Handle; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.util.List; /** * A builder for a method invocation. */ public enum MethodInvocation { /** * A virtual method invocation. */ VIRTUAL(Opcodes.INVOKEVIRTUAL, Opcodes.H_INVOKEVIRTUAL), /** * An interface-typed virtual method invocation. */ INTERFACE(Opcodes.INVOKEINTERFACE, Opcodes.H_INVOKEINTERFACE), /** * A static method invocation. */ STATIC(Opcodes.INVOKESTATIC, Opcodes.H_INVOKESTATIC), /** * A specialized pseudo-virtual method invocation for a non-constructor. */ SPECIAL(Opcodes.INVOKESPECIAL, Opcodes.H_INVOKESPECIAL), /** * A specialized pseudo-virtual method invocation for a constructor. */ SPECIAL_CONSTRUCTOR(Opcodes.INVOKESPECIAL, Opcodes.H_NEWINVOKESPECIAL); /** * The opcode for invoking a method. */ private final int invocationOpcode; /** * The handle being used for a dynamic method invocation. */ private final int handle; /** * Creates a new type of method invocation. * * @param callOpcode The opcode for invoking a method. * @param handle The handle being used for a dynamic method invocation. */ MethodInvocation(int callOpcode, int handle) { this.invocationOpcode = callOpcode; this.handle = handle; } /** * Creates a method invocation with an implicitly determined invocation type. * * @param methodDescription The method to be invoked. * @return A stack manipulation with implicitly determined invocation type. */ public static WithImplicitInvocationTargetType invoke(MethodDescription.InDefinedShape methodDescription) { if (methodDescription.isTypeInitializer()) { return IllegalInvocation.INSTANCE; } else if (methodDescription.isStatic()) { // Check this property first, private static methods must use INVOKESTATIC return STATIC.new Invocation(methodDescription); } else if (methodDescription.isConstructor()) { return SPECIAL_CONSTRUCTOR.new Invocation(methodDescription); // Check this property second, constructors might be private } else if (methodDescription.isPrivate()) { return SPECIAL.new Invocation(methodDescription); } else if (methodDescription.getDeclaringType().isInterface()) { // Check this property last, default methods must be called by INVOKESPECIAL return INTERFACE.new Invocation(methodDescription); } else { return VIRTUAL.new Invocation(methodDescription); } } /** * Creates a method invocation with an implicitly determined invocation type. If the method's return type derives from its declared shape, the value * is additionally casted to the value of the generically resolved method. * * @param methodDescription The method to be invoked. * @return A stack manipulation with implicitly determined invocation type. */ public static WithImplicitInvocationTargetType invoke(MethodDescription methodDescription) { MethodDescription.InDefinedShape declaredMethod = methodDescription.asDefined(); return declaredMethod.getReturnType().asErasure().equals(methodDescription.getReturnType().asErasure()) ? invoke(declaredMethod) : OfGenericMethod.of(methodDescription, invoke(declaredMethod)); } /** * An illegal implicit method invocation. */ protected enum IllegalInvocation implements WithImplicitInvocationTargetType { /** * The singleton instance. */ INSTANCE; @Override public StackManipulation virtual(TypeDescription invocationTarget) { return Illegal.INSTANCE; } @Override public StackManipulation special(TypeDescription invocationTarget) { return Illegal.INSTANCE; } @Override public StackManipulation dynamic(String methodName, TypeDescription returnType, List<? extends TypeDescription> methodType, List<?> arguments) { return Illegal.INSTANCE; } @Override public StackManipulation onHandle(HandleType type) { return Illegal.INSTANCE; } @Override public boolean isValid() { return false; } @Override public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) { return Illegal.INSTANCE.apply(methodVisitor, implementationContext); } } /** * Represents a method invocation where the invocation type (static, virtual, special, interface) is derived * from the given method's description. */ public interface WithImplicitInvocationTargetType extends StackManipulation { /** * Transforms this method invocation into a virtual (or interface) method invocation on the given type. * * @param invocationTarget The type on which the method is to be invoked virtually on. * @return A stack manipulation representing this method invocation. */ StackManipulation virtual(TypeDescription invocationTarget); /** * Transforms this method invocation into a special invocation on the given type. * * @param invocationTarget The type on which the method is to be invoked specially on. * @return A stack manipulation representing this method invocation. */ StackManipulation special(TypeDescription invocationTarget); /** * Invokes the method as a bootstrap method to bind a call site with the given properties. Note that the * Java virtual machine currently only knows how to resolve bootstrap methods that link static methods * or a constructor. * * @param methodName The name of the method to be bound. * @param returnType The return type of the method to be bound. * @param methodType The parameter types of the method to be bound. * @param arguments The arguments to be passed to the bootstrap method. * @return A stack manipulation that represents the dynamic method invocation. */ StackManipulation dynamic(String methodName, TypeDescription returnType, List<? extends TypeDescription> methodType, List<?> arguments); /** * Invokes the method via a {@code MethodHandle}. * * @param type The type of invocation. * @return A stack manipulation that represents a method call of the specified method via a method handle. */ StackManipulation onHandle(HandleType type); } /** * A method invocation of a generically resolved method. */ @EqualsAndHashCode protected static class OfGenericMethod implements WithImplicitInvocationTargetType { /** * The generically resolved return type of the method. */ private final TypeDescription targetType; /** * The invocation of the method in its defined shape. */ private final WithImplicitInvocationTargetType invocation; /** * Creates a generic method invocation. * * @param targetType The generically resolved return type of the method. * @param invocation The invocation of the method in its defined shape. */ protected OfGenericMethod(TypeDescription targetType, WithImplicitInvocationTargetType invocation) { this.targetType = targetType; this.invocation = invocation; } /** * Creates a generic access dispatcher for a given method. * * @param methodDescription The generically resolved return type of the method. * @param invocation The invocation of the method in its defined shape. * @return A method access dispatcher for the given method. */ protected static WithImplicitInvocationTargetType of(MethodDescription methodDescription, WithImplicitInvocationTargetType invocation) { return new OfGenericMethod(methodDescription.getReturnType().asErasure(), invocation); } @Override public StackManipulation virtual(TypeDescription invocationTarget) { return new StackManipulation.Compound(invocation.virtual(invocationTarget), TypeCasting.to(targetType)); } @Override public StackManipulation special(TypeDescription invocationTarget) { return new StackManipulation.Compound(invocation.special(invocationTarget), TypeCasting.to(targetType)); } @Override public StackManipulation dynamic(String methodName, TypeDescription returnType, List<? extends TypeDescription> methodType, List<?> arguments) { return invocation.dynamic(methodName, returnType, methodType, arguments); } @Override public StackManipulation onHandle(HandleType type) { return new Compound(invocation.onHandle(type), TypeCasting.to(targetType)); } @Override public boolean isValid() { return invocation.isValid(); } @Override public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) { return new Compound(invocation, TypeCasting.to(targetType)).apply(methodVisitor, implementationContext); } } /** * An implementation of a method invoking stack manipulation. */ protected class Invocation implements WithImplicitInvocationTargetType { /** * The method to be invoked. */ private final TypeDescription typeDescription; /** * The type on which this method is to be invoked. */ private final MethodDescription.InDefinedShape methodDescription; /** * Creates an invocation of a given method on its declaring type as an invocation target. * * @param methodDescription The method to be invoked. */ protected Invocation(MethodDescription.InDefinedShape methodDescription) { this(methodDescription, methodDescription.getDeclaringType()); } /** * Creates an invocation of a given method on a given invocation target type. * * @param methodDescription The method to be invoked. * @param typeDescription The type on which this method is to be invoked. */ protected Invocation(MethodDescription.InDefinedShape methodDescription, TypeDescription typeDescription) { this.typeDescription = typeDescription; this.methodDescription = methodDescription; } @Override public boolean isValid() { return true; } @Override public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) { methodVisitor.visitMethodInsn(invocationOpcode, typeDescription.getInternalName(), methodDescription.getInternalName(), methodDescription.getDescriptor(), typeDescription.isInterface()); int parameterSize = methodDescription.getStackSize(), returnValueSize = methodDescription.getReturnType().getStackSize().getSize(); return new Size(returnValueSize - parameterSize, Math.max(0, returnValueSize - parameterSize)); } @Override public StackManipulation virtual(TypeDescription invocationTarget) { if (methodDescription.isPrivate() || methodDescription.isConstructor() || methodDescription.isStatic()) { return Illegal.INSTANCE; } if (invocationTarget.isInterface()) { return INTERFACE.new Invocation(methodDescription, invocationTarget); } else { return VIRTUAL.new Invocation(methodDescription, invocationTarget); } } @Override public StackManipulation special(TypeDescription invocationTarget) { return methodDescription.isSpecializableFor(invocationTarget) ? SPECIAL.new Invocation(methodDescription, invocationTarget) : Illegal.INSTANCE; } @Override public StackManipulation dynamic(String methodName, TypeDescription returnType, List<? extends TypeDescription> methodType, List<?> arguments) { return methodDescription.isBootstrap() ? new DynamicInvocation(methodName, returnType, new TypeList.Explicit(methodType), methodDescription.asDefined(), arguments) : Illegal.INSTANCE; } @Override public StackManipulation onHandle(HandleType type) { return new HandleInvocation(methodDescription, type); } /** * Returns the outer instance. * * @return The outer instance. */ private MethodInvocation getOuterInstance() { return MethodInvocation.this; } @Override // HE: Remove when Lombok support for getOuter is added. public boolean equals(Object other) { if (this == other) return true; if (other == null || getClass() != other.getClass()) return false; Invocation that = (Invocation) other; return MethodInvocation.this.equals(((Invocation) other).getOuterInstance()) && methodDescription.asSignatureToken().equals(that.methodDescription.asSignatureToken()) && typeDescription.equals(that.typeDescription); } @Override // HE: Remove when Lombok support for getOuter is added. public int hashCode() { int result = typeDescription.hashCode(); result = 31 * result + MethodInvocation.this.hashCode(); result = 31 * result + methodDescription.asSignatureToken().hashCode(); return result; } } /** * Performs a dynamic method invocation of the given method. */ protected class DynamicInvocation implements StackManipulation { /** * The internal name of the method that is to be bootstrapped. */ private final String methodName; /** * The return type of the method to be bootstrapped. */ private final TypeDescription returnType; /** * The parameter types of the method to be bootstrapped. */ private final TypeList parameterTypes; /** * The bootstrap method. */ private final MethodDescription.InDefinedShape bootstrapMethod; /** * The list of arguments to be handed over to the bootstrap method. */ private final List<?> arguments; /** * Creates a new dynamic method invocation. * * @param methodName The internal name of the method that is to be bootstrapped. * @param returnType The return type of the method to be bootstrapped. * @param parameterTypes The type of the parameters to be bootstrapped. * @param bootstrapMethod The bootstrap method. * @param arguments The list of arguments to be handed over to the bootstrap method. */ public DynamicInvocation(String methodName, TypeDescription returnType, TypeList parameterTypes, MethodDescription.InDefinedShape bootstrapMethod, List<?> arguments) { this.methodName = methodName; this.returnType = returnType; this.parameterTypes = parameterTypes; this.bootstrapMethod = bootstrapMethod; this.arguments = arguments; } @Override public boolean isValid() { return true; } @Override public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) { StringBuilder stringBuilder = new StringBuilder("("); for (TypeDescription parameterType : parameterTypes) { stringBuilder.append(parameterType.getDescriptor()); } String methodDescriptor = stringBuilder.append(')').append(returnType.getDescriptor()).toString(); methodVisitor.visitInvokeDynamicInsn(methodName, methodDescriptor, new Handle(handle, bootstrapMethod.getDeclaringType().getInternalName(), bootstrapMethod.getInternalName(), bootstrapMethod.getDescriptor(), bootstrapMethod.getDeclaringType().isInterface()), arguments.toArray(new Object[arguments.size()])); int stackSize = returnType.getStackSize().getSize() - parameterTypes.getStackSize(); return new Size(stackSize, Math.max(stackSize, 0)); } @Override public boolean equals(Object other) { if (this == other) return true; if (other == null || getClass() != other.getClass()) return false; DynamicInvocation that = (DynamicInvocation) other; return MethodInvocation.this == that.getOuter() && arguments.equals(that.arguments) && bootstrapMethod.equals(that.bootstrapMethod) && returnType.equals(that.returnType) && parameterTypes.equals(that.parameterTypes) && methodName.equals(that.methodName); } /** * Returns the outer instance. * * @return The outer instance. */ private MethodInvocation getOuter() { return MethodInvocation.this; } @Override public int hashCode() { int result = methodName.hashCode(); result = 31 * result + MethodInvocation.this.hashCode(); result = 31 * result + returnType.hashCode(); result = 31 * result + parameterTypes.hashCode(); result = 31 * result + bootstrapMethod.hashCode(); result = 31 * result + arguments.hashCode(); return result; } } /** * Performs a method invocation on a method handle with a polymorphic type signature. */ @EqualsAndHashCode protected static class HandleInvocation implements StackManipulation { /** * The internal name of the method handle type. */ private static final String METHOD_HANDLE = "java/lang/invoke/MethodHandle"; /** * The invoked method. */ private final MethodDescription.InDefinedShape methodDescription; /** * The type of method handle invocation. */ private final HandleType type; /** * Creates a new method handle invocation. * * @param methodDescription The invoked method. * @param type The type of method handle invocation. */ protected HandleInvocation(MethodDescription.InDefinedShape methodDescription, HandleType type) { this.methodDescription = methodDescription; this.type = type; } @Override public boolean isValid() { return true; } @Override public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) { methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, METHOD_HANDLE, type.getMethodName(), methodDescription.isStatic() || methodDescription.isConstructor() ? methodDescription.getDescriptor() : "(" + methodDescription.getDeclaringType().getDescriptor() + methodDescription.getDescriptor().substring(1), false); int parameterSize = 1 + methodDescription.getStackSize(), returnValueSize = methodDescription.getReturnType().getStackSize().getSize(); return new Size(returnValueSize - parameterSize, Math.max(0, returnValueSize - parameterSize)); } } /** * The type of method handle invocation. */ public enum HandleType { /** * An exact invocation without type adjustments. */ EXACT("invokeExact"), /** * A regular invocation with standard type adjustments. */ REGULAR("invoke"); /** * The name of the invoked method. */ private final String methodName; /** * Creates a new handle type. * * @param methodName The name of the invoked method. */ HandleType(String methodName) { this.methodName = methodName; } /** * Returns the name of the represented method. * * @return The name of the invoked method. */ protected String getMethodName() { return methodName; } } }