All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.testifyproject.bytebuddy.implementation.Implementation Maven / Gradle / Ivy

There is a newer version: 1.0.6
Show newest version
package org.testifyproject.bytebuddy.implementation;

import lombok.EqualsAndHashCode;
import org.testifyproject.bytebuddy.ClassFileVersion;
import org.testifyproject.bytebuddy.description.annotation.AnnotationList;
import org.testifyproject.bytebuddy.description.annotation.AnnotationValue;
import org.testifyproject.bytebuddy.description.field.FieldDescription;
import org.testifyproject.bytebuddy.description.method.MethodDescription;
import org.testifyproject.bytebuddy.description.method.ParameterDescription;
import org.testifyproject.bytebuddy.description.method.ParameterList;
import org.testifyproject.bytebuddy.description.modifier.Visibility;
import org.testifyproject.bytebuddy.description.type.TypeDefinition;
import org.testifyproject.bytebuddy.description.type.TypeDescription;
import org.testifyproject.bytebuddy.description.type.TypeList;
import org.testifyproject.bytebuddy.dynamic.DynamicType;
import org.testifyproject.bytebuddy.dynamic.scaffold.InstrumentedType;
import org.testifyproject.bytebuddy.dynamic.scaffold.MethodGraph;
import org.testifyproject.bytebuddy.dynamic.scaffold.TypeInitializer;
import org.testifyproject.bytebuddy.dynamic.scaffold.TypeWriter;
import org.testifyproject.bytebuddy.implementation.attribute.AnnotationValueFilter;
import org.testifyproject.bytebuddy.implementation.auxiliary.AuxiliaryType;
import org.testifyproject.bytebuddy.implementation.bytecode.ByteCodeAppender;
import org.testifyproject.bytebuddy.implementation.bytecode.StackManipulation;
import org.testifyproject.bytebuddy.implementation.bytecode.member.FieldAccess;
import org.testifyproject.bytebuddy.implementation.bytecode.member.MethodInvocation;
import org.testifyproject.bytebuddy.implementation.bytecode.member.MethodReturn;
import org.testifyproject.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import org.testifyproject.bytebuddy.utility.RandomString;
import org.testifyproject.bytebuddy.jar.asm.ClassVisitor;
import org.testifyproject.bytebuddy.jar.asm.FieldVisitor;
import org.testifyproject.bytebuddy.jar.asm.MethodVisitor;
import org.testifyproject.bytebuddy.jar.asm.Opcodes;

import java.util.*;

/**
 * An implementation is responsible for implementing methods of a dynamically created type as byte code. An
 * implementation is applied in two stages:
 * 
    *
  1. The implementation is able to prepare an instrumented type by adding fields and/or helper methods that are * required for the methods implemented by this implementation. Furthermore, * {@link LoadedTypeInitializer}s and byte code for the type initializer can be registered for the instrumented * type.
  2. *
  3. Any implementation is required to supply a byte code appender that is responsible for providing the byte code * to the instrumented methods that were delegated to this implementation. This byte code appender is also * be responsible for providing implementations for the methods added in step 1.
  4. *
*

 

* An implementation should provide meaningful implementations of both {@link java.lang.Object#equals(Object)} * and {@link Object#hashCode()} if it wants to avoid to be used twice within the creation of a dynamic type. For two * equal implementations only one will be applied on the creation of a dynamic type. */ public interface Implementation extends InstrumentedType.Prepareable { /** * Creates a byte code appender that determines the implementation of the instrumented type's methods. * * @param implementationTarget The target of the current implementation. * @return A byte code appender for implementing methods delegated to this implementation. This byte code appender * is also responsible for handling methods that were added by this implementation on the call to * {@link Implementation#prepare(InstrumentedType)}. */ ByteCodeAppender appender(Target implementationTarget); /** * Represents an implementation that can be chained together with another implementation. */ interface Composable extends Implementation { /** * Appends the supplied implementation to this implementation. * * @param implementation The subsequent implementation. * @return An implementation that combines this implementation with the provided one. */ Implementation andThen(Implementation implementation); } /** * Represents an type-specific method invocation on the current instrumented type which is not legal from outside * the type such as a super method or default method invocation. Legal instances of special method invocations must * be equal to one another if they represent the same invocation target. */ interface SpecialMethodInvocation extends StackManipulation { /** * Returns the method that represents this special method invocation. This method can be different even for * equal special method invocations, dependant on the method that was used to request such an invocation by the * means of a {@link Implementation.Target}. * * @return The method description that describes this instances invocation target. */ MethodDescription getMethodDescription(); /** * Returns the target type the represented method is invoked on. * * @return The type the represented method is invoked on. */ TypeDescription getTypeDescription(); /** * A canonical implementation of an illegal {@link Implementation.SpecialMethodInvocation}. */ enum Illegal implements SpecialMethodInvocation { /** * The singleton instance. */ INSTANCE; @Override public boolean isValid() { return false; } @Override public Size apply(MethodVisitor methodVisitor, Context implementationContext) { throw new IllegalStateException("Cannot implement an undefined method"); } @Override public MethodDescription getMethodDescription() { throw new IllegalStateException("An illegal special method invocation must not be applied"); } @Override public TypeDescription getTypeDescription() { throw new IllegalStateException("An illegal special method invocation must not be applied"); } } /** * An abstract base implementation of a valid special method invocation. */ abstract class AbstractBase implements SpecialMethodInvocation { @Override public boolean isValid() { return true; } @Override public int hashCode() { return 31 * getMethodDescription().asSignatureToken().hashCode() + getTypeDescription().hashCode(); } @Override public boolean equals(Object other) { if (this == other) return true; if (!(other instanceof SpecialMethodInvocation)) return false; SpecialMethodInvocation specialMethodInvocation = (SpecialMethodInvocation) other; return getMethodDescription().asSignatureToken().equals(specialMethodInvocation.getMethodDescription().asSignatureToken()) && getTypeDescription().equals(((SpecialMethodInvocation) other).getTypeDescription()); } } /** * A canonical implementation of a {@link SpecialMethodInvocation}. */ class Simple extends SpecialMethodInvocation.AbstractBase { /** * The method description that is represented by this legal special method invocation. */ private final MethodDescription methodDescription; /** * The type description that is represented by this legal special method invocation. */ private final TypeDescription typeDescription; /** * A stack manipulation representing the method's invocation on the type description. */ private final StackManipulation stackManipulation; /** * Creates a new legal special method invocation. * * @param methodDescription The method that represents the special method invocation. * @param typeDescription The type on which the method should be invoked on by an {@code INVOKESPECIAL} * invocation. * @param stackManipulation The stack manipulation that represents this special method invocation. */ protected Simple(MethodDescription methodDescription, TypeDescription typeDescription, StackManipulation stackManipulation) { this.methodDescription = methodDescription; this.typeDescription = typeDescription; this.stackManipulation = stackManipulation; } /** * Creates a special method invocation for a given invocation target. * * @param methodDescription The method that represents the special method invocation. * @param typeDescription The type on which the method should be invoked on by an {@code INVOKESPECIAL} * invocation. * @return A special method invocation representing a legal invocation if the method can be invoked * specially on the target type or an illegal invocation if this is not possible. */ public static SpecialMethodInvocation of(MethodDescription methodDescription, TypeDescription typeDescription) { StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription).special(typeDescription); return stackManipulation.isValid() ? new Simple(methodDescription, typeDescription, stackManipulation) : SpecialMethodInvocation.Illegal.INSTANCE; } @Override public MethodDescription getMethodDescription() { return methodDescription; } @Override public TypeDescription getTypeDescription() { return typeDescription; } @Override public Size apply(MethodVisitor methodVisitor, Context implementationContext) { return stackManipulation.apply(methodVisitor, implementationContext); } } } /** * The target of an implementation. Implementation targets must be immutable and can be queried without altering * the implementation result. An implementation target provides information on the type that is to be created * where it is the implementation's responsibility to cache expensive computations, especially such computations * that require reflective look-up. */ interface Target { /** * Returns a description of the instrumented type. * * @return A description of the instrumented type. */ TypeDescription getInstrumentedType(); /** * Identifies the origin type of an implementation. The origin type describes the type that is subject to * any form of enhancement. If a subclass of a given type is generated, the base type of this subclass * describes the origin type. If a given type is redefined or rebased, the origin type is described by the * instrumented type itself. * * @return The origin type of this implementation. */ TypeDefinition getOriginType(); /** * Creates a special method invocation for invoking the super method of the given method. * * @param token A token of the method that is to be invoked as a super method. * @return The corresponding special method invocation which might be illegal if the requested invocation is not legal. */ SpecialMethodInvocation invokeSuper(MethodDescription.SignatureToken token); /** * Creates a special method invocation for invoking a default method with the given token. The default method call must * not be ambiguous or an illegal special method invocation is returned. * * @param token A token of the method that is to be invoked as a default method. * @return The corresponding default method invocation which might be illegal if the requested invocation is not legal or ambiguous. */ SpecialMethodInvocation invokeDefault(MethodDescription.SignatureToken token); /** * Creates a special method invocation for invoking a default method. * * @param targetType The interface on which the default method is to be invoked. * @param token A token that uniquely describes the method to invoke. * @return The corresponding special method invocation which might be illegal if the requested invocation is * not legal. */ SpecialMethodInvocation invokeDefault(MethodDescription.SignatureToken token, TypeDescription targetType); /** * Invokes a dominant method, i.e. if the method token can be invoked as a super method invocation, this invocation is considered dominant. * Alternatively, a method invocation is attempted on an interface type as a default method invocation only if this invocation is not ambiguous * for several interfaces. * * @param token The method token representing the method to be invoked. * @return A special method invocation for a method representing the method token. */ SpecialMethodInvocation invokeDominant(MethodDescription.SignatureToken token); /** * A factory for creating an {@link Implementation.Target}. */ interface Factory { /** * Creates an implementation target. * * @param instrumentedType The instrumented type. * @param methodGraph A method graph of the instrumented type. * @param classFileVersion The type's class file version. * @return An implementation target for the instrumented type. */ Target make(TypeDescription instrumentedType, MethodGraph.Linked methodGraph, ClassFileVersion classFileVersion); } /** * An abstract base implementation for an {@link Implementation.Target}. */ @EqualsAndHashCode abstract class AbstractBase implements Target { /** * The instrumented type. */ protected final TypeDescription instrumentedType; /** * The instrumented type's method graph. */ protected final MethodGraph.Linked methodGraph; /** * The default method invocation mode to apply. */ protected final DefaultMethodInvocation defaultMethodInvocation; /** * Creates a new implementation target. * * @param instrumentedType The instrumented type. * @param methodGraph The instrumented type's method graph. * @param defaultMethodInvocation The default method invocation mode to apply. */ protected AbstractBase(TypeDescription instrumentedType, MethodGraph.Linked methodGraph, DefaultMethodInvocation defaultMethodInvocation) { this.instrumentedType = instrumentedType; this.methodGraph = methodGraph; this.defaultMethodInvocation = defaultMethodInvocation; } @Override public TypeDescription getInstrumentedType() { return instrumentedType; } @Override public SpecialMethodInvocation invokeDefault(MethodDescription.SignatureToken token) { SpecialMethodInvocation specialMethodInvocation = SpecialMethodInvocation.Illegal.INSTANCE; for (TypeDescription interfaceType : instrumentedType.getInterfaces().asErasures()) { SpecialMethodInvocation invocation = invokeDefault(token, interfaceType); if (invocation.isValid()) { if (specialMethodInvocation.isValid()) { return SpecialMethodInvocation.Illegal.INSTANCE; } else { specialMethodInvocation = invocation; } } } return specialMethodInvocation; } @Override public SpecialMethodInvocation invokeDefault(MethodDescription.SignatureToken token, TypeDescription targetType) { return defaultMethodInvocation.apply(methodGraph.getInterfaceGraph(targetType).locate(token), targetType); } @Override public SpecialMethodInvocation invokeDominant(MethodDescription.SignatureToken token) { SpecialMethodInvocation specialMethodInvocation = invokeSuper(token); return specialMethodInvocation.isValid() ? specialMethodInvocation : invokeDefault(token); } /** * Determines if default method invocations are possible. */ protected enum DefaultMethodInvocation { /** * Permits default method invocations, if an interface declaring a default method is possible. */ ENABLED { @Override protected SpecialMethodInvocation apply(MethodGraph.Node node, TypeDescription targetType) { return node.getSort().isUnique() ? SpecialMethodInvocation.Simple.of(node.getRepresentative(), targetType) : SpecialMethodInvocation.Illegal.INSTANCE; } }, /** * Does not permit default method invocations. */ DISABLED { @Override protected SpecialMethodInvocation apply(MethodGraph.Node node, TypeDescription targetType) { return SpecialMethodInvocation.Illegal.INSTANCE; } }; /** * Resolves a default method invocation depending on the class file version permitting such calls. * * @param classFileVersion The class file version to resolve for. * @return A suitable default method invocation mode. */ public static DefaultMethodInvocation of(ClassFileVersion classFileVersion) { return classFileVersion.isAtLeast(ClassFileVersion.JAVA_V8) ? ENABLED : DISABLED; } /** * Resolves a default method invocation for a given node. * * @param node The node representing the default method call. * @param targetType The target type defining the default method. * @return A suitable special method invocation. */ protected abstract SpecialMethodInvocation apply(MethodGraph.Node node, TypeDescription targetType); } } } /** * The context for an implementation application. An implementation context represents a mutable data structure * where any registration is irrevocable. Calling methods on an implementation context should be considered equally * sensitive as calling a {@link org.testifyproject.bytebuddy.jar.asm.MethodVisitor}. As such, an implementation context and a * {@link org.testifyproject.bytebuddy.jar.asm.MethodVisitor} are complementary for creating an new Java type. */ interface Context extends MethodAccessorFactory { /** * Registers an auxiliary type as required for the current implementation. Registering a type will cause the * creation of this type even if this type is not effectively used for the current implementation. * * @param auxiliaryType The auxiliary type that is required for the current implementation. * @return A description of the registered auxiliary type. */ TypeDescription register(AuxiliaryType auxiliaryType); /** * Caches a single value by storing it in form of a {@code private}, {@code final} and {@code static} field. * By caching values, expensive instance creations can be avoided and object identity can be preserved. * The field is initiated in a generated class's static initializer. * * @param fieldValue A stack manipulation for creating the value that is to be cached in a {@code static} field. * After executing the stack manipulation, exactly one value must be put onto the operand * stack which is assignable to the given {@code fieldType}. * @param fieldType The type of the field for storing the cached value. This field's type determines the value * that is put onto the operand stack by this method's returned stack manipulation. * @return A description of a field that was defined on the instrumented type which contains the given value. */ FieldDescription.InDefinedShape cache(StackManipulation fieldValue, TypeDescription fieldType); /** * Returns the instrumented type of the current implementation. The instrumented type is exposed with the intend of allowing optimal * byte code generation and not for implementing checks or changing the behavior of a {@link StackManipulation}. * * @return The instrumented type of the current implementation. */ TypeDescription getInstrumentedType(); /** * Returns the class file version of the currently created dynamic type. * * @return The class file version of the currently created dynamic type. */ ClassFileVersion getClassFileVersion(); /** * Represents an extractable view of an {@link Implementation.Context} which * allows the retrieval of any registered auxiliary type. */ interface ExtractableView extends Context { /** * Returns {@code true} if this implementation context permits the registration of any implicit type initializers. * * @return {@code true} if this implementation context permits the registration of any implicit type initializers. */ boolean isEnabled(); /** * Returns any {@link org.testifyproject.bytebuddy.implementation.auxiliary.AuxiliaryType} that was registered * with this {@link Implementation.Context}. * * @return A list of all manifested registered auxiliary types. */ List getAuxiliaryTypes(); /** * Writes any information that was registered with an {@link Implementation.Context} * to the provided class visitor. This contains any fields for value caching, any accessor method and it * writes the type initializer. The type initializer must therefore never be written manually. * * @param drain The drain to write the type initializer to. * @param classVisitor The class visitor to which the extractable view is to be written. * @param annotationValueFilterFactory The annotation value filter factory to apply when writing annotation. */ void drain(TypeInitializer.Drain drain, ClassVisitor classVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory); /** * An abstract base implementation of an extractable view of an implementation context. */ @EqualsAndHashCode abstract class AbstractBase implements ExtractableView { /** * The instrumented type. */ protected final TypeDescription instrumentedType; /** * The class file version of the dynamic type. */ protected final ClassFileVersion classFileVersion; /** * Create a new extractable view. * * @param instrumentedType The instrumented type. * @param classFileVersion The class file version of the dynamic type. */ protected AbstractBase(TypeDescription instrumentedType, ClassFileVersion classFileVersion) { this.instrumentedType = instrumentedType; this.classFileVersion = classFileVersion; } @Override public TypeDescription getInstrumentedType() { return instrumentedType; } @Override public ClassFileVersion getClassFileVersion() { return classFileVersion; } } } /** * A factory for creating a new implementation context. */ interface Factory { /** * Creates a new implementation context. * * @param instrumentedType The description of the type that is currently subject of creation. * @param auxiliaryTypeNamingStrategy The naming strategy for naming an auxiliary type. * @param typeInitializer The type initializer of the created instrumented type. * @param classFileVersion The class file version of the created class. * @param auxiliaryClassFileVersion The class file version of any auxiliary classes. * @return An implementation context in its extractable view. */ ExtractableView make(TypeDescription instrumentedType, AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy, TypeInitializer typeInitializer, ClassFileVersion classFileVersion, ClassFileVersion auxiliaryClassFileVersion); } /** * An implementation context that does not allow for any injections into the static initializer block. This can be useful when * redefining a class when it is not allowed to add methods to a class what is an implicit requirement when copying the static * initializer block into another method. */ class Disabled extends ExtractableView.AbstractBase { /** * Creates a new disabled implementation context. * * @param instrumentedType The instrumented type. * @param classFileVersion The class file version to create the class in. */ protected Disabled(TypeDescription instrumentedType, ClassFileVersion classFileVersion) { super(instrumentedType, classFileVersion); } @Override public boolean isEnabled() { return false; } @Override public List getAuxiliaryTypes() { return Collections.emptyList(); } @Override public void drain(TypeInitializer.Drain drain, ClassVisitor classVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) { drain.apply(classVisitor, TypeInitializer.None.INSTANCE, this); } @Override public TypeDescription register(AuxiliaryType auxiliaryType) { throw new IllegalStateException("Registration of auxiliary types was disabled: " + auxiliaryType); } @Override public MethodDescription.InDefinedShape registerAccessorFor(SpecialMethodInvocation specialMethodInvocation, AccessType accessType) { throw new IllegalStateException("Registration of method accessors was disabled: " + specialMethodInvocation.getMethodDescription()); } @Override public MethodDescription.InDefinedShape registerGetterFor(FieldDescription fieldDescription, AccessType accessType) { throw new IllegalStateException("Registration of field accessor was disabled: " + fieldDescription); } @Override public MethodDescription.InDefinedShape registerSetterFor(FieldDescription fieldDescription, AccessType accessType) { throw new IllegalStateException("Registration of field accessor was disabled: " + fieldDescription); } @Override public FieldDescription.InDefinedShape cache(StackManipulation fieldValue, TypeDescription fieldType) { throw new IllegalStateException("Field values caching was disabled: " + fieldType); } /** * A factory for creating a {@link org.testifyproject.bytebuddy.implementation.Implementation.Context.Disabled}. */ public enum Factory implements Context.Factory { /** * The singleton instance. */ INSTANCE; @Override public ExtractableView make(TypeDescription instrumentedType, AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy, TypeInitializer typeInitializer, ClassFileVersion classFileVersion, ClassFileVersion auxiliaryClassFileVersion) { if (typeInitializer.isDefined()) { throw new IllegalStateException("Cannot define type initializer which was explicitly disabled: " + typeInitializer); } return new Disabled(instrumentedType, classFileVersion); } } } /** * A default implementation of an {@link Implementation.Context.ExtractableView} * which serves as its own {@link MethodAccessorFactory}. */ class Default extends ExtractableView.AbstractBase { /** * The name suffix to be appended to an accessor method. */ public static final String ACCESSOR_METHOD_SUFFIX = "accessor"; /** * The name prefix to be prepended to a field storing a cached value. */ public static final String FIELD_CACHE_PREFIX = "cachedValue"; /** * The naming strategy for naming auxiliary types that are registered. */ private final AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy; /** * The type initializer of the created instrumented type. */ private final TypeInitializer typeInitializer; /** * The class file version to use for auxiliary classes. */ private final ClassFileVersion auxiliaryClassFileVersion; /** * A mapping of special method invocations to their accessor methods that each invoke their mapped invocation. */ private final Map registeredAccessorMethods; /** * The registered getters. */ private final Map registeredGetters; /** * The registered setters. */ private final Map registeredSetters; /** * A map of registered auxiliary types to their dynamic type representation. */ private final Map auxiliaryTypes; /** * A map of already registered field caches to their field representation. */ private final Map registeredFieldCacheEntries; /** * A random suffix to append to the names of accessor methods. */ private final String suffix; /** * If {@code false}, the type initializer for this instance was already drained what prohibits the registration of additional cached field values. */ private boolean fieldCacheCanAppendEntries; /** * Creates a new default implementation context. * * @param instrumentedType The description of the type that is currently subject of creation. * @param classFileVersion The class file version of the created class. * @param auxiliaryTypeNamingStrategy The naming strategy for naming an auxiliary type. * @param typeInitializer The type initializer of the created instrumented type. * @param auxiliaryClassFileVersion The class file version to use for auxiliary classes. */ protected Default(TypeDescription instrumentedType, ClassFileVersion classFileVersion, AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy, TypeInitializer typeInitializer, ClassFileVersion auxiliaryClassFileVersion) { super(instrumentedType, classFileVersion); this.auxiliaryTypeNamingStrategy = auxiliaryTypeNamingStrategy; this.typeInitializer = typeInitializer; this.auxiliaryClassFileVersion = auxiliaryClassFileVersion; registeredAccessorMethods = new HashMap(); registeredGetters = new HashMap(); registeredSetters = new HashMap(); auxiliaryTypes = new HashMap(); registeredFieldCacheEntries = new HashMap(); suffix = RandomString.make(); fieldCacheCanAppendEntries = true; } @Override public boolean isEnabled() { return true; } @Override public MethodDescription.InDefinedShape registerAccessorFor(SpecialMethodInvocation specialMethodInvocation, AccessType accessType) { DelegationRecord record = registeredAccessorMethods.get(specialMethodInvocation); record = record == null ? new AccessorMethodDelegation(instrumentedType, suffix, accessType, specialMethodInvocation) : record.with(accessType); registeredAccessorMethods.put(specialMethodInvocation, record); return record.getMethod(); } @Override public MethodDescription.InDefinedShape registerGetterFor(FieldDescription fieldDescription, AccessType accessType) { DelegationRecord record = registeredGetters.get(fieldDescription); record = record == null ? new FieldGetterDelegation(instrumentedType, suffix, accessType, fieldDescription) : record.with(accessType); registeredGetters.put(fieldDescription, record); return record.getMethod(); } @Override public MethodDescription.InDefinedShape registerSetterFor(FieldDescription fieldDescription, AccessType accessType) { DelegationRecord record = registeredSetters.get(fieldDescription); record = record == null ? new FieldSetterDelegation(instrumentedType, suffix, accessType, fieldDescription) : record.with(accessType); registeredSetters.put(fieldDescription, record); return record.getMethod(); } @Override public TypeDescription register(AuxiliaryType auxiliaryType) { DynamicType dynamicType = auxiliaryTypes.get(auxiliaryType); if (dynamicType == null) { dynamicType = auxiliaryType.make(auxiliaryTypeNamingStrategy.name(instrumentedType), auxiliaryClassFileVersion, this); auxiliaryTypes.put(auxiliaryType, dynamicType); } return dynamicType.getTypeDescription(); } @Override public List getAuxiliaryTypes() { return new ArrayList(auxiliaryTypes.values()); } @Override public FieldDescription.InDefinedShape cache(StackManipulation fieldValue, TypeDescription fieldType) { FieldCacheEntry fieldCacheEntry = new FieldCacheEntry(fieldValue, fieldType); FieldDescription.InDefinedShape fieldCache = registeredFieldCacheEntries.get(fieldCacheEntry); if (fieldCache != null) { return fieldCache; } if (!fieldCacheCanAppendEntries) { throw new IllegalStateException("Cached values cannot be registered after defining the type initializer for " + instrumentedType); } fieldCache = new CacheValueField(instrumentedType, fieldType.asGenericType(), suffix, fieldValue.hashCode()); registeredFieldCacheEntries.put(fieldCacheEntry, fieldCache); return fieldCache; } @Override public void drain(TypeInitializer.Drain drain, ClassVisitor classVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) { fieldCacheCanAppendEntries = false; TypeInitializer typeInitializer = this.typeInitializer; for (Map.Entry entry : registeredFieldCacheEntries.entrySet()) { FieldVisitor fieldVisitor = classVisitor.visitField(entry.getValue().getModifiers(), entry.getValue().getInternalName(), entry.getValue().getDescriptor(), entry.getValue().getGenericSignature(), FieldDescription.NO_DEFAULT_VALUE); if (fieldVisitor != null) { fieldVisitor.visitEnd(); typeInitializer = typeInitializer.expandWith(entry.getKey().storeIn(entry.getValue())); } } drain.apply(classVisitor, typeInitializer, this); for (TypeWriter.MethodPool.Record record : registeredAccessorMethods.values()) { record.apply(classVisitor, this, annotationValueFilterFactory); } for (TypeWriter.MethodPool.Record record : registeredGetters.values()) { record.apply(classVisitor, this, annotationValueFilterFactory); } for (TypeWriter.MethodPool.Record record : registeredSetters.values()) { record.apply(classVisitor, this, annotationValueFilterFactory); } } /** * A description of a field that stores a cached value. */ protected static class CacheValueField extends FieldDescription.InDefinedShape.AbstractBase { /** * The instrumented type. */ private final TypeDescription instrumentedType; /** * The type of the cache's field. */ private final TypeDescription.Generic fieldType; /** * The suffix to use for the cache field's name. */ private final String suffix; /** * The hash value of the field's value for creating a unique field name. */ private final int hashCode; /** * Creates a new cache value field. * * @param instrumentedType The instrumented type. * @param fieldType The type of the cache's field. * @param suffix The suffix to use for the cache field's name. * @param hashCode The hash value of the field's value for creating a unique field name. */ protected CacheValueField(TypeDescription instrumentedType, TypeDescription.Generic fieldType, String suffix, int hashCode) { this.instrumentedType = instrumentedType; this.fieldType = fieldType; this.suffix = suffix; this.hashCode = hashCode; } @Override public TypeDescription.Generic getType() { return fieldType; } @Override public AnnotationList getDeclaredAnnotations() { return new AnnotationList.Empty(); } @Override public TypeDescription getDeclaringType() { return instrumentedType; } @Override public int getModifiers() { return Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC | (instrumentedType.isInterface() ? Opcodes.ACC_PUBLIC : Opcodes.ACC_PRIVATE); } @Override public String getName() { return String.format("%s$%s$%s", FIELD_CACHE_PREFIX, suffix, RandomString.hashOf(hashCode)); } } /** * A field cache entry for uniquely identifying a cached field. A cached field is described by the stack * manipulation that loads the field's value onto the operand stack and the type of the field. */ @EqualsAndHashCode protected static class FieldCacheEntry implements StackManipulation { /** * The field value that is represented by this field cache entry. */ private final StackManipulation fieldValue; /** * The field type that is represented by this field cache entry. */ private final TypeDescription fieldType; /** * Creates a new field cache entry. * * @param fieldValue The field value that is represented by this field cache entry. * @param fieldType The field type that is represented by this field cache entry. */ protected FieldCacheEntry(StackManipulation fieldValue, TypeDescription fieldType) { this.fieldValue = fieldValue; this.fieldType = fieldType; } /** * Returns a stack manipulation where the represented value is stored in the given field. * * @param fieldDescription A static field in which the value is to be stored. * @return A byte code appender that represents this storage. */ protected ByteCodeAppender storeIn(FieldDescription fieldDescription) { return new ByteCodeAppender.Simple(this, FieldAccess.forField(fieldDescription).write()); } /** * Returns the field type that is represented by this field cache entry. * * @return The field type that is represented by this field cache entry. */ protected TypeDescription getFieldType() { return fieldType; } @Override public boolean isValid() { return fieldValue.isValid(); } @Override public Size apply(MethodVisitor methodVisitor, Context implementationContext) { return fieldValue.apply(methodVisitor, implementationContext); } } /** * A base implementation of a method that accesses a property of an instrumented type. */ protected abstract static class AbstractPropertyAccessorMethod extends MethodDescription.InDefinedShape.AbstractBase { @Override public int getModifiers() { return Opcodes.ACC_SYNTHETIC | getBaseModifiers() | (getDeclaringType().isInterface() ? Opcodes.ACC_PUBLIC : Opcodes.ACC_FINAL); } /** * Returns the base modifiers, i.e. the modifiers that define the accessed property's features. * * @return Returns the base modifiers of the represented methods. */ protected abstract int getBaseModifiers(); } /** * A description of an accessor method to access another method from outside the instrumented type. */ protected static class AccessorMethod extends AbstractPropertyAccessorMethod { /** * The instrumented type. */ private final TypeDescription instrumentedType; /** * The method that is being accessed. */ private final MethodDescription methodDescription; /** * The suffix to append to the accessor method's name. */ private final String suffix; /** * Creates a new accessor method. * * @param instrumentedType The instrumented type. * @param methodDescription The method that is being accessed. * @param suffix The suffix to append to the accessor method's name. */ protected AccessorMethod(TypeDescription instrumentedType, MethodDescription methodDescription, String suffix) { this.instrumentedType = instrumentedType; this.methodDescription = methodDescription; this.suffix = suffix; } @Override public TypeDescription.Generic getReturnType() { return methodDescription.getReturnType().asRawType(); } @Override public ParameterList getParameters() { return new ParameterList.Explicit.ForTypes(this, methodDescription.getParameters().asTypeList().asRawTypes()); } @Override public TypeList.Generic getExceptionTypes() { return methodDescription.getExceptionTypes().asRawTypes(); } @Override public AnnotationValue getDefaultValue() { return AnnotationValue.UNDEFINED; } @Override public TypeList.Generic getTypeVariables() { return new TypeList.Generic.Empty(); } @Override public AnnotationList getDeclaredAnnotations() { return new AnnotationList.Empty(); } @Override public TypeDescription getDeclaringType() { return instrumentedType; } @Override public int getBaseModifiers() { return methodDescription.isStatic() ? Opcodes.ACC_STATIC : EMPTY_MASK; } @Override public String getInternalName() { return String.format("%s$%s$%s", methodDescription.getInternalName(), ACCESSOR_METHOD_SUFFIX, suffix); } } /** * A description of a field getter method. */ protected static class FieldGetter extends AbstractPropertyAccessorMethod { /** * The instrumented type. */ private final TypeDescription instrumentedType; /** * The field for which a getter is described. */ private final FieldDescription fieldDescription; /** * The name suffix for the field getter method. */ private final String suffix; /** * Creates a new field getter. * * @param instrumentedType The instrumented type. * @param fieldDescription The field for which a getter is described. * @param suffix The name suffix for the field getter method. */ protected FieldGetter(TypeDescription instrumentedType, FieldDescription fieldDescription, String suffix) { this.instrumentedType = instrumentedType; this.fieldDescription = fieldDescription; this.suffix = suffix; } @Override public TypeDescription.Generic getReturnType() { return fieldDescription.getType().asRawType(); } @Override public ParameterList getParameters() { return new ParameterList.Empty(); } @Override public TypeList.Generic getExceptionTypes() { return new TypeList.Generic.Empty(); } @Override public AnnotationValue getDefaultValue() { return AnnotationValue.UNDEFINED; } @Override public TypeList.Generic getTypeVariables() { return new TypeList.Generic.Empty(); } @Override public AnnotationList getDeclaredAnnotations() { return new AnnotationList.Empty(); } @Override public TypeDescription getDeclaringType() { return instrumentedType; } @Override protected int getBaseModifiers() { return fieldDescription.isStatic() ? Opcodes.ACC_STATIC : EMPTY_MASK; } @Override public String getInternalName() { return String.format("%s$%s$%s", fieldDescription.getName(), ACCESSOR_METHOD_SUFFIX, suffix); } } /** * A description of a field setter method. */ protected static class FieldSetter extends AbstractPropertyAccessorMethod { /** * The instrumented type. */ private final TypeDescription instrumentedType; /** * The field for which a setter is described. */ private final FieldDescription fieldDescription; /** * The name suffix for the field setter method. */ private final String suffix; /** * Creates a new field setter. * * @param instrumentedType The instrumented type. * @param fieldDescription The field for which a setter is described. * @param suffix The name suffix for the field setter method. */ protected FieldSetter(TypeDescription instrumentedType, FieldDescription fieldDescription, String suffix) { this.instrumentedType = instrumentedType; this.fieldDescription = fieldDescription; this.suffix = suffix; } @Override public TypeDescription.Generic getReturnType() { return TypeDescription.Generic.VOID; } @Override public ParameterList getParameters() { return new ParameterList.Explicit.ForTypes(this, Collections.singletonList(fieldDescription.getType().asRawType())); } @Override public TypeList.Generic getExceptionTypes() { return new TypeList.Generic.Empty(); } @Override public AnnotationValue getDefaultValue() { return AnnotationValue.UNDEFINED; } @Override public TypeList.Generic getTypeVariables() { return new TypeList.Generic.Empty(); } @Override public AnnotationList getDeclaredAnnotations() { return new AnnotationList.Empty(); } @Override public TypeDescription getDeclaringType() { return instrumentedType; } @Override protected int getBaseModifiers() { return fieldDescription.isStatic() ? Opcodes.ACC_STATIC : EMPTY_MASK; } @Override public String getInternalName() { return String.format("%s$%s$%s", fieldDescription.getName(), ACCESSOR_METHOD_SUFFIX, suffix); } } /** * An abstract method pool entry that delegates the implementation of a method to itself. */ @EqualsAndHashCode(callSuper = false) protected abstract static class DelegationRecord extends TypeWriter.MethodPool.Record.ForDefinedMethod implements ByteCodeAppender { /** * The delegation method. */ protected final MethodDescription.InDefinedShape methodDescription; /** * The record's visibility. */ protected final Visibility visibility; /** * Creates a new delegation record. * * @param methodDescription The delegation method. * @param visibility The method's actual visibility. */ protected DelegationRecord(MethodDescription.InDefinedShape methodDescription, Visibility visibility) { this.methodDescription = methodDescription; this.visibility = visibility; } /** * Returns this delegation record with the minimal visibility represented by the supplied access type. * * @param accessType The access type to enforce. * @return A new version of this delegation record with the minimal implied visibility. */ protected abstract DelegationRecord with(AccessType accessType); @Override public MethodDescription.InDefinedShape getMethod() { return methodDescription; } @Override public Sort getSort() { return Sort.IMPLEMENTED; } @Override public Visibility getVisibility() { return visibility; } @Override public void applyHead(MethodVisitor methodVisitor) { /* do nothing */ } @Override public void applyBody(MethodVisitor methodVisitor, Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory) { methodVisitor.visitCode(); Size size = applyCode(methodVisitor, implementationContext); methodVisitor.visitMaxs(size.getOperandStackSize(), size.getLocalVariableSize()); } @Override public void applyAttributes(MethodVisitor methodVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) { /* do nothing */ } @Override public Size applyCode(MethodVisitor methodVisitor, Context implementationContext) { return apply(methodVisitor, implementationContext, getMethod()); } @Override public TypeWriter.MethodPool.Record prepend(ByteCodeAppender byteCodeAppender) { throw new UnsupportedOperationException("Cannot prepend code to a delegation for " + methodDescription); } } /** * An implementation of a {@link TypeWriter.MethodPool.Record} for implementing * an accessor method. */ @EqualsAndHashCode(callSuper = true) protected static class AccessorMethodDelegation extends DelegationRecord { /** * The stack manipulation that represents the requested special method invocation. */ private final StackManipulation accessorMethodInvocation; /** * Creates a delegation to an accessor method. * * @param instrumentedType The instrumented type. * @param suffix The suffix to append to the method. * @param accessType The access type. * @param specialMethodInvocation The actual method's invocation. */ protected AccessorMethodDelegation(TypeDescription instrumentedType, String suffix, AccessType accessType, SpecialMethodInvocation specialMethodInvocation) { this(new AccessorMethod(instrumentedType, specialMethodInvocation.getMethodDescription(), suffix), accessType.getVisibility(), specialMethodInvocation); } /** * Creates a delegation to an accessor method. * * @param methodDescription The accessor method. * @param visibility The method's visibility. * @param accessorMethodInvocation The actual method's invocation. */ private AccessorMethodDelegation(MethodDescription.InDefinedShape methodDescription, Visibility visibility, StackManipulation accessorMethodInvocation) { super(methodDescription, visibility); this.accessorMethodInvocation = accessorMethodInvocation; } @Override protected DelegationRecord with(AccessType accessType) { return new AccessorMethodDelegation(methodDescription, visibility.expandTo(accessType.getVisibility()), accessorMethodInvocation); } @Override public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) { StackManipulation.Size stackSize = new StackManipulation.Compound( MethodVariableAccess.allArgumentsOf(instrumentedMethod).prependThisReference(), accessorMethodInvocation, MethodReturn.of(instrumentedMethod.getReturnType()) ).apply(methodVisitor, implementationContext); return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize()); } } /** * An implementation for a field getter. */ @EqualsAndHashCode(callSuper = true) protected static class FieldGetterDelegation extends DelegationRecord { /** * The field to read from. */ private final FieldDescription fieldDescription; /** * Creates a new field getter implementation. * * @param instrumentedType The instrumented type. * @param suffix The suffix to use for the setter method. * @param accessType The method's access type. * @param fieldDescription The field to write to. */ protected FieldGetterDelegation(TypeDescription instrumentedType, String suffix, AccessType accessType, FieldDescription fieldDescription) { this(new FieldGetter(instrumentedType, fieldDescription, suffix), accessType.getVisibility(), fieldDescription); } /** * Creates a new field getter implementation. * * @param methodDescription The delegation method. * @param visibility The delegation method's visibility. * @param fieldDescription The field to read. */ private FieldGetterDelegation(MethodDescription.InDefinedShape methodDescription, Visibility visibility, FieldDescription fieldDescription) { super(methodDescription, visibility); this.fieldDescription = fieldDescription; } @Override protected DelegationRecord with(AccessType accessType) { return new FieldGetterDelegation(methodDescription, visibility.expandTo(accessType.getVisibility()), fieldDescription); } @Override public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) { StackManipulation.Size stackSize = new StackManipulation.Compound( fieldDescription.isStatic() ? StackManipulation.Trivial.INSTANCE : MethodVariableAccess.loadThis(), FieldAccess.forField(fieldDescription).read(), MethodReturn.of(fieldDescription.getType()) ).apply(methodVisitor, implementationContext); return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize()); } } /** * An implementation for a field setter. */ @EqualsAndHashCode(callSuper = true) protected static class FieldSetterDelegation extends DelegationRecord { /** * The field to write to. */ private final FieldDescription fieldDescription; /** * Creates a new field setter implementation. * * @param instrumentedType The instrumented type. * @param suffix The suffix to use for the setter method. * @param accessType The method's access type. * @param fieldDescription The field to write to. */ protected FieldSetterDelegation(TypeDescription instrumentedType, String suffix, AccessType accessType, FieldDescription fieldDescription) { this(new FieldSetter(instrumentedType, fieldDescription, suffix), accessType.getVisibility(), fieldDescription); } /** * Creates a new field setter. * * @param methodDescription The field accessor method. * @param visibility The delegation method's visibility. * @param fieldDescription The field to write to. */ private FieldSetterDelegation(MethodDescription.InDefinedShape methodDescription, Visibility visibility, FieldDescription fieldDescription) { super(methodDescription, visibility); this.fieldDescription = fieldDescription; } @Override protected DelegationRecord with(AccessType accessType) { return new FieldSetterDelegation(methodDescription, visibility.expandTo(accessType.getVisibility()), fieldDescription); } @Override public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) { StackManipulation.Size stackSize = new StackManipulation.Compound( MethodVariableAccess.allArgumentsOf(instrumentedMethod).prependThisReference(), FieldAccess.forField(fieldDescription).write(), MethodReturn.VOID ).apply(methodVisitor, implementationContext); return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize()); } } /** * A factory for creating a {@link org.testifyproject.bytebuddy.implementation.Implementation.Context.Default}. */ public enum Factory implements ExtractableView.Factory { /** * The singleton instance. */ INSTANCE; @Override public ExtractableView make(TypeDescription instrumentedType, AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy, TypeInitializer typeInitializer, ClassFileVersion classFileVersion, ClassFileVersion auxiliaryClassFileVersion) { return new Default(instrumentedType, classFileVersion, auxiliaryTypeNamingStrategy, typeInitializer, auxiliaryClassFileVersion); } } } } /** * A compound implementation that allows to combine several implementations. *

 

* Note that the combination of two implementation might break the contract for implementing * {@link java.lang.Object#equals(Object)} and {@link Object#hashCode()} as described for * {@link Implementation}. * * @see Implementation */ @EqualsAndHashCode class Compound implements Implementation { /** * All implementation that are represented by this compound implementation. */ private final List implementations; /** * Creates a new immutable compound implementation. * * @param implementation The implementations to combine in their order. */ public Compound(Implementation... implementation) { this(Arrays.asList(implementation)); } /** * Creates a new immutable compound implementation. * * @param implementations The implementations to combine in their order. */ public Compound(List implementations) { this.implementations = new ArrayList(); for (Implementation implementation : implementations) { if (implementation instanceof Compound) { this.implementations.addAll(((Compound) implementation).implementations); } else { this.implementations.add(implementation); } } } @Override public InstrumentedType prepare(InstrumentedType instrumentedType) { for (Implementation implementation : implementations) { instrumentedType = implementation.prepare(instrumentedType); } return instrumentedType; } @Override public ByteCodeAppender appender(Target implementationTarget) { ByteCodeAppender[] byteCodeAppender = new ByteCodeAppender[implementations.size()]; int index = 0; for (Implementation implementation : implementations) { byteCodeAppender[index++] = implementation.appender(implementationTarget); } return new ByteCodeAppender.Compound(byteCodeAppender); } } /** * A simple implementation that does not register any members with the instrumented type. */ @EqualsAndHashCode class Simple implements Implementation { /** * The byte code appender to emmit. */ private final ByteCodeAppender byteCodeAppender; /** * Creates a new simple implementation for the given byte code appenders. * * @param byteCodeAppender The byte code appenders to apply in their order of application. */ public Simple(ByteCodeAppender... byteCodeAppender) { this.byteCodeAppender = new ByteCodeAppender.Compound(byteCodeAppender); } /** * Creates a new simple instrumentation for the given stack manipulations which are summarized in a * byte code appender that defines any requested method by these manipulations. * * @param stackManipulation The stack manipulation to apply in their order of application. */ public Simple(StackManipulation... stackManipulation) { byteCodeAppender = new ByteCodeAppender.Simple(stackManipulation); } @Override public InstrumentedType prepare(InstrumentedType instrumentedType) { return instrumentedType; } @Override public ByteCodeAppender appender(Target implementationTarget) { return byteCodeAppender; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy