package net.bytebuddy.implementation.auxiliary; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import lombok.EqualsAndHashCode; import net.bytebuddy.ByteBuddy; import net.bytebuddy.ClassFileVersion; import net.bytebuddy.description.field.FieldDescription; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.modifier.Ownership; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.TargetType; import net.bytebuddy.dynamic.scaffold.InstrumentedType; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.MethodAccessorFactory; import net.bytebuddy.implementation.bytecode.*; import net.bytebuddy.implementation.bytecode.constant.DefaultValue; import net.bytebuddy.implementation.bytecode.member.FieldAccess; import net.bytebuddy.implementation.bytecode.member.MethodInvocation; import net.bytebuddy.implementation.bytecode.member.MethodReturn; import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess; import net.bytebuddy.matcher.ElementMatchers; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import java.io.Serializable; import java.util.List; import static net.bytebuddy.matcher.ElementMatchers.*; /** * A type proxy creates accessor methods for all overridable methods of a given type by subclassing the given type and * delegating all method calls to accessor methods of the instrumented type it was created for. */ @EqualsAndHashCode public class TypeProxy implements AuxiliaryType { /** * The name of the {@code static} method that is added to this auxiliary type for creating instances by using the * Oracle JDK's {@link sun.reflect.ReflectionFactory}. */ public static final String REFLECTION_METHOD = "make"; /** * The name of the field that stores the delegation instance. */ public static final String INSTANCE_FIELD = "target"; /** * The type that is proxied, i.e. the original instrumented type this proxy is created for. */ private final TypeDescription proxiedType; /** * The implementation target of the proxied type. */ private final Implementation.Target implementationTarget; /** * The invocation factory for creating special method invocations. */ private final InvocationFactory invocationFactory; /** * {@code true} if the finalizer method should not be instrumented. */ private final boolean ignoreFinalizer; /** * Determines if the proxy should be serializable. */ private final boolean serializableProxy; /** * Creates a new type proxy. * * @param proxiedType The type this proxy should implement which can either be a non-final class or an interface. * @param implementationTarget The implementation target this type proxy is created for. * @param invocationFactory The invocation factory for creating special method invocations. * @param ignoreFinalizer {@code true} if any finalizer methods should be ignored for proxying. * @param serializableProxy Determines if the proxy should be serializable. */ public TypeProxy(TypeDescription proxiedType, Implementation.Target implementationTarget, InvocationFactory invocationFactory, boolean ignoreFinalizer, boolean serializableProxy) { this.proxiedType = proxiedType; this.implementationTarget = implementationTarget; this.invocationFactory = invocationFactory; this.ignoreFinalizer = ignoreFinalizer; this.serializableProxy = serializableProxy; } @Override public DynamicType make(String auxiliaryTypeName, ClassFileVersion classFileVersion, MethodAccessorFactory methodAccessorFactory) { return new ByteBuddy(classFileVersion) .ignore(ignoreFinalizer ? isFinalizer() : ElementMatchers.<MethodDescription>none()) .subclass(proxiedType) .name(auxiliaryTypeName) .modifiers(DEFAULT_TYPE_MODIFIER) .implement(serializableProxy ? new Class<?>[]{Serializable.class} : new Class<?>[0]) .method(any()).intercept(new MethodCall(methodAccessorFactory)) .defineMethod(REFLECTION_METHOD, TargetType.class, Ownership.STATIC).intercept(SilentConstruction.INSTANCE) .make(); } /** * A stack manipulation that throws an abstract method error in case that a given super method cannot be invoked. */ protected enum AbstractMethodErrorThrow implements StackManipulation { /** * The singleton instance. */ INSTANCE; /** * The stack manipulation that throws the abstract method error. */ private final StackManipulation implementation; /** * Creates the singleton instance. */ @SuppressFBWarnings(value = "SE_BAD_FIELD_STORE", justification = "Fields of enumerations are never serialized") AbstractMethodErrorThrow() { TypeDescription abstractMethodError = new TypeDescription.ForLoadedType(AbstractMethodError.class); MethodDescription constructor = abstractMethodError.getDeclaredMethods() .filter(isConstructor().and(takesArguments(0))).getOnly(); implementation = new Compound(TypeCreation.of(abstractMethodError), Duplication.SINGLE, MethodInvocation.invoke(constructor), Throw.INSTANCE); } @Override public boolean isValid() { return implementation.isValid(); } @Override public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) { return implementation.apply(methodVisitor, implementationContext); } } /** * An implementation of a <i>silent construction</i> of a given type by using the non-standardized * {@link sun.reflect.ReflectionFactory}. This way, a constructor invocation can be avoided. However, this comes * at the cost of potentially breaking compatibility as the reflection factory is not standardized. */ protected enum SilentConstruction implements Implementation { /** * The singleton instance. */ INSTANCE; @Override public InstrumentedType prepare(InstrumentedType instrumentedType) { return instrumentedType; } @Override public ByteCodeAppender appender(Target implementationTarget) { return new Appender(implementationTarget.getInstrumentedType()); } /** * The appender for implementing a {@link net.bytebuddy.implementation.auxiliary.TypeProxy.SilentConstruction}. */ @EqualsAndHashCode protected static class Appender implements ByteCodeAppender { /** * The internal name of the reflection factory class. */ public static final String REFLECTION_FACTORY_INTERNAL_NAME = "sun/reflect/ReflectionFactory"; /** * The name of the factory method for getting hold of an instance of the reflection factory class. */ public static final String GET_REFLECTION_FACTORY_METHOD_NAME = "getReflectionFactory"; /** * The descriptor of the factory method for getting hold of an instance of the reflection factory class. */ public static final String GET_REFLECTION_FACTORY_METHOD_DESCRIPTOR = "()Lsun/reflect/ReflectionFactory;"; /** * The name of the method for creating a new serialization constructor. */ public static final String NEW_CONSTRUCTOR_FOR_SERIALIZATION_METHOD_NAME = "newConstructorForSerialization"; /** * The descriptor of the method for creating a new serialization constructor. */ public static final String NEW_CONSTRUCTOR_FOR_SERIALIZATION_METHOD_DESCRIPTOR = "(Ljava/lang/Class;Ljava/lang/reflect/Constructor;)Ljava/lang/reflect/Constructor;"; /** * The descriptor of the {@link java.lang.Object} class. */ public static final String JAVA_LANG_OBJECT_DESCRIPTOR = "Ljava/lang/Object;"; /** * The internal name of the {@link java.lang.Object} class. */ public static final String JAVA_LANG_OBJECT_INTERNAL_NAME = "java/lang/Object"; /** * The internal name of the {@link java.lang.reflect.Constructor} class. */ public static final String JAVA_LANG_CONSTRUCTOR_INTERNAL_NAME = "java/lang/reflect/Constructor"; /** * The internal name of the {@link java.lang.reflect.Constructor#newInstance(Object...)} method. */ public static final String NEW_INSTANCE_METHOD_NAME = "newInstance"; /** * The descriptor of the {@link java.lang.reflect.Constructor#newInstance(Object...)} method. */ public static final String NEW_INSTANCE_METHOD_DESCRIPTOR = "([Ljava/lang/Object;)Ljava/lang/Object;"; /** * The internal name of the {@link java.lang.Class} class. */ public static final String JAVA_LANG_CLASS_INTERNAL_NAME = "java/lang/Class"; /** * The internal name of the {@link Class#getDeclaredClasses()} method. */ public static final String GET_DECLARED_CONSTRUCTOR_METHOD_NAME = "getDeclaredConstructor"; /** * The descriptor of the {@link Class#getDeclaredClasses()} method. */ public static final String GET_DECLARED_CONSTRUCTOR_METHOD_DESCRIPTOR = "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;"; /** * The instrumented type that this factory method is created for. */ private final TypeDescription instrumentedType; /** * Creates a new appender. * * @param instrumentedType The instrumented type that the factory method is created for. */ private Appender(TypeDescription instrumentedType) { this.instrumentedType = instrumentedType; } @Override public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) { methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, REFLECTION_FACTORY_INTERNAL_NAME, GET_REFLECTION_FACTORY_METHOD_NAME, GET_REFLECTION_FACTORY_METHOD_DESCRIPTOR, false); methodVisitor.visitLdcInsn(Type.getType(instrumentedType.getDescriptor())); methodVisitor.visitLdcInsn(Type.getType(JAVA_LANG_OBJECT_DESCRIPTOR)); methodVisitor.visitInsn(Opcodes.ICONST_0); methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, JAVA_LANG_CLASS_INTERNAL_NAME); methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, JAVA_LANG_CLASS_INTERNAL_NAME, GET_DECLARED_CONSTRUCTOR_METHOD_NAME, GET_DECLARED_CONSTRUCTOR_METHOD_DESCRIPTOR, false); methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, REFLECTION_FACTORY_INTERNAL_NAME, NEW_CONSTRUCTOR_FOR_SERIALIZATION_METHOD_NAME, NEW_CONSTRUCTOR_FOR_SERIALIZATION_METHOD_DESCRIPTOR, false); methodVisitor.visitInsn(Opcodes.ICONST_0); methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, JAVA_LANG_OBJECT_INTERNAL_NAME); methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, JAVA_LANG_CONSTRUCTOR_INTERNAL_NAME, NEW_INSTANCE_METHOD_NAME, NEW_INSTANCE_METHOD_DESCRIPTOR, false); methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, instrumentedType.getInternalName()); methodVisitor.visitInsn(Opcodes.ARETURN); return new Size(4, 0); } } } /** * An invocation factory is responsible for creating a special method invocation for any method that is to be * invoked. These special method invocations are then implemented by the * {@link net.bytebuddy.implementation.auxiliary.TypeProxy}. * Illegal {@link Implementation.SpecialMethodInvocation} are implemented by * throwing an {@link java.lang.AbstractMethodError}. */ public interface InvocationFactory { /** * Creates a special method invocation to implement for a given method. * * @param implementationTarget The implementation target the type proxy is created for. * @param proxiedType The type for the type proxy to subclass or implement. * @param instrumentedMethod The instrumented method that is to be invoked. * @return A special method invocation of the given method or an illegal invocation if the proxy should * throw an {@link java.lang.AbstractMethodError} when the instrumented method is invoked. */ Implementation.SpecialMethodInvocation invoke(Implementation.Target implementationTarget, TypeDescription proxiedType, MethodDescription instrumentedMethod); /** * Default implementations of the * {@link net.bytebuddy.implementation.auxiliary.TypeProxy.InvocationFactory}. */ enum Default implements InvocationFactory { /** * Invokes the super method of the instrumented method. */ SUPER_METHOD { @Override public Implementation.SpecialMethodInvocation invoke(Implementation.Target implementationTarget, TypeDescription proxiedType, MethodDescription instrumentedMethod) { return implementationTarget.invokeDominant(instrumentedMethod.asSignatureToken()); } }, /** * Invokes the default method of the instrumented method if it exists and is not ambiguous. */ DEFAULT_METHOD { @Override public Implementation.SpecialMethodInvocation invoke(Implementation.Target implementationTarget, TypeDescription proxiedType, MethodDescription instrumentedMethod) { return implementationTarget.invokeDefault(instrumentedMethod.asSignatureToken(), proxiedType); } }; } } /** * Loads a type proxy onto the operand stack which is created by calling one of its constructors. When this * stack manipulation is applied, an instance of the instrumented type must lie on top of the operand stack. * All constructor parameters will be assigned their default values when this stack operation is applied. */ @EqualsAndHashCode public static class ForSuperMethodByConstructor implements StackManipulation { /** * The type for the type proxy to subclass or implement. */ private final TypeDescription proxiedType; /** * The implementation target this type proxy is created for. */ private final Implementation.Target implementationTarget; /** * The parameter types of the constructor that should be called. */ private final List<TypeDescription> constructorParameters; /** * {@code true} if any finalizers should be ignored for the delegation. */ private final boolean ignoreFinalizer; /** * Determines if the proxy should be serializable. */ private final boolean serializableProxy; /** * Creates a new stack operation for creating a type proxy by calling one of its constructors. * * @param proxiedType The type for the type proxy to subclass or implement. * @param implementationTarget The implementation target this type proxy is created for. * @param constructorParameters The parameter types of the constructor that should be called. * @param ignoreFinalizer {@code true} if any finalizers should be ignored for the delegation. * @param serializableProxy Determines if the proxy should be serializable. */ public ForSuperMethodByConstructor(TypeDescription proxiedType, Implementation.Target implementationTarget, List<TypeDescription> constructorParameters, boolean ignoreFinalizer, boolean serializableProxy) { this.proxiedType = proxiedType; this.implementationTarget = implementationTarget; this.constructorParameters = constructorParameters; this.ignoreFinalizer = ignoreFinalizer; this.serializableProxy = serializableProxy; } @Override public boolean isValid() { return true; } @Override public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) { TypeDescription proxyType = implementationContext .register(new TypeProxy(proxiedType, implementationTarget, InvocationFactory.Default.SUPER_METHOD, ignoreFinalizer, serializableProxy)); StackManipulation[] constructorValue = new StackManipulation[constructorParameters.size()]; int index = 0; for (TypeDescription parameterType : constructorParameters) { constructorValue[index++] = DefaultValue.of(parameterType); } return new Compound( TypeCreation.of(proxyType), Duplication.SINGLE, new Compound(constructorValue), MethodInvocation.invoke(proxyType.getDeclaredMethods().filter(isConstructor().and(takesArguments(constructorParameters))).getOnly()), Duplication.SINGLE, MethodVariableAccess.loadThis(), FieldAccess.forField(proxyType.getDeclaredFields().filter((named(INSTANCE_FIELD))).getOnly()).write() ).apply(methodVisitor, implementationContext); } } /** * Loads a type proxy onto the operand stack which is created by constructing a serialization constructor using * the Oracle JDK's {@link sun.reflect.ReflectionFactory#newConstructorForSerialization(Class, java.lang.reflect.Constructor)} * method which might not be available in any Java runtime. When this stack manipulation is applied, an instance of * the instrumented type must lie on top of the operand stack. */ @EqualsAndHashCode public static class ForSuperMethodByReflectionFactory implements StackManipulation { /** * The type for which a proxy type is created. */ private final TypeDescription proxiedType; /** * The implementation target of the proxied type. */ private final Implementation.Target implementationTarget; /** * {@code true} {@code true} if any finalizer methods should be ignored for proxying. */ private final boolean ignoreFinalizer; /** * Determines if the proxy should be serializable. */ private final boolean serializableProxy; /** * Creates a new stack operation for reflectively creating a type proxy for the given arguments. * * @param proxiedType The type for the type proxy to subclass or implement. * @param implementationTarget The implementation target this type proxy is created for. * @param ignoreFinalizer {@code true} if any finalizer methods should be ignored for proxying. * @param serializableProxy Determines if the proxy should be serializable. */ public ForSuperMethodByReflectionFactory(TypeDescription proxiedType, Implementation.Target implementationTarget, boolean ignoreFinalizer, boolean serializableProxy) { this.proxiedType = proxiedType; this.implementationTarget = implementationTarget; this.ignoreFinalizer = ignoreFinalizer; this.serializableProxy = serializableProxy; } @Override public boolean isValid() { return true; } @Override public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) { TypeDescription proxyType = implementationContext.register(new TypeProxy(proxiedType, implementationTarget, InvocationFactory.Default.SUPER_METHOD, ignoreFinalizer, serializableProxy)); return new Compound( MethodInvocation.invoke(proxyType.getDeclaredMethods().filter(named(REFLECTION_METHOD).and(takesArguments(0))).getOnly()), Duplication.SINGLE, MethodVariableAccess.loadThis(), FieldAccess.forField(proxyType.getDeclaredFields().filter((named(INSTANCE_FIELD))).getOnly()).write() ).apply(methodVisitor, implementationContext); } } /** * Creates a type proxy which delegates its super method calls to any invokable default method of * a given interface and loads an instance of this proxy onto the operand stack. */ @EqualsAndHashCode public static class ForDefaultMethod implements StackManipulation { /** * The proxied interface type. */ private final TypeDescription proxiedType; /** * The implementation target for the original instrumentation. */ private final Implementation.Target implementationTarget; /** * {@code true} if the proxy should be {@link java.io.Serializable}. */ private final boolean serializableProxy; /** * Creates a new proxy creation for a default interface type proxy. * * @param proxiedType The proxied interface type. * @param implementationTarget The implementation target for the original implementation. * @param serializableProxy {@code true} if the proxy should be {@link java.io.Serializable}. */ public ForDefaultMethod(TypeDescription proxiedType, Implementation.Target implementationTarget, boolean serializableProxy) { this.proxiedType = proxiedType; this.implementationTarget = implementationTarget; this.serializableProxy = serializableProxy; } @Override public boolean isValid() { return true; } @Override public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) { TypeDescription proxyType = implementationContext .register(new TypeProxy(proxiedType, implementationTarget, InvocationFactory.Default.DEFAULT_METHOD, true, serializableProxy)); return new Compound( TypeCreation.of(proxyType), Duplication.SINGLE, MethodInvocation.invoke(proxyType.getDeclaredMethods().filter(isConstructor()).getOnly()), Duplication.SINGLE, MethodVariableAccess.loadThis(), FieldAccess.forField(proxyType.getDeclaredFields().filter((named(INSTANCE_FIELD))).getOnly()).write() ).apply(methodVisitor, implementationContext); } } /** * An implementation for a method call of a {@link net.bytebuddy.implementation.auxiliary.TypeProxy}. */ protected class MethodCall implements Implementation { /** * The method accessor factory to query for the super method invocation. */ private final MethodAccessorFactory methodAccessorFactory; /** * Creates a new method call implementation. * * @param methodAccessorFactory The method accessor factory to query for the super method invocation. */ protected MethodCall(MethodAccessorFactory methodAccessorFactory) { this.methodAccessorFactory = methodAccessorFactory; } @Override public InstrumentedType prepare(InstrumentedType instrumentedType) { return instrumentedType.withField(new FieldDescription.Token(INSTANCE_FIELD, Opcodes.ACC_SYNTHETIC, implementationTarget.getInstrumentedType().asGenericType())); } @Override public ByteCodeAppender appender(Target implementationTarget) { return new Appender(implementationTarget.getInstrumentedType()); } /** * Returns the outer instance. * * @return The outer instance. */ private TypeProxy getTypeProxy() { return TypeProxy.this; } @Override // HE: Remove when Lombok support for getOuter is added. public boolean equals(Object other) { return this == other || !(other == null || getClass() != other.getClass()) && methodAccessorFactory.equals(((MethodCall) other).methodAccessorFactory) && TypeProxy.this.equals(((MethodCall) other).getTypeProxy()); } @Override // HE: Remove when Lombok support for getOuter is added. public int hashCode() { return 31 * TypeProxy.this.hashCode() + methodAccessorFactory.hashCode(); } /** * Implementation of a byte code appender for a {@link net.bytebuddy.implementation.auxiliary.TypeProxy.MethodCall}. */ protected class Appender implements ByteCodeAppender { /** * The stack manipulation for loading the proxied instance onto the stack. */ private final StackManipulation fieldLoadingInstruction; /** * Creates a new appender. * * @param instrumentedType The instrumented type that is proxied by the enclosing instrumentation. */ protected Appender(TypeDescription instrumentedType) { fieldLoadingInstruction = FieldAccess.forField(instrumentedType.getDeclaredFields().filter((named(INSTANCE_FIELD))).getOnly()).read(); } @Override public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) { SpecialMethodInvocation specialMethodInvocation = invocationFactory.invoke(implementationTarget, proxiedType, instrumentedMethod); StackManipulation.Size size = (specialMethodInvocation.isValid() ? new AccessorMethodInvocation(instrumentedMethod, specialMethodInvocation) : AbstractMethodErrorThrow.INSTANCE).apply(methodVisitor, implementationContext); return new Size(size.getMaximalSize(), instrumentedMethod.getStackSize()); } /** * Returns the outer instance. * * @return The outer instance. */ private MethodCall getMethodCall() { return MethodCall.this; } @Override // HE: Remove when Lombok support for getOuter is added. public boolean equals(Object other) { return this == other || !(other == null || getClass() != other.getClass()) && fieldLoadingInstruction.equals(((Appender) other).fieldLoadingInstruction) && MethodCall.this.equals(((Appender) other).getMethodCall()); } @Override // HE: Remove when Lombok support for getOuter is added. public int hashCode() { return 31 * MethodCall.this.hashCode() + fieldLoadingInstruction.hashCode(); } /** * Stack manipulation for invoking an accessor method. */ protected class AccessorMethodInvocation implements StackManipulation { /** * The instrumented method that is implemented. */ private final MethodDescription instrumentedMethod; /** * The special method invocation that is invoked by this accessor method invocation. */ private final SpecialMethodInvocation specialMethodInvocation; /** * Creates a new accessor method invocation. * * @param instrumentedMethod The instrumented method that is implemented. * @param specialMethodInvocation The special method invocation that is invoked by this accessor * method invocation. */ protected AccessorMethodInvocation(MethodDescription instrumentedMethod, SpecialMethodInvocation specialMethodInvocation) { this.instrumentedMethod = instrumentedMethod; this.specialMethodInvocation = specialMethodInvocation; } @Override public boolean isValid() { return specialMethodInvocation.isValid(); } @Override public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) { MethodDescription.InDefinedShape proxyMethod = methodAccessorFactory.registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT); return new StackManipulation.Compound( MethodVariableAccess.loadThis(), fieldLoadingInstruction, MethodVariableAccess.allArgumentsOf(instrumentedMethod).asBridgeOf(proxyMethod), MethodInvocation.invoke(proxyMethod), MethodReturn.of(instrumentedMethod.getReturnType()) ).apply(methodVisitor, implementationContext); } /** * Returns the outer instance. * * @return The outer instance. */ private Appender getAppender() { return Appender.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; AccessorMethodInvocation that = (AccessorMethodInvocation) other; return Appender.this.equals(that.getAppender()) && instrumentedMethod.equals(that.instrumentedMethod) && specialMethodInvocation.equals(that.specialMethodInvocation); } @Override // HE: Remove when Lombok support for getOuter is added. public int hashCode() { int result = Appender.this.hashCode(); result = 31 * result + instrumentedMethod.hashCode(); result = 31 * result + specialMethodInvocation.hashCode(); return result; } } } } }