package net.bytebuddy.dynamic.scaffold;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.EqualsAndHashCode;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.annotation.AnnotationList;
import net.bytebuddy.description.annotation.AnnotationValue;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.method.ParameterList;
import net.bytebuddy.description.modifier.ModifierContributor;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.PackageDescription;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.TypeList;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.TypeResolutionStrategy;
import net.bytebuddy.dynamic.scaffold.inline.MethodRebaseResolver;
import net.bytebuddy.dynamic.scaffold.inline.RebaseImplementationTarget;
import net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.LoadedTypeInitializer;
import net.bytebuddy.implementation.attribute.*;
import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
import net.bytebuddy.implementation.bytecode.constant.DefaultValue;
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.CompoundList;
import net.bytebuddy.utility.privilege.GetSystemPropertyAction;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.ClassRemapper;
import org.objectweb.asm.commons.SimpleRemapper;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import static net.bytebuddy.matcher.ElementMatchers.is;
import static net.bytebuddy.matcher.ElementMatchers.isSubTypeOf;
/**
* A type writer is a utility for writing an actual class file using the ASM library.
*
* @param <T> The best known loaded type for the dynamically created type.
*/
public interface TypeWriter<T> {
/**
* A system property that indicates a folder for Byte Buddy to dump class files of all types that it creates.
* If this property is not set, Byte Buddy does not dump any class files. This property is only read a single
* time which is why it must be set on application start-up.
*/
String DUMP_PROPERTY = "net.bytebuddy.dump";
/**
* Creates the dynamic type that is described by this type writer.
*
* @param typeResolver The type resolution strategy to use.
* @return An unloaded dynamic type that describes the created type.
*/
DynamicType.Unloaded<T> make(TypeResolutionStrategy.Resolved typeResolver);
/**
* An field pool that allows a lookup for how to implement a field.
*/
interface FieldPool {
/**
* Returns the field attribute appender that matches a given field description or a default field
* attribute appender if no appender was registered for the given field.
*
* @param fieldDescription The field description of interest.
* @return The registered field attribute appender for the given field or the default appender if no such
* appender was found.
*/
Record target(FieldDescription fieldDescription);
/**
* An entry of a field pool that describes how a field is implemented.
*
* @see net.bytebuddy.dynamic.scaffold.TypeWriter.FieldPool
*/
interface Record {
/**
* Determines if this record is implicit, i.e is not defined by a {@link FieldPool}.
*
* @return {@code true} if this record is implicit.
*/
boolean isImplicit();
/**
* Returns the field that this record represents.
*
* @return The field that this record represents.
*/
FieldDescription getField();
/**
* Returns the field attribute appender for a given field.
*
* @return The attribute appender to be applied on the given field.
*/
FieldAttributeAppender getFieldAppender();
/**
* Resolves the default value that this record represents. This is not possible for implicit records.
*
* @param defaultValue The default value that was defined previously or {@code null} if no default value is defined.
* @return The default value for the represented field or {@code null} if no default value is to be defined.
*/
Object resolveDefault(Object defaultValue);
/**
* Writes this entry to a given class visitor.
*
* @param classVisitor The class visitor to which this entry is to be written to.
* @param annotationValueFilterFactory The annotation value filter factory to apply when writing annotations.
*/
void apply(ClassVisitor classVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory);
/**
* Applies this record to a field visitor. This is not possible for implicit records.
*
* @param fieldVisitor The field visitor onto which this record is to be applied.
* @param annotationValueFilterFactory The annotation value filter factory to use for annotations.
*/
void apply(FieldVisitor fieldVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory);
/**
* A record for a simple field without a default value where all of the field's declared annotations are appended.
*/
@EqualsAndHashCode
class ForImplicitField implements Record {
/**
* The implemented field.
*/
private final FieldDescription fieldDescription;
/**
* Creates a new record for a simple field.
*
* @param fieldDescription The described field.
*/
public ForImplicitField(FieldDescription fieldDescription) {
this.fieldDescription = fieldDescription;
}
@Override
public boolean isImplicit() {
return true;
}
@Override
public FieldDescription getField() {
return fieldDescription;
}
@Override
public FieldAttributeAppender getFieldAppender() {
throw new IllegalStateException("An implicit field record does not expose a field appender: " + this);
}
@Override
public Object resolveDefault(Object defaultValue) {
throw new IllegalStateException("An implicit field record does not expose a default value: " + this);
}
@Override
public void apply(ClassVisitor classVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
FieldVisitor fieldVisitor = classVisitor.visitField(fieldDescription.getActualModifiers(),
fieldDescription.getInternalName(),
fieldDescription.getDescriptor(),
fieldDescription.getGenericSignature(),
FieldDescription.NO_DEFAULT_VALUE);
if (fieldVisitor != null) {
FieldAttributeAppender.ForInstrumentedField.INSTANCE.apply(fieldVisitor,
fieldDescription,
annotationValueFilterFactory.on(fieldDescription));
fieldVisitor.visitEnd();
}
}
@Override
public void apply(FieldVisitor fieldVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
throw new IllegalStateException("An implicit field record is not intended for partial application: " + this);
}
}
/**
* A record for a rich field with attributes and a potential default value.
*/
@EqualsAndHashCode
class ForExplicitField implements Record {
/**
* The attribute appender for the field.
*/
private final FieldAttributeAppender attributeAppender;
/**
* The field's default value.
*/
private final Object defaultValue;
/**
* The implemented field.
*/
private final FieldDescription fieldDescription;
/**
* Creates a record for a rich field.
*
* @param attributeAppender The attribute appender for the field.
* @param defaultValue The field's default value.
* @param fieldDescription The implemented field.
*/
public ForExplicitField(FieldAttributeAppender attributeAppender, Object defaultValue, FieldDescription fieldDescription) {
this.attributeAppender = attributeAppender;
this.defaultValue = defaultValue;
this.fieldDescription = fieldDescription;
}
@Override
public boolean isImplicit() {
return false;
}
@Override
public FieldDescription getField() {
return fieldDescription;
}
@Override
public FieldAttributeAppender getFieldAppender() {
return attributeAppender;
}
@Override
public Object resolveDefault(Object defaultValue) {
return this.defaultValue == null
? defaultValue
: this.defaultValue;
}
@Override
public void apply(ClassVisitor classVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
FieldVisitor fieldVisitor = classVisitor.visitField(fieldDescription.getActualModifiers(),
fieldDescription.getInternalName(),
fieldDescription.getDescriptor(),
fieldDescription.getGenericSignature(),
resolveDefault(FieldDescription.NO_DEFAULT_VALUE));
if (fieldVisitor != null) {
attributeAppender.apply(fieldVisitor, fieldDescription, annotationValueFilterFactory.on(fieldDescription));
fieldVisitor.visitEnd();
}
}
@Override
public void apply(FieldVisitor fieldVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
attributeAppender.apply(fieldVisitor, fieldDescription, annotationValueFilterFactory.on(fieldDescription));
}
}
}
}
/**
* An method pool that allows a lookup for how to implement a method.
*/
interface MethodPool {
/**
* Looks up a handler entry for a given method.
*
* @param methodDescription The method being processed.
* @return A handler entry for the given method.
*/
Record target(MethodDescription methodDescription);
/**
* An entry of a method pool that describes how a method is implemented.
*
* @see net.bytebuddy.dynamic.scaffold.TypeWriter.MethodPool
*/
interface Record {
/**
* Returns the sort of this method instrumentation.
*
* @return The sort of this method instrumentation.
*/
Sort getSort();
/**
* Returns the method that is implemented where the returned method resembles a potential transformation. An implemented
* method is only defined if a method is not {@link Record.Sort#SKIPPED}.
*
* @return The implemented method.
*/
MethodDescription getMethod();
/**
* The visibility to enforce for this method.
*
* @return The visibility to enforce for this method.
*/
Visibility getVisibility();
/**
* Prepends the given method appender to this entry.
*
* @param byteCodeAppender The byte code appender to prepend.
* @return This entry with the given code prepended.
*/
Record prepend(ByteCodeAppender byteCodeAppender);
/**
* Applies this method entry. This method can always be called and might be a no-op.
*
* @param classVisitor The class visitor to which this entry should be applied.
* @param implementationContext The implementation context to which this entry should be applied.
* @param annotationValueFilterFactory The annotation value filter factory to apply when writing annotations.
*/
void apply(ClassVisitor classVisitor, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory);
/**
* Applies the head of this entry. Applying an entry is only possible if a method is defined, i.e. the sort of this entry is not
* {@link Record.Sort#SKIPPED}.
*
* @param methodVisitor The method visitor to which this entry should be applied.
*/
void applyHead(MethodVisitor methodVisitor);
/**
* Applies the body of this entry. Applying the body of an entry is only possible if a method is implemented, i.e. the sort of this
* entry is {@link Record.Sort#IMPLEMENTED}.
*
* @param methodVisitor The method visitor to which this entry should be applied.
* @param implementationContext The implementation context to which this entry should be applied.
* @param annotationValueFilterFactory The annotation value filter factory to apply when writing annotations.
*/
void applyBody(MethodVisitor methodVisitor, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory);
/**
* Applies the attributes of this entry. Applying the body of an entry is only possible if a method is implemented, i.e. the sort of this
* entry is {@link Record.Sort#DEFINED}.
*
* @param methodVisitor The method visitor to which this entry should be applied.
* @param annotationValueFilterFactory The annotation value filter factory to apply when writing annotations.
*/
void applyAttributes(MethodVisitor methodVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory);
/**
* Applies the code of this entry. Applying the body of an entry is only possible if a method is implemented, i.e. the sort of this
* entry is {@link Record.Sort#IMPLEMENTED}.
*
* @param methodVisitor The method visitor to which this entry should be applied.
* @param implementationContext The implementation context to which this entry should be applied.
* @return The size requirements of the implemented code.
*/
ByteCodeAppender.Size applyCode(MethodVisitor methodVisitor, Implementation.Context implementationContext);
/**
* The sort of an entry.
*/
enum Sort {
/**
* Describes a method that should not be implemented or retained in its original state.
*/
SKIPPED(false, false),
/**
* Describes a method that should be defined but is abstract or native, i.e. does not define any byte code.
*/
DEFINED(true, false),
/**
* Describes a method that is implemented in byte code.
*/
IMPLEMENTED(true, true);
/**
* Indicates if this sort defines a method, with or without byte code.
*/
private final boolean define;
/**
* Indicates if this sort defines byte code.
*/
private final boolean implement;
/**
* Creates a new sort.
*
* @param define Indicates if this sort defines a method, with or without byte code.
* @param implement Indicates if this sort defines byte code.
*/
Sort(boolean define, boolean implement) {
this.define = define;
this.implement = implement;
}
/**
* Indicates if this sort defines a method, with or without byte code.
*
* @return {@code true} if this sort defines a method, with or without byte code.
*/
public boolean isDefined() {
return define;
}
/**
* Indicates if this sort defines byte code.
*
* @return {@code true} if this sort defines byte code.
*/
public boolean isImplemented() {
return implement;
}
}
/**
* A canonical implementation of a method that is not declared but inherited by the instrumented type.
*/
@EqualsAndHashCode
class ForNonImplementedMethod implements Record {
/**
* The undefined method.
*/
private final MethodDescription methodDescription;
/**
* Creates a new undefined record.
*
* @param methodDescription The undefined method.
*/
public ForNonImplementedMethod(MethodDescription methodDescription) {
this.methodDescription = methodDescription;
}
@Override
public void apply(ClassVisitor classVisitor, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory) {
/* do nothing */
}
@Override
public void applyBody(MethodVisitor methodVisitor, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory) {
throw new IllegalStateException("Cannot apply body for non-implemented method on " + methodDescription);
}
@Override
public void applyAttributes(MethodVisitor methodVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
/* do nothing */
}
@Override
public ByteCodeAppender.Size applyCode(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
throw new IllegalStateException("Cannot apply code for non-implemented method on " + methodDescription);
}
@Override
public void applyHead(MethodVisitor methodVisitor) {
throw new IllegalStateException("Cannot apply head for non-implemented method on " + methodDescription);
}
@Override
public MethodDescription getMethod() {
return methodDescription;
}
@Override
public Visibility getVisibility() {
return methodDescription.getVisibility();
}
@Override
public Sort getSort() {
return Sort.SKIPPED;
}
@Override
public Record prepend(ByteCodeAppender byteCodeAppender) {
return new ForDefinedMethod.WithBody(methodDescription, new ByteCodeAppender.Compound(byteCodeAppender,
new ByteCodeAppender.Simple(DefaultValue.of(methodDescription.getReturnType()), MethodReturn.of(methodDescription.getReturnType()))));
}
}
/**
* A base implementation of an abstract entry that defines a method.
*/
abstract class ForDefinedMethod implements Record {
@Override
public void apply(ClassVisitor classVisitor, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory) {
MethodVisitor methodVisitor = classVisitor.visitMethod(getMethod().getActualModifiers(getSort().isImplemented(), getVisibility()),
getMethod().getInternalName(),
getMethod().getDescriptor(),
getMethod().getGenericSignature(),
getMethod().getExceptionTypes().asErasures().toInternalNames());
if (methodVisitor != null) {
ParameterList<?> parameterList = getMethod().getParameters();
if (parameterList.hasExplicitMetaData()) {
for (ParameterDescription parameterDescription : parameterList) {
methodVisitor.visitParameter(parameterDescription.getName(), parameterDescription.getModifiers());
}
}
applyHead(methodVisitor);
applyBody(methodVisitor, implementationContext, annotationValueFilterFactory);
methodVisitor.visitEnd();
}
}
/**
* Describes an entry that defines a method as byte code.
*/
@EqualsAndHashCode(callSuper = false)
public static class WithBody extends ForDefinedMethod {
/**
* The implemented method.
*/
private final MethodDescription methodDescription;
/**
* The byte code appender to apply.
*/
private final ByteCodeAppender byteCodeAppender;
/**
* The method attribute appender to apply.
*/
private final MethodAttributeAppender methodAttributeAppender;
/**
* The represented method's minimum visibility.
*/
private final Visibility visibility;
/**
* Creates a new record for an implemented method without attributes or a modifier resolver.
*
* @param methodDescription The implemented method.
* @param byteCodeAppender The byte code appender to apply.
*/
public WithBody(MethodDescription methodDescription, ByteCodeAppender byteCodeAppender) {
this(methodDescription, byteCodeAppender, MethodAttributeAppender.NoOp.INSTANCE, methodDescription.getVisibility());
}
/**
* Creates a new entry for a method that defines a method as byte code.
*
* @param methodDescription The implemented method.
* @param byteCodeAppender The byte code appender to apply.
* @param methodAttributeAppender The method attribute appender to apply.
* @param visibility The represented method's minimum visibility.
*/
public WithBody(MethodDescription methodDescription,
ByteCodeAppender byteCodeAppender,
MethodAttributeAppender methodAttributeAppender,
Visibility visibility) {
this.methodDescription = methodDescription;
this.byteCodeAppender = byteCodeAppender;
this.methodAttributeAppender = methodAttributeAppender;
this.visibility = visibility;
}
@Override
public MethodDescription 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, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory) {
applyAttributes(methodVisitor, annotationValueFilterFactory);
methodVisitor.visitCode();
ByteCodeAppender.Size size = applyCode(methodVisitor, implementationContext);
methodVisitor.visitMaxs(size.getOperandStackSize(), size.getLocalVariableSize());
}
@Override
public void applyAttributes(MethodVisitor methodVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilterFactory.on(methodDescription));
}
@Override
public ByteCodeAppender.Size applyCode(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
return byteCodeAppender.apply(methodVisitor, implementationContext, methodDescription);
}
@Override
public Record prepend(ByteCodeAppender byteCodeAppender) {
return new WithBody(methodDescription,
new ByteCodeAppender.Compound(byteCodeAppender, this.byteCodeAppender),
methodAttributeAppender,
visibility);
}
}
/**
* Describes an entry that defines a method but without byte code and without an annotation value.
*/
@EqualsAndHashCode(callSuper = false)
public static class WithoutBody extends ForDefinedMethod {
/**
* The implemented method.
*/
private final MethodDescription methodDescription;
/**
* The method attribute appender to apply.
*/
private final MethodAttributeAppender methodAttributeAppender;
/**
* The represented method's minimum visibility.
*/
private final Visibility visibility;
/**
* Creates a new entry for a method that is defines but does not append byte code, i.e. is native or abstract.
*
* @param methodDescription The implemented method.
* @param methodAttributeAppender The method attribute appender to apply.
* @param visibility The represented method's minimum visibility.
*/
public WithoutBody(MethodDescription methodDescription, MethodAttributeAppender methodAttributeAppender, Visibility visibility) {
this.methodDescription = methodDescription;
this.methodAttributeAppender = methodAttributeAppender;
this.visibility = visibility;
}
@Override
public MethodDescription getMethod() {
return methodDescription;
}
@Override
public Sort getSort() {
return Sort.DEFINED;
}
@Override
public Visibility getVisibility() {
return visibility;
}
@Override
public void applyHead(MethodVisitor methodVisitor) {
/* do nothing */
}
@Override
public void applyBody(MethodVisitor methodVisitor, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory) {
applyAttributes(methodVisitor, annotationValueFilterFactory);
}
@Override
public void applyAttributes(MethodVisitor methodVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilterFactory.on(methodDescription));
}
@Override
public ByteCodeAppender.Size applyCode(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
throw new IllegalStateException("Cannot apply code for abstract method on " + methodDescription);
}
@Override
public Record prepend(ByteCodeAppender byteCodeAppender) {
throw new IllegalStateException("Cannot prepend code for abstract method on " + methodDescription);
}
}
/**
* Describes an entry that defines a method with a default annotation value.
*/
@EqualsAndHashCode(callSuper = false)
public static class WithAnnotationDefaultValue extends ForDefinedMethod {
/**
* The implemented method.
*/
private final MethodDescription methodDescription;
/**
* The annotation value to define.
*/
private final AnnotationValue<?, ?> annotationValue;
/**
* The method attribute appender to apply.
*/
private final MethodAttributeAppender methodAttributeAppender;
/**
* Creates a new entry for defining a method with a default annotation value.
*
* @param methodDescription The implemented method.
* @param annotationValue The annotation value to define.
* @param methodAttributeAppender The method attribute appender to apply.
*/
public WithAnnotationDefaultValue(MethodDescription methodDescription,
AnnotationValue<?, ?> annotationValue,
MethodAttributeAppender methodAttributeAppender) {
this.methodDescription = methodDescription;
this.annotationValue = annotationValue;
this.methodAttributeAppender = methodAttributeAppender;
}
@Override
public MethodDescription getMethod() {
return methodDescription;
}
@Override
public Sort getSort() {
return Sort.DEFINED;
}
@Override
public Visibility getVisibility() {
return methodDescription.getVisibility();
}
@Override
public void applyHead(MethodVisitor methodVisitor) {
if (!methodDescription.isDefaultValue(annotationValue)) {
throw new IllegalStateException("Cannot set " + annotationValue + " as default for " + methodDescription);
}
AnnotationVisitor annotationVisitor = methodVisitor.visitAnnotationDefault();
AnnotationAppender.Default.apply(annotationVisitor,
methodDescription.getReturnType().asErasure(),
AnnotationAppender.NO_NAME,
annotationValue.resolve());
annotationVisitor.visitEnd();
}
@Override
public void applyBody(MethodVisitor methodVisitor, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory) {
methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilterFactory.on(methodDescription));
}
@Override
public void applyAttributes(MethodVisitor methodVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
throw new IllegalStateException("Cannot apply attributes for default value on " + methodDescription);
}
@Override
public ByteCodeAppender.Size applyCode(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
throw new IllegalStateException("Cannot apply code for default value on " + methodDescription);
}
@Override
public Record prepend(ByteCodeAppender byteCodeAppender) {
throw new IllegalStateException("Cannot prepend code for default value on " + methodDescription);
}
}
/**
* A record for a visibility bridge.
*/
@EqualsAndHashCode(callSuper = false)
public static class OfVisibilityBridge extends ForDefinedMethod implements ByteCodeAppender {
/**
* The visibility bridge.
*/
private final MethodDescription visibilityBridge;
/**
* The method the visibility bridge invokes.
*/
private final MethodDescription bridgeTarget;
/**
* The type on which the bridge method is invoked.
*/
private final TypeDescription bridgeType;
/**
* The attribute appender to apply to the visibility bridge.
*/
private final MethodAttributeAppender attributeAppender;
/**
* Creates a new record for a visibility bridge.
*
* @param visibilityBridge The visibility bridge.
* @param bridgeTarget The method the visibility bridge invokes.
* @param bridgeType The type of the instrumented type.
* @param attributeAppender The attribute appender to apply to the visibility bridge.
*/
protected OfVisibilityBridge(MethodDescription visibilityBridge,
MethodDescription bridgeTarget,
TypeDescription bridgeType,
MethodAttributeAppender attributeAppender) {
this.visibilityBridge = visibilityBridge;
this.bridgeTarget = bridgeTarget;
this.bridgeType = bridgeType;
this.attributeAppender = attributeAppender;
}
/**
* Creates a record for a visibility bridge.
*
* @param instrumentedType The instrumented type.
* @param bridgeTarget The target method of the visibility bridge.
* @param attributeAppender The attribute appender to apply to the visibility bridge.
* @return A record describing the visibility bridge.
*/
public static Record of(TypeDescription instrumentedType, MethodDescription bridgeTarget, MethodAttributeAppender attributeAppender) {
// Default method bridges must be dispatched on an implemented interface type, not considering the declaring type.
TypeDefinition bridgeType = null;
if (bridgeTarget.isDefaultMethod()) {
TypeDescription declaringType = bridgeTarget.getDeclaringType().asErasure();
for (TypeDescription interfaceType : instrumentedType.getInterfaces().asErasures().filter(isSubTypeOf(declaringType))) {
if (bridgeType == null || declaringType.isAssignableTo(bridgeType.asErasure())) {
bridgeType = interfaceType;
}
}
}
// Non-default method or default method that is inherited by a super class.
if (bridgeType == null) {
bridgeType = instrumentedType.getSuperClass();
}
return new OfVisibilityBridge(new VisibilityBridge(instrumentedType, bridgeTarget),
bridgeTarget,
bridgeType.asErasure(),
attributeAppender);
}
@Override
public MethodDescription getMethod() {
return visibilityBridge;
}
@Override
public Sort getSort() {
return Sort.IMPLEMENTED;
}
@Override
public Visibility getVisibility() {
return bridgeTarget.getVisibility();
}
@Override
public Record prepend(ByteCodeAppender byteCodeAppender) {
return new ForDefinedMethod.WithBody(visibilityBridge,
new ByteCodeAppender.Compound(this, byteCodeAppender),
attributeAppender,
bridgeTarget.getVisibility());
}
@Override
public void applyHead(MethodVisitor methodVisitor) {
/* do nothing */
}
@Override
public void applyBody(MethodVisitor methodVisitor, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory) {
applyAttributes(methodVisitor, annotationValueFilterFactory);
methodVisitor.visitCode();
ByteCodeAppender.Size size = applyCode(methodVisitor, implementationContext);
methodVisitor.visitMaxs(size.getOperandStackSize(), size.getLocalVariableSize());
}
@Override
public void applyAttributes(MethodVisitor methodVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
attributeAppender.apply(methodVisitor, visibilityBridge, annotationValueFilterFactory.on(visibilityBridge));
}
@Override
public Size applyCode(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
return apply(methodVisitor, implementationContext, visibilityBridge);
}
@Override
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) {
return new ByteCodeAppender.Simple(
MethodVariableAccess.allArgumentsOf(instrumentedMethod).prependThisReference(),
MethodInvocation.invoke(bridgeTarget).special(bridgeType),
MethodReturn.of(instrumentedMethod.getReturnType())
).apply(methodVisitor, implementationContext, instrumentedMethod);
}
/**
* A method describing a visibility bridge.
*/
protected static class VisibilityBridge extends MethodDescription.InDefinedShape.AbstractBase {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* The method that is the target of the bridge.
*/
private final MethodDescription bridgeTarget;
/**
* Creates a new visibility bridge.
*
* @param instrumentedType The instrumented type.
* @param bridgeTarget The method that is the target of the bridge.
*/
protected VisibilityBridge(TypeDescription instrumentedType, MethodDescription bridgeTarget) {
this.instrumentedType = instrumentedType;
this.bridgeTarget = bridgeTarget;
}
@Override
public TypeDescription getDeclaringType() {
return instrumentedType;
}
@Override
public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
return new ParameterList.Explicit.ForTypes(this, bridgeTarget.getParameters().asTypeList().asRawTypes());
}
@Override
public TypeDescription.Generic getReturnType() {
return bridgeTarget.getReturnType().asRawType();
}
@Override
public TypeList.Generic getExceptionTypes() {
return bridgeTarget.getExceptionTypes().asRawTypes();
}
@Override
public AnnotationValue<?, ?> getDefaultValue() {
return AnnotationValue.UNDEFINED;
}
@Override
public TypeList.Generic getTypeVariables() {
return new TypeList.Generic.Empty();
}
@Override
public AnnotationList getDeclaredAnnotations() {
return bridgeTarget.getDeclaredAnnotations();
}
@Override
public int getModifiers() {
return (bridgeTarget.getModifiers() | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_BRIDGE) & ~Opcodes.ACC_NATIVE;
}
@Override
public String getInternalName() {
return bridgeTarget.getName();
}
}
}
}
/**
* A wrapper that appends accessor bridges for a method's implementation. The bridges are only added if
* {@link net.bytebuddy.dynamic.scaffold.TypeWriter.MethodPool.Record#apply(ClassVisitor, Implementation.Context, AnnotationValueFilter.Factory)}
* is invoked such that bridges are not appended for methods that are rebased or redefined as such types already have bridge methods in place.
*/
@EqualsAndHashCode
class AccessBridgeWrapper implements Record {
/**
* The delegate for implementing the bridge's target.
*/
private final Record delegate;
/**
* The instrumented type that defines the bridge methods and the bridge target.
*/
private final TypeDescription instrumentedType;
/**
* The target of the bridge method.
*/
private final MethodDescription bridgeTarget;
/**
* A collection of all tokens representing all bridge methods.
*/
private final Set<MethodDescription.TypeToken> bridgeTypes;
/**
* The attribute appender being applied for the bridge target.
*/
private final MethodAttributeAppender attributeAppender;
/**
* Creates a wrapper for adding accessor bridges.
*
* @param delegate The delegate for implementing the bridge's target.
* @param instrumentedType The instrumented type that defines the bridge methods and the bridge target.
* @param bridgeTarget The target of the bridge method.
* @param bridgeTypes A collection of all tokens representing all bridge methods.
* @param attributeAppender The attribute appender being applied for the bridge target.
*/
protected AccessBridgeWrapper(Record delegate,
TypeDescription instrumentedType,
MethodDescription bridgeTarget,
Set<MethodDescription.TypeToken> bridgeTypes,
MethodAttributeAppender attributeAppender) {
this.delegate = delegate;
this.instrumentedType = instrumentedType;
this.bridgeTarget = bridgeTarget;
this.bridgeTypes = bridgeTypes;
this.attributeAppender = attributeAppender;
}
/**
* Wraps the given record in an accessor bridge wrapper if necessary.
*
* @param delegate The delegate for implementing the bridge's target.
* @param instrumentedType The instrumented type that defines the bridge methods and the bridge target.
* @param bridgeTarget The bridge methods' target methods.
* @param bridgeTypes A collection of all tokens representing all bridge methods.
* @param attributeAppender The attribute appender being applied for the bridge target.
* @return The given record wrapped by a bridge method wrapper if necessary.
*/
public static Record of(Record delegate,
TypeDescription instrumentedType,
MethodDescription bridgeTarget,
Set<MethodDescription.TypeToken> bridgeTypes,
MethodAttributeAppender attributeAppender) {
Set<MethodDescription.TypeToken> compatibleBridgeTypes = new HashSet<MethodDescription.TypeToken>();
for (MethodDescription.TypeToken bridgeType : bridgeTypes) {
if (bridgeTarget.isBridgeCompatible(bridgeType)) {
compatibleBridgeTypes.add(bridgeType);
}
}
return compatibleBridgeTypes.isEmpty() || (instrumentedType.isInterface() && !delegate.getSort().isImplemented())
? delegate
: new AccessBridgeWrapper(delegate, instrumentedType, bridgeTarget, compatibleBridgeTypes, attributeAppender);
}
@Override
public Sort getSort() {
return delegate.getSort();
}
@Override
public MethodDescription getMethod() {
return bridgeTarget;
}
@Override
public Visibility getVisibility() {
return delegate.getVisibility();
}
@Override
public Record prepend(ByteCodeAppender byteCodeAppender) {
return new AccessBridgeWrapper(delegate.prepend(byteCodeAppender), instrumentedType, bridgeTarget, bridgeTypes, attributeAppender);
}
@Override
public void apply(ClassVisitor classVisitor,
Implementation.Context implementationContext,
AnnotationValueFilter.Factory annotationValueFilterFactory) {
delegate.apply(classVisitor, implementationContext, annotationValueFilterFactory);
for (MethodDescription.TypeToken bridgeType : bridgeTypes) {
MethodDescription.InDefinedShape bridgeMethod = new AccessorBridge(bridgeTarget, bridgeType, instrumentedType);
MethodDescription.InDefinedShape bridgeTarget = new BridgeTarget(this.bridgeTarget, instrumentedType);
MethodVisitor methodVisitor = classVisitor.visitMethod(bridgeMethod.getActualModifiers(true, getVisibility()),
bridgeMethod.getInternalName(),
bridgeMethod.getDescriptor(),
MethodDescription.NON_GENERIC_SIGNATURE,
bridgeMethod.getExceptionTypes().asErasures().toInternalNames());
if (methodVisitor != null) {
attributeAppender.apply(methodVisitor, bridgeMethod, annotationValueFilterFactory.on(instrumentedType));
methodVisitor.visitCode();
ByteCodeAppender.Size size = new ByteCodeAppender.Simple(
MethodVariableAccess.allArgumentsOf(bridgeMethod).asBridgeOf(bridgeTarget).prependThisReference(),
MethodInvocation.invoke(bridgeTarget).virtual(instrumentedType),
bridgeTarget.getReturnType().asErasure().isAssignableTo(bridgeMethod.getReturnType().asErasure())
? StackManipulation.Trivial.INSTANCE
: TypeCasting.to(bridgeMethod.getReturnType().asErasure()),
MethodReturn.of(bridgeMethod.getReturnType())
).apply(methodVisitor, implementationContext, bridgeMethod);
methodVisitor.visitMaxs(size.getOperandStackSize(), size.getLocalVariableSize());
methodVisitor.visitEnd();
}
}
}
@Override
public void applyHead(MethodVisitor methodVisitor) {
delegate.applyHead(methodVisitor);
}
@Override
public void applyBody(MethodVisitor methodVisitor,
Implementation.Context implementationContext,
AnnotationValueFilter.Factory annotationValueFilterFactory) {
delegate.applyBody(methodVisitor, implementationContext, annotationValueFilterFactory);
}
@Override
public void applyAttributes(MethodVisitor methodVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
delegate.applyAttributes(methodVisitor, annotationValueFilterFactory);
}
@Override
public ByteCodeAppender.Size applyCode(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
return delegate.applyCode(methodVisitor, implementationContext);
}
/**
* A method representing an accessor bridge method.
*/
protected static class AccessorBridge extends MethodDescription.InDefinedShape.AbstractBase {
/**
* The target method of the bridge.
*/
private final MethodDescription bridgeTarget;
/**
* The bridge's type token.
*/
private final MethodDescription.TypeToken bridgeType;
/**
* The instrumented type defining the bridge target.
*/
private final TypeDescription instrumentedType;
/**
* Creates a new accessor bridge method.
*
* @param bridgeTarget The target method of the bridge.
* @param bridgeType The bridge's type token.
* @param instrumentedType The instrumented type defining the bridge target.
*/
protected AccessorBridge(MethodDescription bridgeTarget, TypeToken bridgeType, TypeDescription instrumentedType) {
this.bridgeTarget = bridgeTarget;
this.bridgeType = bridgeType;
this.instrumentedType = instrumentedType;
}
@Override
public TypeDescription getDeclaringType() {
return instrumentedType;
}
@Override
public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
return new ParameterList.Explicit.ForTypes(this, bridgeType.getParameterTypes());
}
@Override
public TypeDescription.Generic getReturnType() {
return bridgeType.getReturnType().asGenericType();
}
@Override
public TypeList.Generic getExceptionTypes() {
return bridgeTarget.getExceptionTypes().accept(TypeDescription.Generic.Visitor.TypeErasing.INSTANCE);
}
@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 int getModifiers() {
return (bridgeTarget.getModifiers() | Opcodes.ACC_BRIDGE | Opcodes.ACC_SYNTHETIC) & ~(Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE);
}
@Override
public String getInternalName() {
return bridgeTarget.getInternalName();
}
}
/**
* A method representing a bridge's target method in its defined shape.
*/
protected static class BridgeTarget extends MethodDescription.InDefinedShape.AbstractBase {
/**
* The target method of the bridge.
*/
private final MethodDescription bridgeTarget;
/**
* The instrumented type defining the bridge target.
*/
private final TypeDescription instrumentedType;
/**
* Creates a new bridge target.
*
* @param bridgeTarget The target method of the bridge.
* @param instrumentedType The instrumented type defining the bridge target.
*/
protected BridgeTarget(MethodDescription bridgeTarget, TypeDescription instrumentedType) {
this.bridgeTarget = bridgeTarget;
this.instrumentedType = instrumentedType;
}
@Override
public TypeDescription getDeclaringType() {
return instrumentedType;
}
@Override
public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
return new ParameterList.ForTokens(this, bridgeTarget.getParameters().asTokenList(is(instrumentedType)));
}
@Override
public TypeDescription.Generic getReturnType() {
return bridgeTarget.getReturnType();
}
@Override
public TypeList.Generic getExceptionTypes() {
return bridgeTarget.getExceptionTypes();
}
@Override
public AnnotationValue<?, ?> getDefaultValue() {
return bridgeTarget.getDefaultValue();
}
@Override
public TypeList.Generic getTypeVariables() {
return bridgeTarget.getTypeVariables();
}
@Override
public AnnotationList getDeclaredAnnotations() {
return bridgeTarget.getDeclaredAnnotations();
}
@Override
public int getModifiers() {
return bridgeTarget.getModifiers();
}
@Override
public String getInternalName() {
return bridgeTarget.getInternalName();
}
}
}
}
}
/**
* A default implementation of a {@link net.bytebuddy.dynamic.scaffold.TypeWriter}.
*
* @param <S> The best known loaded type for the dynamically created type.
*/
@EqualsAndHashCode
abstract class Default<S> implements TypeWriter<S> {
/**
* A folder for dumping class files or {@code null} if no dump should be generated.
*/
private static final String DUMP_FOLDER;
/*
* Reads the dumping property that is set at program start up. This might cause an error because of security constraints.
*/
static {
String dumpFolder;
try {
dumpFolder = AccessController.doPrivileged(new GetSystemPropertyAction(DUMP_PROPERTY));
} catch (RuntimeException exception) {
dumpFolder = null;
Logger.getLogger("net.bytebuddy").log(Level.WARNING, "Could not enable dumping of class files", exception);
}
DUMP_FOLDER = dumpFolder;
}
/**
* The instrumented type to be created.
*/
protected final TypeDescription instrumentedType;
/**
* The class file specified by the user.
*/
protected final ClassFileVersion classFileVersion;
/**
* The field pool to use.
*/
protected final FieldPool fieldPool;
/**
* The explicit auxiliary types to add to the created type.
*/
protected final List<? extends DynamicType> auxiliaryTypes;
/**
* The instrumented type's declared fields.
*/
protected final FieldList<FieldDescription.InDefinedShape> fields;
/**
* The instrumented type's methods that are declared or inherited.
*/
protected final MethodList<?> methods;
/**
* The instrumented methods relevant to this type creation.
*/
protected final MethodList<?> instrumentedMethods;
/**
* The loaded type initializer to apply onto the created type after loading.
*/
protected final LoadedTypeInitializer loadedTypeInitializer;
/**
* The type initializer to include in the created type's type initializer.
*/
protected final TypeInitializer typeInitializer;
/**
* The type attribute appender to apply onto the instrumented type.
*/
protected final TypeAttributeAppender typeAttributeAppender;
/**
* The ASM visitor wrapper to apply onto the class writer.
*/
protected final AsmVisitorWrapper asmVisitorWrapper;
/**
* The annotation value filter factory to apply.
*/
protected final AnnotationValueFilter.Factory annotationValueFilterFactory;
/**
* The annotation retention to apply.
*/
protected final AnnotationRetention annotationRetention;
/**
* The naming strategy for auxiliary types to apply.
*/
protected final AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy;
/**
* The implementation context factory to apply.
*/
protected final Implementation.Context.Factory implementationContextFactory;
/**
* Determines if a type should be explicitly validated.
*/
protected final TypeValidation typeValidation;
/**
* The type pool to use for computing stack map frames, if required.
*/
protected final TypePool typePool;
/**
* Creates a new default type writer.
*
* @param instrumentedType The instrumented type to be created.
* @param classFileVersion The class file specified by the user.
* @param fieldPool The field pool to use.
* @param auxiliaryTypes The explicit auxiliary types to add to the created type.
* @param fields The instrumented type's declared fields.
* @param methods The instrumented type's declared and virtually inhertied methods.
* @param instrumentedMethods The instrumented methods relevant to this type creation.
* @param loadedTypeInitializer The loaded type initializer to apply onto the created type after loading.
* @param typeInitializer The type initializer to include in the created type's type initializer.
* @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
* @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
* @param annotationValueFilterFactory The annotation value filter factory to apply.
* @param annotationRetention The annotation retention to apply.
* @param auxiliaryTypeNamingStrategy The naming strategy for auxiliary types to apply.
* @param implementationContextFactory The implementation context factory to apply.
* @param typeValidation Determines if a type should be explicitly validated.
* @param typePool The type pool to use for computing stack map frames, if required.
*/
protected Default(TypeDescription instrumentedType,
ClassFileVersion classFileVersion,
FieldPool fieldPool,
List<? extends DynamicType> auxiliaryTypes,
FieldList<FieldDescription.InDefinedShape> fields,
MethodList<?> methods,
MethodList<?> instrumentedMethods,
LoadedTypeInitializer loadedTypeInitializer,
TypeInitializer typeInitializer,
TypeAttributeAppender typeAttributeAppender,
AsmVisitorWrapper asmVisitorWrapper,
AnnotationValueFilter.Factory annotationValueFilterFactory,
AnnotationRetention annotationRetention,
AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
Implementation.Context.Factory implementationContextFactory,
TypeValidation typeValidation,
TypePool typePool) {
this.instrumentedType = instrumentedType;
this.classFileVersion = classFileVersion;
this.fieldPool = fieldPool;
this.auxiliaryTypes = auxiliaryTypes;
this.fields = fields;
this.methods = methods;
this.instrumentedMethods = instrumentedMethods;
this.loadedTypeInitializer = loadedTypeInitializer;
this.typeInitializer = typeInitializer;
this.typeAttributeAppender = typeAttributeAppender;
this.asmVisitorWrapper = asmVisitorWrapper;
this.auxiliaryTypeNamingStrategy = auxiliaryTypeNamingStrategy;
this.annotationValueFilterFactory = annotationValueFilterFactory;
this.annotationRetention = annotationRetention;
this.implementationContextFactory = implementationContextFactory;
this.typeValidation = typeValidation;
this.typePool = typePool;
}
/**
* Creates a type writer for creating a new type.
*
* @param methodRegistry The compiled method registry to use.
* @param fieldPool The field pool to use.
* @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
* @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
* @param classFileVersion The class file version to use when no explicit class file version is applied.
* @param annotationValueFilterFactory The annotation value filter factory to apply.
* @param annotationRetention The annotation retention to apply.
* @param auxiliaryTypeNamingStrategy The naming strategy for auxiliary types to apply.
* @param implementationContextFactory The implementation context factory to apply.
* @param typeValidation Determines if a type should be explicitly validated.
* @param typePool The type pool to use for computing stack map frames, if required.
* @param <U> A loaded type that the instrumented type guarantees to subclass.
* @return A suitable type writer.
*/
public static <U> TypeWriter<U> forCreation(MethodRegistry.Compiled methodRegistry,
FieldPool fieldPool,
TypeAttributeAppender typeAttributeAppender,
AsmVisitorWrapper asmVisitorWrapper,
ClassFileVersion classFileVersion,
AnnotationValueFilter.Factory annotationValueFilterFactory,
AnnotationRetention annotationRetention,
AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
Implementation.Context.Factory implementationContextFactory,
TypeValidation typeValidation,
TypePool typePool) {
return new ForCreation<U>(methodRegistry.getInstrumentedType(),
classFileVersion,
fieldPool,
methodRegistry,
Collections.<DynamicType>emptyList(),
methodRegistry.getInstrumentedType().getDeclaredFields(),
methodRegistry.getMethods(),
methodRegistry.getInstrumentedMethods(),
methodRegistry.getLoadedTypeInitializer(),
methodRegistry.getTypeInitializer(),
typeAttributeAppender,
asmVisitorWrapper,
annotationValueFilterFactory,
annotationRetention,
auxiliaryTypeNamingStrategy,
implementationContextFactory,
typeValidation,
typePool);
}
/**
* Creates a type writer for redefining a type.
*
* @param methodRegistry The compiled method registry to use.
* @param fieldPool The field pool to use.
* @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
* @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
* @param classFileVersion The class file version to use when no explicit class file version is applied.
* @param annotationValueFilterFactory The annotation value filter factory to apply.
* @param annotationRetention The annotation retention to apply.
* @param auxiliaryTypeNamingStrategy The naming strategy for auxiliary types to apply.
* @param implementationContextFactory The implementation context factory to apply.
* @param typeValidation Determines if a type should be explicitly validated.
* @param typePool The type pool to use for computing stack map frames, if required.
* @param originalType The original type that is being redefined or rebased.
* @param classFileLocator The class file locator for locating the original type's class file.
* @param <U> A loaded type that the instrumented type guarantees to subclass.
* @return A suitable type writer.
*/
public static <U> TypeWriter<U> forRedefinition(MethodRegistry.Prepared methodRegistry,
FieldPool fieldPool,
TypeAttributeAppender typeAttributeAppender,
AsmVisitorWrapper asmVisitorWrapper,
ClassFileVersion classFileVersion,
AnnotationValueFilter.Factory annotationValueFilterFactory,
AnnotationRetention annotationRetention,
AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
Implementation.Context.Factory implementationContextFactory,
TypeValidation typeValidation,
TypePool typePool,
TypeDescription originalType,
ClassFileLocator classFileLocator) {
return new ForInlining<U>(methodRegistry.getInstrumentedType(),
classFileVersion,
fieldPool,
methodRegistry,
SubclassImplementationTarget.Factory.LEVEL_TYPE,
Collections.<DynamicType>emptyList(),
methodRegistry.getInstrumentedType().getDeclaredFields(),
methodRegistry.getMethods(),
methodRegistry.getInstrumentedMethods(),
methodRegistry.getLoadedTypeInitializer(),
methodRegistry.getTypeInitializer(),
typeAttributeAppender,
asmVisitorWrapper,
annotationValueFilterFactory,
annotationRetention,
auxiliaryTypeNamingStrategy,
implementationContextFactory,
typeValidation,
typePool,
originalType,
classFileLocator,
MethodRebaseResolver.Disabled.INSTANCE);
}
/**
* Creates a type writer for rebasing a type.
*
* @param methodRegistry The compiled method registry to use.
* @param fieldPool The field pool to use.
* @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
* @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
* @param classFileVersion The class file version to use when no explicit class file version is applied.
* @param annotationValueFilterFactory The annotation value filter factory to apply.
* @param annotationRetention The annotation retention to apply.
* @param auxiliaryTypeNamingStrategy The naming strategy for auxiliary types to apply.
* @param implementationContextFactory The implementation context factory to apply.
* @param typeValidation Determines if a type should be explicitly validated.
* @param typePool The type pool to use for computing stack map frames, if required.
* @param originalType The original type that is being redefined or rebased.
* @param classFileLocator The class file locator for locating the original type's class file.
* @param methodRebaseResolver The method rebase resolver to use for rebasing names.
* @param <U> A loaded type that the instrumented type guarantees to subclass.
* @return A suitable type writer.
*/
public static <U> TypeWriter<U> forRebasing(MethodRegistry.Prepared methodRegistry,
FieldPool fieldPool,
TypeAttributeAppender typeAttributeAppender,
AsmVisitorWrapper asmVisitorWrapper,
ClassFileVersion classFileVersion,
AnnotationValueFilter.Factory annotationValueFilterFactory,
AnnotationRetention annotationRetention,
AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
Implementation.Context.Factory implementationContextFactory,
TypeValidation typeValidation,
TypePool typePool,
TypeDescription originalType,
ClassFileLocator classFileLocator,
MethodRebaseResolver methodRebaseResolver) {
return new ForInlining<U>(methodRegistry.getInstrumentedType(),
classFileVersion,
fieldPool,
methodRegistry,
new RebaseImplementationTarget.Factory(methodRebaseResolver),
methodRebaseResolver.getAuxiliaryTypes(),
methodRegistry.getInstrumentedType().getDeclaredFields(),
methodRegistry.getMethods(),
methodRegistry.getInstrumentedMethods(),
methodRegistry.getLoadedTypeInitializer(),
methodRegistry.getTypeInitializer(),
typeAttributeAppender,
asmVisitorWrapper,
annotationValueFilterFactory,
annotationRetention,
auxiliaryTypeNamingStrategy,
implementationContextFactory,
typeValidation,
typePool,
originalType,
classFileLocator,
methodRebaseResolver);
}
@Override
@SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Setting a debugging property should never change the program outcome")
public DynamicType.Unloaded<S> make(TypeResolutionStrategy.Resolved typeResolutionStrategy) {
UnresolvedType unresolvedType = create(typeResolutionStrategy.injectedInto(typeInitializer));
if (DUMP_FOLDER != null) {
try {
AccessController.doPrivileged(new ClassDumpAction(DUMP_FOLDER, instrumentedType, unresolvedType.getBinaryRepresentation()));
} catch (Exception exception) {
Logger.getLogger("net.bytebuddy").log(Level.WARNING, "Could not dump class file for " + instrumentedType, exception);
}
}
return unresolvedType.toDynamicType(typeResolutionStrategy);
}
/**
* Creates an unresolved version of the dynamic type.
*
* @param typeInitializer The type initializer to use.
* @return An unresolved type.
*/
protected abstract UnresolvedType create(TypeInitializer typeInitializer);
/**
* An unresolved type.
*/
protected class UnresolvedType {
/**
* The type's binary representation.
*/
private final byte[] binaryRepresentation;
/**
* A list of auxiliary types for this unresolved type.
*/
private final List<? extends DynamicType> auxiliaryTypes;
/**
* Creates a new unresolved type.
*
* @param binaryRepresentation The type's binary representation.
* @param auxiliaryTypes A list of auxiliary types for this unresolved type.
*/
protected UnresolvedType(byte[] binaryRepresentation, List<? extends DynamicType> auxiliaryTypes) {
this.binaryRepresentation = binaryRepresentation;
this.auxiliaryTypes = auxiliaryTypes;
}
/**
* Resolves this type to a dynamic type.
*
* @param typeResolutionStrategy The type resolution strategy to apply.
* @return A dynamic type representing the inlined type.
*/
protected DynamicType.Unloaded<S> toDynamicType(TypeResolutionStrategy.Resolved typeResolutionStrategy) {
return new DynamicType.Default.Unloaded<S>(instrumentedType,
binaryRepresentation,
loadedTypeInitializer,
CompoundList.of(Default.this.auxiliaryTypes, auxiliaryTypes),
typeResolutionStrategy);
}
/**
* Returns the binary representation of this unresolved type.
*
* @return The binary representation of this unresolved type.
*/
protected byte[] getBinaryRepresentation() {
return binaryRepresentation;
}
/**
* Returns the outer instance.
*
* @return The outer instance.
*/
private Default getOuter() {
return Default.this;
}
@Override // HE: Remove when Lombok support for getOuter is added.
@SuppressWarnings("unchecked")
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
UnresolvedType that = (UnresolvedType) object; // Java 6 compilers cannot cast to a nested wildcard.
return Arrays.equals(binaryRepresentation, that.binaryRepresentation)
&& Default.this.equals(that.getOuter())
&& auxiliaryTypes.equals(that.auxiliaryTypes);
}
@Override // HE: Remove when Lombok support for getOuter is added.
public int hashCode() {
int result = Arrays.hashCode(binaryRepresentation);
result = 31 * result + auxiliaryTypes.hashCode();
result = 31 * result + Default.this.hashCode();
return result;
}
}
/**
* A class validator that validates that a class only defines members that are appropriate for the sort of the generated class.
*/
protected static class ValidatingClassVisitor extends ClassVisitor {
/**
* Indicates that a method has no method parameters.
*/
private static final String NO_PARAMETERS = "()";
/**
* Indicates that a method returns void.
*/
private static final String RETURNS_VOID = "V";
/**
* The descriptor of the {@link String} type.
*/
private static final String STRING_DESCRIPTOR = "Ljava/lang/String;";
/**
* Indicates that a field is ignored.
*/
private static final FieldVisitor IGNORE_FIELD = null;
/**
* Indicates that a method is ignored.
*/
private static final MethodVisitor IGNORE_METHOD = null;
/**
* The constraint to assert the members against. The constraint is first defined when the general class information is visited.
*/
private Constraint constraint;
/**
* Creates a validating class visitor.
*
* @param classVisitor The class visitor to which any calls are delegated to.
*/
protected ValidatingClassVisitor(ClassVisitor classVisitor) {
super(Opcodes.ASM5, classVisitor);
}
/**
* Adds a validating visitor if type validation is enabled.
*
* @param classVisitor The original class visitor.
* @param typeValidation The type validation state.
* @return A class visitor that applies type validation if this is required.
*/
protected static ClassVisitor of(ClassVisitor classVisitor, TypeValidation typeValidation) {
return typeValidation.isEnabled()
? new ValidatingClassVisitor(classVisitor)
: classVisitor;
}
@Override
public void visit(int version, int modifiers, String name, String signature, String superName, String[] interfaces) {
ClassFileVersion classFileVersion = ClassFileVersion.ofMinorMajor(version);
List<Constraint> constraints = new ArrayList<Constraint>();
constraints.add(new Constraint.ForClassFileVersion(classFileVersion));
if (name.endsWith('/' + PackageDescription.PACKAGE_CLASS_NAME)) {
constraints.add(Constraint.ForPackageType.INSTANCE);
} else if ((modifiers & Opcodes.ACC_ANNOTATION) != 0) {
if (!classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)) {
throw new IllegalStateException("Cannot define an annotation type for class file version " + classFileVersion);
}
constraints.add(classFileVersion.isAtLeast(ClassFileVersion.JAVA_V8)
? Constraint.ForAnnotation.JAVA_8
: Constraint.ForAnnotation.CLASSIC);
} else if ((modifiers & Opcodes.ACC_INTERFACE) != 0) {
constraints.add(classFileVersion.isAtLeast(ClassFileVersion.JAVA_V8)
? Constraint.ForInterface.JAVA_8
: Constraint.ForInterface.CLASSIC);
} else if ((modifiers & Opcodes.ACC_ABSTRACT) != 0) {
constraints.add(Constraint.ForClass.ABSTRACT);
} else {
constraints.add(Constraint.ForClass.MANIFEST);
}
constraint = new Constraint.Compound(constraints);
constraint.assertType(modifiers, interfaces != null, signature != null);
super.visit(version, modifiers, name, signature, superName, interfaces);
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
constraint.assertAnnotation();
return super.visitAnnotation(descriptor, visible);
}
@Override
public AnnotationVisitor visitTypeAnnotation(int typeReference, TypePath typePath, String descriptor, boolean visible) {
constraint.assertTypeAnnotation();
return super.visitTypeAnnotation(typeReference, typePath, descriptor, visible);
}
@Override
public FieldVisitor visitField(int modifiers, String name, String descriptor, String signature, Object defaultValue) {
if (defaultValue != null) {
Class<?> type;
switch (descriptor.charAt(0)) {
case 'Z':
case 'B':
case 'C':
case 'S':
case 'I':
type = Integer.class;
break;
case 'J':
type = Long.class;
break;
case 'F':
type = Float.class;
break;
case 'D':
type = Double.class;
break;
default:
if (!descriptor.equals(STRING_DESCRIPTOR)) {
throw new IllegalStateException("Cannot define a default value for type of field " + name);
}
type = String.class;
}
if (!type.isInstance(defaultValue)) {
throw new IllegalStateException("Field " + name + " defines an incompatible default value " + defaultValue);
} else if (type == Integer.class) {
int minimum, maximum;
switch (descriptor.charAt(0)) {
case 'Z':
minimum = 0;
maximum = 1;
break;
case 'B':
minimum = Byte.MIN_VALUE;
maximum = Byte.MAX_VALUE;
break;
case 'C':
minimum = Character.MIN_VALUE;
maximum = Character.MAX_VALUE;
break;
case 'S':
minimum = Short.MIN_VALUE;
maximum = Short.MAX_VALUE;
break;
default:
minimum = Integer.MIN_VALUE;
maximum = Integer.MAX_VALUE;
}
int value = (Integer) defaultValue;
if (value < minimum || value > maximum) {
throw new IllegalStateException("Field " + name + " defines an incompatible default value " + defaultValue);
}
}
}
constraint.assertField(name,
(modifiers & Opcodes.ACC_PUBLIC) != 0,
(modifiers & Opcodes.ACC_STATIC) != 0,
(modifiers & Opcodes.ACC_FINAL) != 0,
signature != null);
FieldVisitor fieldVisitor = super.visitField(modifiers, name, descriptor, signature, defaultValue);
return fieldVisitor == null
? IGNORE_FIELD
: new ValidatingFieldVisitor(fieldVisitor);
}
@Override
public MethodVisitor visitMethod(int modifiers, String name, String descriptor, String signature, String[] exceptions) {
constraint.assertMethod(name,
(modifiers & Opcodes.ACC_ABSTRACT) != 0,
(modifiers & Opcodes.ACC_PUBLIC) != 0,
(modifiers & Opcodes.ACC_PRIVATE) != 0,
(modifiers & Opcodes.ACC_STATIC) != 0,
!name.equals(MethodDescription.CONSTRUCTOR_INTERNAL_NAME)
&& !name.equals(MethodDescription.TYPE_INITIALIZER_INTERNAL_NAME)
&& (modifiers & (Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC)) == 0,
name.equals(MethodDescription.CONSTRUCTOR_INTERNAL_NAME),
!descriptor.startsWith(NO_PARAMETERS) || descriptor.endsWith(RETURNS_VOID),
signature != null);
MethodVisitor methodVisitor = super.visitMethod(modifiers, name, descriptor, signature, exceptions);
return methodVisitor == null
? IGNORE_METHOD
: new ValidatingMethodVisitor(methodVisitor, name);
}
/**
* A constraint for members that are legal for a given type.
*/
protected interface Constraint {
/**
* Asserts if the type can legally represent a package description.
*
* @param modifier The modifier that is to be written to the type.
* @param definesInterfaces {@code true} if this type implements at least one interface.
* @param isGeneric {@code true} if this type defines a generic type signature.
*/
void assertType(int modifier, boolean definesInterfaces, boolean isGeneric);
/**
* Asserts a field for being valid.
*
* @param name The name of the field.
* @param isPublic {@code true} if this field is public.
* @param isStatic {@code true} if this field is static.
* @param isFinal {@code true} if this field is final.
* @param isGeneric {@code true} if this field defines a generic signature.
*/
void assertField(String name, boolean isPublic, boolean isStatic, boolean isFinal, boolean isGeneric);
/**
* Asserts a method for being valid.
*
* @param name The name of the method.
* @param isAbstract {@code true} if the method is abstract.
* @param isPublic {@code true} if this method is public.
* @param isPrivate {@code true} if this method is private.
* @param isStatic {@code true} if this method is static.
* @param isVirtual {@code true} if this method is virtual.
* @param isConstructor {@code true} if this method is a constructor.
* @param isDefaultValueIncompatible {@code true} if a method's signature cannot describe an annotation property method.
* @param isGeneric {@code true} if this method defines a generic signature.
*/
void assertMethod(String name,
boolean isAbstract,
boolean isPublic,
boolean isPrivate,
boolean isStatic,
boolean isVirtual,
boolean isConstructor,
boolean isDefaultValueIncompatible,
boolean isGeneric);
/**
* Asserts the legitimacy of an annotation for the instrumented type.
*/
void assertAnnotation();
/**
* Asserts the legitimacy of a type annotation for the instrumented type.
*/
void assertTypeAnnotation();
/**
* Asserts if a default value is legal for a method.
*
* @param name The name of the method.
*/
void assertDefaultValue(String name);
/**
* Asserts if it is legal to invoke a default method from a type.
*/
void assertDefaultMethodCall();
/**
* Asserts the capability to store a type constant in the class's constant pool.
*/
void assertTypeInConstantPool();
/**
* Asserts the capability to store a method type constant in the class's constant pool.
*/
void assertMethodTypeInConstantPool();
/**
* Asserts the capability to store a method handle in the class's constant pool.
*/
void assertHandleInConstantPool();
/**
* Asserts the capability to invoke a method dynamically.
*/
void assertInvokeDynamic();
/**
* Asserts the capability of executing a subroutine.
*/
void assertSubRoutine();
/**
* Represents the constraint of a class type.
*/
enum ForClass implements Constraint {
/**
* Represents the constraints of a non-abstract class.
*/
MANIFEST(true),
/**
* Represents the constraints of an abstract class.
*/
ABSTRACT(false);
/**
* {@code true} if this instance represents the constraints a non-abstract class.
*/
private final boolean manifestType;
/**
* Creates a new constraint for a class.
*
* @param manifestType {@code true} if this instance represents a non-abstract class.
*/
ForClass(boolean manifestType) {
this.manifestType = manifestType;
}
@Override
public void assertType(int modifier, boolean definesInterfaces, boolean isGeneric) {
/* do nothing */
}
@Override
public void assertField(String name, boolean isPublic, boolean isStatic, boolean isFinal, boolean isGeneric) {
/* do nothing */
}
@Override
public void assertMethod(String name,
boolean isAbstract,
boolean isPublic,
boolean isPrivate,
boolean isStatic,
boolean isVirtual,
boolean isConstructor,
boolean isDefaultValueIncompatible,
boolean isGeneric) {
if (isAbstract && manifestType) {
throw new IllegalStateException("Cannot define abstract method '" + name + "' for non-abstract class");
}
}
@Override
public void assertAnnotation() {
/* do nothing */
}
@Override
public void assertTypeAnnotation() {
/* do nothing */
}
@Override
public void assertDefaultValue(String name) {
throw new IllegalStateException("Cannot define default value for '" + name + "' for non-annotation type");
}
@Override
public void assertDefaultMethodCall() {
/* do nothing */
}
@Override
public void assertTypeInConstantPool() {
/* do nothing */
}
@Override
public void assertMethodTypeInConstantPool() {
/* do nothing */
}
@Override
public void assertHandleInConstantPool() {
/* do nothing */
}
@Override
public void assertInvokeDynamic() {
/* do nothing */
}
@Override
public void assertSubRoutine() {
/* do nothing */
}
}
/**
* Represents the constraint of a package type.
*/
enum ForPackageType implements Constraint {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public void assertField(String name, boolean isPublic, boolean isStatic, boolean isFinal, boolean isGeneric) {
throw new IllegalStateException("Cannot define a field for a package description type");
}
@Override
public void assertMethod(String name,
boolean isAbstract,
boolean isPublic,
boolean isPrivate,
boolean isStatic,
boolean isVirtual,
boolean isConstructor,
boolean isNoDefaultValue,
boolean isGeneric) {
throw new IllegalStateException("Cannot define a method for a package description type");
}
@Override
public void assertAnnotation() {
/* do nothing */
}
@Override
public void assertTypeAnnotation() {
/* do nothing */
}
@Override
public void assertDefaultValue(String name) {
/* do nothing, implicit by forbidding methods */
}
@Override
public void assertDefaultMethodCall() {
/* do nothing */
}
@Override
public void assertTypeInConstantPool() {
/* do nothing */
}
@Override
public void assertMethodTypeInConstantPool() {
/* do nothing */
}
@Override
public void assertHandleInConstantPool() {
/* do nothing */
}
@Override
public void assertInvokeDynamic() {
/* do nothing */
}
@Override
public void assertSubRoutine() {
/* do nothing */
}
@Override
public void assertType(int modifier, boolean definesInterfaces, boolean isGeneric) {
if (modifier != PackageDescription.PACKAGE_MODIFIERS) {
throw new IllegalStateException("A package description type must define " + PackageDescription.PACKAGE_MODIFIERS + " as modifier");
} else if (definesInterfaces) {
throw new IllegalStateException("Cannot implement interface for package type");
}
}
}
/**
* Represents the constraint of an interface type.
*/
enum ForInterface implements Constraint {
/**
* An interface type with the constrains for the Java versions 5 to 7.
*/
CLASSIC(true),
/**
* An interface type with the constrains for the Java versions 8+.
*/
JAVA_8(false);
/**
* {@code true} if this instance represents a classic interface type (pre Java 8).
*/
private final boolean classic;
/**
* Creates a constraint for an interface type.
*
* @param classic {@code true} if this instance represents a classic interface (pre Java 8).
*/
ForInterface(boolean classic) {
this.classic = classic;
}
@Override
public void assertField(String name, boolean isPublic, boolean isStatic, boolean isFinal, boolean isGeneric) {
if (!isStatic || !isPublic || !isFinal) {
throw new IllegalStateException("Cannot only define public, static, final field '" + name + "' for interface type");
}
}
@Override
public void assertMethod(String name,
boolean isAbstract,
boolean isPublic,
boolean isPrivate,
boolean isStatic,
boolean isVirtual,
boolean isConstructor,
boolean isDefaultValueIncompatible,
boolean isGeneric) {
if (!name.equals(MethodDescription.TYPE_INITIALIZER_INTERNAL_NAME)) {
if (isConstructor) {
throw new IllegalStateException("Cannot define constructor for interface type");
} else if (classic && !isPublic) {
throw new IllegalStateException("Cannot define non-public method '" + name + "' for interface type");
} else if (classic && !isVirtual) {
throw new IllegalStateException("Cannot define non-virtual method '" + name + "' for a pre-Java 8 interface type");
} else if (classic && !isAbstract) {
throw new IllegalStateException("Cannot define default method '" + name + "' for pre-Java 8 interface type");
}
}
}
@Override
public void assertAnnotation() {
/* do nothing */
}
@Override
public void assertTypeAnnotation() {
/* do nothing */
}
@Override
public void assertDefaultValue(String name) {
throw new IllegalStateException("Cannot define default value for '" + name + "' for non-annotation type");
}
@Override
public void assertDefaultMethodCall() {
/* do nothing */
}
@Override
public void assertType(int modifier, boolean definesInterfaces, boolean isGeneric) {
/* do nothing */
}
@Override
public void assertTypeInConstantPool() {
/* do nothing */
}
@Override
public void assertMethodTypeInConstantPool() {
/* do nothing */
}
@Override
public void assertHandleInConstantPool() {
/* do nothing */
}
@Override
public void assertInvokeDynamic() {
/* do nothing */
}
@Override
public void assertSubRoutine() {
/* do nothing */
}
}
/**
* Represents the constraint of an annotation type.
*/
enum ForAnnotation implements Constraint {
/**
* An annotation type with the constrains for the Java versions 5 to 7.
*/
CLASSIC(true),
/**
* An annotation type with the constrains for the Java versions 8+.
*/
JAVA_8(false);
/**
* {@code true} if this instance represents a classic annotation type (pre Java 8).
*/
private final boolean classic;
/**
* Creates a constraint for an annotation type.
*
* @param classic {@code true} if this instance represents a classic annotation type (pre Java 8).
*/
ForAnnotation(boolean classic) {
this.classic = classic;
}
@Override
public void assertField(String name, boolean isPublic, boolean isStatic, boolean isFinal, boolean isGeneric) {
if (!isStatic || !isPublic || !isFinal) {
throw new IllegalStateException("Cannot only define public, static, final field '" + name + "' for interface type");
}
}
@Override
public void assertMethod(String name,
boolean isAbstract,
boolean isPublic,
boolean isPrivate,
boolean isStatic,
boolean isVirtual,
boolean isConstructor,
boolean isDefaultValueIncompatible,
boolean isGeneric) {
if (!name.equals(MethodDescription.TYPE_INITIALIZER_INTERNAL_NAME)) {
if (isConstructor) {
throw new IllegalStateException("Cannot define constructor for interface type");
} else if (classic && !isVirtual) {
throw new IllegalStateException("Cannot define non-virtual method '" + name + "' for a pre-Java 8 annotation type");
} else if (!isStatic && isDefaultValueIncompatible) {
throw new IllegalStateException("Cannot define method '" + name + "' with the given signature as an annotation type method");
}
}
}
@Override
public void assertAnnotation() {
/* do nothing */
}
@Override
public void assertTypeAnnotation() {
/* do nothing */
}
@Override
public void assertDefaultValue(String name) {
/* do nothing */
}
@Override
public void assertDefaultMethodCall() {
/* do nothing */
}
@Override
public void assertType(int modifier, boolean definesInterfaces, boolean isGeneric) {
if ((modifier & Opcodes.ACC_INTERFACE) == 0) {
throw new IllegalStateException("Cannot define annotation type without interface modifier");
}
}
@Override
public void assertTypeInConstantPool() {
/* do nothing */
}
@Override
public void assertMethodTypeInConstantPool() {
/* do nothing */
}
@Override
public void assertHandleInConstantPool() {
/* do nothing */
}
@Override
public void assertInvokeDynamic() {
/* do nothing */
}
@Override
public void assertSubRoutine() {
/* do nothing */
}
}
/**
* Represents the constraint implied by a class file version.
*/
@EqualsAndHashCode
class ForClassFileVersion implements Constraint {
/**
* The enforced class file version.
*/
private final ClassFileVersion classFileVersion;
/**
* Creates a new constraint for the given class file version.
*
* @param classFileVersion The enforced class file version.
*/
protected ForClassFileVersion(ClassFileVersion classFileVersion) {
this.classFileVersion = classFileVersion;
}
@Override
public void assertType(int modifiers, boolean definesInterfaces, boolean isGeneric) {
if ((modifiers & Opcodes.ACC_ANNOTATION) != 0 && !classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)) {
throw new IllegalStateException("Cannot define annotation type for class file version " + classFileVersion);
} else if (isGeneric && !classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)) {
throw new IllegalStateException("Cannot define a generic type for class file version " + classFileVersion);
}
}
@Override
public void assertField(String name, boolean isPublic, boolean isStatic, boolean isFinal, boolean isGeneric) {
if (isGeneric && !classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)) {
throw new IllegalStateException("Cannot define generic field '" + name + "' for class file version " + classFileVersion);
}
}
@Override
public void assertMethod(String name,
boolean isAbstract,
boolean isPublic,
boolean isPrivate,
boolean isStatic,
boolean isVirtual,
boolean isConstructor,
boolean isDefaultValueIncompatible,
boolean isGeneric) {
if (isGeneric && !classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)) {
throw new IllegalStateException("Cannot define generic method '" + name + "' for class file version " + classFileVersion);
} else if (!isVirtual && isAbstract) {
throw new IllegalStateException("Cannot define static or non-virtual method '" + name + "' to be abstract");
}
}
@Override
public void assertAnnotation() {
if (!classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)) {
throw new IllegalStateException("Cannot write annotations for class file version " + classFileVersion);
}
}
@Override
public void assertTypeAnnotation() {
if (!classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)) {
throw new IllegalStateException("Cannot write type annotations for class file version " + classFileVersion);
}
}
@Override
public void assertDefaultValue(String name) {
/* do nothing, implicitly checked by type assertion */
}
@Override
public void assertDefaultMethodCall() {
if (classFileVersion.isLessThan(ClassFileVersion.JAVA_V8)) {
throw new IllegalStateException("Cannot invoke default method for class file version " + classFileVersion);
}
}
@Override
public void assertTypeInConstantPool() {
if (!classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)) {
throw new IllegalStateException("Cannot write type to constant pool for class file version " + classFileVersion);
}
}
@Override
public void assertMethodTypeInConstantPool() {
if (!classFileVersion.isAtLeast(ClassFileVersion.JAVA_V7)) {
throw new IllegalStateException("Cannot write method type to constant pool for class file version " + classFileVersion);
}
}
@Override
public void assertHandleInConstantPool() {
if (!classFileVersion.isAtLeast(ClassFileVersion.JAVA_V7)) {
throw new IllegalStateException("Cannot write method handle to constant pool for class file version " + classFileVersion);
}
}
@Override
public void assertInvokeDynamic() {
if (!classFileVersion.isAtLeast(ClassFileVersion.JAVA_V7)) {
throw new IllegalStateException("Cannot write invoke dynamic instruction for class file version " + classFileVersion);
}
}
@Override
public void assertSubRoutine() {
if (!classFileVersion.isLessThan(ClassFileVersion.JAVA_V6)) {
throw new IllegalStateException("Cannot write subroutine for class file version " + classFileVersion);
}
}
}
/**
* A constraint implementation that summarizes several constraints.
*/
@EqualsAndHashCode
class Compound implements Constraint {
/**
* A list of constraints that is enforced in the given order.
*/
private final List<Constraint> constraints;
/**
* Creates a new compound constraint.
*
* @param constraints A list of constraints that is enforced in the given order.
*/
public Compound(List<? extends Constraint> constraints) {
this.constraints = new ArrayList<Constraint>();
for (Constraint constraint : constraints) {
if (constraint instanceof Compound) {
this.constraints.addAll(((Compound) constraint).constraints);
} else {
this.constraints.add(constraint);
}
}
}
@Override
public void assertType(int modifier, boolean definesInterfaces, boolean isGeneric) {
for (Constraint constraint : constraints) {
constraint.assertType(modifier, definesInterfaces, isGeneric);
}
}
@Override
public void assertField(String name, boolean isPublic, boolean isStatic, boolean isFinal, boolean isGeneric) {
for (Constraint constraint : constraints) {
constraint.assertField(name, isPublic, isStatic, isFinal, isGeneric);
}
}
@Override
public void assertMethod(String name,
boolean isAbstract,
boolean isPublic,
boolean isPrivate,
boolean isStatic,
boolean isVirtual,
boolean isConstructor,
boolean isDefaultValueIncompatible,
boolean isGeneric) {
for (Constraint constraint : constraints) {
constraint.assertMethod(name,
isAbstract,
isPublic,
isPrivate,
isStatic,
isVirtual,
isConstructor,
isDefaultValueIncompatible,
isGeneric);
}
}
@Override
public void assertDefaultValue(String name) {
for (Constraint constraint : constraints) {
constraint.assertDefaultValue(name);
}
}
@Override
public void assertDefaultMethodCall() {
for (Constraint constraint : constraints) {
constraint.assertDefaultMethodCall();
}
}
@Override
public void assertAnnotation() {
for (Constraint constraint : constraints) {
constraint.assertAnnotation();
}
}
@Override
public void assertTypeAnnotation() {
for (Constraint constraint : constraints) {
constraint.assertTypeAnnotation();
}
}
@Override
public void assertTypeInConstantPool() {
for (Constraint constraint : constraints) {
constraint.assertTypeInConstantPool();
}
}
@Override
public void assertMethodTypeInConstantPool() {
for (Constraint constraint : constraints) {
constraint.assertMethodTypeInConstantPool();
}
}
@Override
public void assertHandleInConstantPool() {
for (Constraint constraint : constraints) {
constraint.assertHandleInConstantPool();
}
}
@Override
public void assertInvokeDynamic() {
for (Constraint constraint : constraints) {
constraint.assertInvokeDynamic();
}
}
@Override
public void assertSubRoutine() {
for (Constraint constraint : constraints) {
constraint.assertSubRoutine();
}
}
}
}
/**
* A field validator for checking default values.
*/
protected class ValidatingFieldVisitor extends FieldVisitor {
/**
* Creates a validating field visitor.
*
* @param fieldVisitor The field visitor to which any calls are delegated to.
*/
protected ValidatingFieldVisitor(FieldVisitor fieldVisitor) {
super(Opcodes.ASM5, fieldVisitor);
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
constraint.assertAnnotation();
return super.visitAnnotation(desc, visible);
}
}
/**
* A method validator for checking default values.
*/
protected class ValidatingMethodVisitor extends MethodVisitor {
/**
* The name of the method being visited.
*/
private final String name;
/**
* Creates a validating method visitor.
*
* @param methodVisitor The method visitor to which any calls are delegated to.
* @param name The name of the method being visited.
*/
protected ValidatingMethodVisitor(MethodVisitor methodVisitor, String name) {
super(Opcodes.ASM5, methodVisitor);
this.name = name;
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
constraint.assertAnnotation();
return super.visitAnnotation(desc, visible);
}
@Override
public AnnotationVisitor visitAnnotationDefault() {
constraint.assertDefaultValue(name);
return super.visitAnnotationDefault();
}
@Override
@SuppressFBWarnings(value = "SF_SWITCH_NO_DEFAULT", justification = "Fall through to default case is intentional")
public void visitLdcInsn(Object constant) {
if (constant instanceof Type) {
Type type = (Type) constant;
switch (type.getSort()) {
case Type.OBJECT:
case Type.ARRAY:
constraint.assertTypeInConstantPool();
break;
case Type.METHOD:
constraint.assertMethodTypeInConstantPool();
break;
}
} else if (constant instanceof Handle) {
constraint.assertHandleInConstantPool();
}
super.visitLdcInsn(constant);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
if (isInterface && opcode == Opcodes.INVOKESPECIAL) {
constraint.assertDefaultMethodCall();
}
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
@Override
public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethod, Object... bootstrapArgument) {
constraint.assertInvokeDynamic();
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethod, bootstrapArgument);
}
@Override
public void visitJumpInsn(int opcode, Label label) {
if (opcode == Opcodes.JSR) {
constraint.assertSubRoutine();
}
super.visitJumpInsn(opcode, label);
}
}
}
/**
* A class writer that piggy-backs on Byte Buddy's {@link ClassFileLocator} to avoid class loading or look-up errors when redefining a class.
* This is not available when creating a new class where automatic frame computation is however not normally a requirement.
*/
protected static class FrameComputingClassWriter extends ClassWriter {
/**
* The type pool to use for computing stack map frames, if required.
*/
private final TypePool typePool;
/**
* Creates a new frame computing class writer.
*
* @param flags The flags to be handed to the writer.
* @param typePool The type pool to use for computing stack map frames, if required.
*/
protected FrameComputingClassWriter(int flags, TypePool typePool) {
super(flags);
this.typePool = typePool;
}
/**
* Creates a new frame computing class writer.
*
* @param classReader The class reader from which the original class is read.
* @param flags The flags to be handed to the writer.
* @param typePool The type pool to use for computing stack map frames, if required.
*/
protected FrameComputingClassWriter(ClassReader classReader, int flags, TypePool typePool) {
super(classReader, flags);
this.typePool = typePool;
}
@Override
protected String getCommonSuperClass(String leftTypeName, String rightTypeName) {
TypeDescription leftType = typePool.describe(leftTypeName.replace('/', '.')).resolve();
TypeDescription rightType = typePool.describe(rightTypeName.replace('/', '.')).resolve();
if (leftType.isAssignableFrom(rightType)) {
return leftType.getInternalName();
} else if (leftType.isAssignableTo(rightType)) {
return rightType.getInternalName();
} else if (leftType.isInterface() || rightType.isInterface()) {
return TypeDescription.OBJECT.getInternalName();
} else {
do {
leftType = leftType.getSuperClass().asErasure();
} while (!leftType.isAssignableFrom(rightType));
return leftType.getInternalName();
}
}
}
/**
* A type writer that inlines the created type into an existing class file.
*
* @param <U> The best known loaded type for the dynamically created type.
*/
@EqualsAndHashCode(callSuper = true)
public static class ForInlining<U> extends Default<U> {
/**
* Indicates that a type does not define a super type in its class file, i.e. the {@link Object} type.
*/
private static final String NO_SUPER_TYPE = null;
/**
* Indicates that a field should be ignored.
*/
private static final FieldVisitor IGNORE_FIELD = null;
/**
* Indicates that a method should be ignored.
*/
private static final MethodVisitor IGNORE_METHOD = null;
/**
* Indicates that an annotation should be ignored.
*/
private static final AnnotationVisitor IGNORE_ANNOTATION = null;
/**
* The method registry to use.
*/
private final MethodRegistry.Prepared methodRegistry;
/**
* The implementation target factory to use.
*/
private final Implementation.Target.Factory implementationTargetFactory;
/**
* The original type that is being redefined or rebased.
*/
private final TypeDescription originalType;
/**
* The class file locator for locating the original type's class file.
*/
private final ClassFileLocator classFileLocator;
/**
* The method rebase resolver to use for rebasing methods.
*/
private final MethodRebaseResolver methodRebaseResolver;
/**
* Creates a new default type writer for creating a new type that is not based on an existing class file.
*
* @param instrumentedType The instrumented type to be created.
* @param classFileVersion The class file version to define auxiliary types in.
* @param fieldPool The field pool to use.
* @param methodRegistry The method registry to use.
* @param implementationTargetFactory The implementation target factory to use.
* @param explicitAuxiliaryTypes The explicit auxiliary types to add to the created type.
* @param fields The instrumented type's declared fields.
* @param methods The instrumented type's declared or virtually inherited methods.
* @param instrumentedMethods The instrumented methods relevant to this type creation.
* @param loadedTypeInitializer The loaded type initializer to apply onto the created type after loading.
* @param typeInitializer The type initializer to include in the created type's type initializer.
* @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
* @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
* @param annotationValueFilterFactory The annotation value filter factory to apply.
* @param annotationRetention The annotation retention to apply.
* @param auxiliaryTypeNamingStrategy The naming strategy for auxiliary types to apply.
* @param implementationContextFactory The implementation context factory to apply.
* @param typeValidation Determines if a type should be explicitly validated.
* @param typePool The type pool to use for computing stack map frames, if required.
* @param originalType The original type that is being redefined or rebased.
* @param classFileLocator The class file locator for locating the original type's class file.
* @param methodRebaseResolver The method rebase resolver to use for rebasing methods.
*/
protected ForInlining(TypeDescription instrumentedType,
ClassFileVersion classFileVersion,
FieldPool fieldPool,
MethodRegistry.Prepared methodRegistry,
Implementation.Target.Factory implementationTargetFactory,
List<DynamicType> explicitAuxiliaryTypes,
FieldList<FieldDescription.InDefinedShape> fields,
MethodList<?> methods,
MethodList<?> instrumentedMethods,
LoadedTypeInitializer loadedTypeInitializer,
TypeInitializer typeInitializer,
TypeAttributeAppender typeAttributeAppender,
AsmVisitorWrapper asmVisitorWrapper,
AnnotationValueFilter.Factory annotationValueFilterFactory,
AnnotationRetention annotationRetention,
AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
Implementation.Context.Factory implementationContextFactory,
TypeValidation typeValidation,
TypePool typePool,
TypeDescription originalType,
ClassFileLocator classFileLocator,
MethodRebaseResolver methodRebaseResolver) {
super(instrumentedType,
classFileVersion,
fieldPool,
explicitAuxiliaryTypes,
fields,
methods,
instrumentedMethods,
loadedTypeInitializer,
typeInitializer,
typeAttributeAppender,
asmVisitorWrapper,
annotationValueFilterFactory,
annotationRetention,
auxiliaryTypeNamingStrategy,
implementationContextFactory,
typeValidation,
typePool);
this.methodRegistry = methodRegistry;
this.implementationTargetFactory = implementationTargetFactory;
this.originalType = originalType;
this.classFileLocator = classFileLocator;
this.methodRebaseResolver = methodRebaseResolver;
}
@Override
protected UnresolvedType create(TypeInitializer typeInitializer) {
try {
int writerFlags = asmVisitorWrapper.mergeWriter(AsmVisitorWrapper.NO_FLAGS);
int readerFlags = asmVisitorWrapper.mergeReader(AsmVisitorWrapper.NO_FLAGS);
ClassReader classReader = new ClassReader(classFileLocator.locate(originalType.getName()).resolve());
ClassWriter classWriter = new FrameComputingClassWriter(classReader, writerFlags, typePool);
ContextRegistry contextRegistry = new ContextRegistry();
classReader.accept(writeTo(ValidatingClassVisitor.of(classWriter, typeValidation),
typeInitializer,
contextRegistry,
writerFlags,
readerFlags), readerFlags);
return new UnresolvedType(classWriter.toByteArray(), contextRegistry.getAuxiliaryTypes());
} catch (IOException exception) {
throw new RuntimeException("The class file could not be written", exception);
}
}
/**
* Creates a class visitor which weaves all changes and additions on the fly.
*
* @param classVisitor The class visitor to which this entry is to be written to.
* @param typeInitializer The type initializer to apply.
* @param contextRegistry A context registry to register the lazily created implementation context to.
* @param writerFlags The writer flags being used.
* @param readerFlags The reader flags being used.
* @return A class visitor which is capable of applying the changes.
*/
private ClassVisitor writeTo(ClassVisitor classVisitor,
TypeInitializer typeInitializer,
ContextRegistry contextRegistry,
int writerFlags,
int readerFlags) {
classVisitor = new RedefinitionClassVisitor(classVisitor, typeInitializer, contextRegistry, writerFlags, readerFlags);
return originalType.getName().equals(instrumentedType.getName())
? classVisitor
: new ClassRemapper(classVisitor, new SimpleRemapper(originalType.getInternalName(), instrumentedType.getInternalName()));
}
/**
* An initialization handler is responsible for handling the creation of the type initializer.
*/
protected interface InitializationHandler {
/**
* Invoked upon completion of writing the instrumented type.
*
* @param classVisitor The class visitor to write any methods to.
* @param implementationContext The implementation context to use.
*/
void complete(ClassVisitor classVisitor, Implementation.Context.ExtractableView implementationContext);
/**
* An initialization handler that creates a new type initializer.
*/
class Creating extends TypeInitializer.Drain.Default implements InitializationHandler {
/**
* Creates a new creating initialization handler.
*
* @param instrumentedType The instrumented type.
* @param methodPool The method pool to use.
* @param annotationValueFilterFactory The annotation value filter factory to use.
*/
protected Creating(TypeDescription instrumentedType,
MethodPool methodPool,
AnnotationValueFilter.Factory annotationValueFilterFactory) {
super(instrumentedType, methodPool, annotationValueFilterFactory);
}
@Override
public void complete(ClassVisitor classVisitor, Implementation.Context.ExtractableView implementationContext) {
implementationContext.drain(this, classVisitor, annotationValueFilterFactory);
}
}
/**
* An initialization handler that appends code to a previously visited type initializer.
*/
abstract class Appending extends MethodVisitor implements InitializationHandler, TypeInitializer.Drain {
/**
* The instrumented type.
*/
protected final TypeDescription instrumentedType;
/**
* The method pool record for the type initializer.
*/
protected final MethodPool.Record record;
/**
* The used annotation value filter factory.
*/
protected final AnnotationValueFilter.Factory annotationValueFilterFactory;
/**
* The frame writer to use.
*/
protected final FrameWriter frameWriter;
/**
* The currently recorded stack size.
*/
protected int stackSize;
/**
* The currently recorded local variable length.
*/
protected int localVariableLength;
/**
* Creates a new appending initialization handler.
*
* @param methodVisitor The underlying method visitor.
* @param instrumentedType The instrumented type.
* @param record The method pool record for the type initializer.
* @param annotationValueFilterFactory The used annotation value filter factory.
* @param requireFrames {@code true} if the visitor is required to add frames.
* @param expandFrames {@code true} if the visitor is required to expand any added frame.
*/
protected Appending(MethodVisitor methodVisitor,
TypeDescription instrumentedType,
MethodPool.Record record,
AnnotationValueFilter.Factory annotationValueFilterFactory,
boolean requireFrames,
boolean expandFrames) {
super(Opcodes.ASM5, methodVisitor);
this.instrumentedType = instrumentedType;
this.record = record;
this.annotationValueFilterFactory = annotationValueFilterFactory;
if (!requireFrames) {
frameWriter = FrameWriter.NoOp.INSTANCE;
} else if (expandFrames) {
frameWriter = FrameWriter.Expanding.INSTANCE;
} else {
frameWriter = new FrameWriter.Active();
}
}
/**
* Resolves an initialization handler.
*
* @param enabled {@code true} if the implementation context is enabled, i.e. any {@link TypeInitializer} might be active.
* @param methodVisitor The delegation method visitor.
* @param instrumentedType The instrumented type.
* @param methodPool The method pool to use.
* @param annotationValueFilterFactory The annotation value filter factory to use.
* @param requireFrames {@code true} if frames must be computed.
* @param expandFrames {@code true} if frames must be expanded.
* @return An initialization handler which is also guaranteed to be a {@link MethodVisitor}.
*/
protected static InitializationHandler of(boolean enabled,
MethodVisitor methodVisitor,
TypeDescription instrumentedType,
MethodPool methodPool,
AnnotationValueFilter.Factory annotationValueFilterFactory,
boolean requireFrames,
boolean expandFrames) {
return enabled
? withDrain(methodVisitor, instrumentedType, methodPool, annotationValueFilterFactory, requireFrames, expandFrames)
: withoutDrain(methodVisitor, instrumentedType, methodPool, annotationValueFilterFactory, requireFrames, expandFrames);
}
/**
* Resolves an initialization handler with a drain.
*
* @param methodVisitor The delegation method visitor.
* @param instrumentedType The instrumented type.
* @param methodPool The method pool to use.
* @param annotationValueFilterFactory The annotation value filter factory to use.
* @param requireFrames {@code true} if frames must be computed.
* @param expandFrames {@code true} if frames must be expanded.
* @return An initialization handler which is also guaranteed to be a {@link MethodVisitor}.
*/
private static WithDrain withDrain(MethodVisitor methodVisitor,
TypeDescription instrumentedType,
MethodPool methodPool,
AnnotationValueFilter.Factory annotationValueFilterFactory,
boolean requireFrames,
boolean expandFrames) {
MethodPool.Record record = methodPool.target(new MethodDescription.Latent.TypeInitializer(instrumentedType));
return record.getSort().isImplemented()
? new WithDrain.WithActiveRecord(methodVisitor, instrumentedType, record, annotationValueFilterFactory, requireFrames, expandFrames)
: new WithDrain.WithoutActiveRecord(methodVisitor, instrumentedType, record, annotationValueFilterFactory, requireFrames, expandFrames);
}
/**
* Resolves an initialization handler without a drain.
*
* @param methodVisitor The delegation method visitor.
* @param instrumentedType The instrumented type.
* @param methodPool The method pool to use.
* @param annotationValueFilterFactory The annotation value filter factory to use.
* @param requireFrames {@code true} if frames must be computed.
* @param expandFrames {@code true} if frames must be expanded.
* @return An initialization handler which is also guaranteed to be a {@link MethodVisitor}.
*/
private static WithoutDrain withoutDrain(MethodVisitor methodVisitor,
TypeDescription instrumentedType,
MethodPool methodPool,
AnnotationValueFilter.Factory annotationValueFilterFactory,
boolean requireFrames,
boolean expandFrames) {
MethodPool.Record record = methodPool.target(new MethodDescription.Latent.TypeInitializer(instrumentedType));
return record.getSort().isImplemented()
? new WithoutDrain.WithActiveRecord(methodVisitor, instrumentedType, record, annotationValueFilterFactory, requireFrames, expandFrames)
: new WithoutDrain.WithoutActiveRecord(methodVisitor, instrumentedType, record, annotationValueFilterFactory);
}
@Override
public void visitCode() {
record.applyAttributes(mv, annotationValueFilterFactory);
super.visitCode();
onStart();
}
/**
* Invoked after the user code was visited.
*/
protected abstract void onStart();
@Override
public void visitFrame(int type, int localVariableLength, Object[] localVariable, int stackSize, Object[] stack) {
super.visitFrame(type, localVariableLength, localVariable, stackSize, stack);
frameWriter.onFrame(type, localVariableLength);
}
@Override
public void visitMaxs(int stackSize, int localVariableLength) {
this.stackSize = stackSize;
this.localVariableLength = localVariableLength;
}
@Override
public void visitEnd() {
onEnd();
}
/**
* Invoked after the user code was completed.
*/
protected abstract void onEnd();
@Override
public void apply(ClassVisitor classVisitor, TypeInitializer typeInitializer, Implementation.Context implementationContext) {
ByteCodeAppender.Size size = typeInitializer.apply(mv, implementationContext, new MethodDescription.Latent.TypeInitializer(instrumentedType));
stackSize = Math.max(stackSize, size.getOperandStackSize());
localVariableLength = Math.max(localVariableLength, size.getLocalVariableSize());
onComplete(implementationContext);
}
/**
* Invoked upon completion of writing the type initializer.
*
* @param implementationContext The implementation context to use.
*/
protected abstract void onComplete(Implementation.Context implementationContext);
@Override
public void complete(ClassVisitor classVisitor, Implementation.Context.ExtractableView implementationContext) {
implementationContext.drain(this, classVisitor, annotationValueFilterFactory);
mv.visitMaxs(stackSize, localVariableLength);
mv.visitEnd();
}
/**
* A frame writer is responsible for adding empty frames on jumo instructions.
*/
protected interface FrameWriter {
/**
* An empty array.
*/
Object[] EMPTY = new Object[0];
/**
* Informs this frame writer of an observed frame.
*
* @param type The frame type.
* @param localVariableLength The length of the local variables array.
*/
void onFrame(int type, int localVariableLength);
/**
* Emits an empty frame.
*
* @param methodVisitor The method visitor to write the frame to.
*/
void emitFrame(MethodVisitor methodVisitor);
/**
* A non-operational frame writer.
*/
enum NoOp implements FrameWriter {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public void onFrame(int type, int localVariableLength) {
/* do nothing */
}
@Override
public void emitFrame(MethodVisitor methodVisitor) {
/* do nothing */
}
}
/**
* A frame writer that creates an expanded frame.
*/
enum Expanding implements FrameWriter {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public void onFrame(int type, int localVariableLength) {
/* do nothing */
}
@Override
public void emitFrame(MethodVisitor methodVisitor) {
methodVisitor.visitFrame(Opcodes.F_NEW, EMPTY.length, EMPTY, EMPTY.length, EMPTY);
}
}
/**
* An active frame writer that creates the most efficient frame.
*/
class Active implements FrameWriter {
/**
* The current length of the current local variable array.
*/
private int currentLocalVariableLength;
@Override
public void onFrame(int type, int localVariableLength) {
switch (type) {
case Opcodes.F_SAME:
case Opcodes.F_SAME1:
break;
case Opcodes.F_APPEND:
currentLocalVariableLength += localVariableLength;
break;
case Opcodes.F_CHOP:
currentLocalVariableLength -= localVariableLength;
break;
case Opcodes.F_NEW:
case Opcodes.F_FULL:
currentLocalVariableLength = localVariableLength;
break;
default:
throw new IllegalStateException("Unexpected frame type: " + type);
}
}
@Override
public void emitFrame(MethodVisitor methodVisitor) {
if (currentLocalVariableLength == 0) {
methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY);
} else if (currentLocalVariableLength > 3) {
methodVisitor.visitFrame(Opcodes.F_FULL, EMPTY.length, EMPTY, EMPTY.length, EMPTY);
} else {
methodVisitor.visitFrame(Opcodes.F_CHOP, currentLocalVariableLength, EMPTY, EMPTY.length, EMPTY);
}
currentLocalVariableLength = 0;
}
}
}
/**
* An initialization handler that appends code to a previously visited type initializer without allowing active
* {@link TypeInitializer} registrations.
*/
protected abstract static class WithoutDrain extends Appending {
/**
* Creates a new appending initialization handler without a drain.
*
* @param methodVisitor The underlying method visitor.
* @param instrumentedType The instrumented type.
* @param record The method pool record for the type initializer.
* @param annotationValueFilterFactory The used annotation value filter factory.
* @param requireFrames {@code true} if the visitor is required to add frames.
* @param expandFrames {@code true} if the visitor is required to expand any added frame.
*/
protected WithoutDrain(MethodVisitor methodVisitor,
TypeDescription instrumentedType,
MethodPool.Record record,
AnnotationValueFilter.Factory annotationValueFilterFactory,
boolean requireFrames,
boolean expandFrames) {
super(methodVisitor, instrumentedType, record, annotationValueFilterFactory, requireFrames, expandFrames);
}
@Override
protected void onStart() {
/* do nothing */
}
@Override
protected void onEnd() {
/* do nothing */
}
/**
* An initialization handler that appends code to a previously visited type initializer without allowing active
* {@link TypeInitializer} registrations and without an active record.
*/
protected static class WithoutActiveRecord extends WithoutDrain {
/**
* Creates a new appending initialization handler without a drain and without an active record.
*
* @param methodVisitor The underlying method visitor.
* @param instrumentedType The instrumented type.
* @param record The method pool record for the type initializer.
* @param annotationValueFilterFactory The used annotation value filter factory.
*/
protected WithoutActiveRecord(MethodVisitor methodVisitor,
TypeDescription instrumentedType,
MethodPool.Record record,
AnnotationValueFilter.Factory annotationValueFilterFactory) {
super(methodVisitor, instrumentedType, record, annotationValueFilterFactory, false, false);
}
@Override
protected void onComplete(Implementation.Context implementationContext) {
/* do nothing */
}
}
/**
* An initialization handler that appends code to a previously visited type initializer without allowing active
* {@link TypeInitializer} registrations and with an active record.
*/
protected static class WithActiveRecord extends WithoutDrain {
/**
* The label that indicates the beginning of the active record.
*/
private final Label label;
/**
* Creates a new appending initialization handler without a drain and with an active record.
*
* @param methodVisitor The underlying method visitor.
* @param instrumentedType The instrumented type.
* @param record The method pool record for the type initializer.
* @param annotationValueFilterFactory The used annotation value filter factory.
* @param requireFrames {@code true} if the visitor is required to add frames.
* @param expandFrames {@code true} if the visitor is required to expand any added frame.
*/
protected WithActiveRecord(MethodVisitor methodVisitor,
TypeDescription instrumentedType,
MethodPool.Record record,
AnnotationValueFilter.Factory annotationValueFilterFactory,
boolean requireFrames,
boolean expandFrames) {
super(methodVisitor, instrumentedType, record, annotationValueFilterFactory, requireFrames, expandFrames);
label = new Label();
}
@Override
public void visitInsn(int opcode) {
if (opcode == Opcodes.RETURN) {
mv.visitJumpInsn(Opcodes.GOTO, label);
} else {
super.visitInsn(opcode);
}
}
@Override
protected void onComplete(Implementation.Context implementationContext) {
mv.visitLabel(label);
frameWriter.emitFrame(mv);
ByteCodeAppender.Size size = record.applyCode(mv, implementationContext);
stackSize = Math.max(stackSize, size.getOperandStackSize());
localVariableLength = Math.max(localVariableLength, size.getLocalVariableSize());
}
}
}
/**
* An initialization handler that appends code to a previously visited type initializer with allowing active
* {@link TypeInitializer} registrations.
*/
protected abstract static class WithDrain extends Appending {
/**
* A label marking the beginning of the appended code.
*/
protected final Label appended;
/**
* A label marking the beginning og the original type initializer's code.
*/
protected final Label original;
/**
* Creates a new appending initialization handler with a drain.
*
* @param methodVisitor The underlying method visitor.
* @param instrumentedType The instrumented type.
* @param record The method pool record for the type initializer.
* @param annotationValueFilterFactory The used annotation value filter factory.
* @param requireFrames {@code true} if the visitor is required to add frames.
* @param expandFrames {@code true} if the visitor is required to expand any added frame.
*/
protected WithDrain(MethodVisitor methodVisitor,
TypeDescription instrumentedType,
MethodPool.Record record,
AnnotationValueFilter.Factory annotationValueFilterFactory,
boolean requireFrames,
boolean expandFrames) {
super(methodVisitor, instrumentedType, record, annotationValueFilterFactory, requireFrames, expandFrames);
appended = new Label();
original = new Label();
}
@Override
protected void onStart() {
mv.visitJumpInsn(Opcodes.GOTO, appended);
mv.visitLabel(original);
frameWriter.emitFrame(mv);
}
@Override
protected void onEnd() {
mv.visitLabel(appended);
frameWriter.emitFrame(mv);
}
@Override
protected void onComplete(Implementation.Context implementationContext) {
mv.visitJumpInsn(Opcodes.GOTO, original);
afterComplete(implementationContext);
}
/**
* Invoked after completion of writing the type initializer.
*
* @param implementationContext The implementation context to use.
*/
protected abstract void afterComplete(Implementation.Context implementationContext);
/**
* A code appending initialization handler with a drain that does not apply an explicit record.
*/
protected static class WithoutActiveRecord extends WithDrain {
/**
* Creates a new appending initialization handler with a drain and without an active record.
*
* @param methodVisitor The underlying method visitor.
* @param instrumentedType The instrumented type.
* @param record The method pool record for the type initializer.
* @param annotationValueFilterFactory The used annotation value filter factory.
* @param requireFrames {@code true} if the visitor is required to add frames.
* @param expandFrames {@code true} if the visitor is required to expand any added frame.
*/
protected WithoutActiveRecord(MethodVisitor methodVisitor,
TypeDescription instrumentedType,
MethodPool.Record record,
AnnotationValueFilter.Factory annotationValueFilterFactory,
boolean requireFrames,
boolean expandFrames) {
super(methodVisitor, instrumentedType, record, annotationValueFilterFactory, requireFrames, expandFrames);
}
@Override
protected void afterComplete(Implementation.Context implementationContext) {
/* do nothing */
}
}
/**
* A code appending initialization handler with a drain that applies an explicit record.
*/
protected static class WithActiveRecord extends WithDrain {
/**
* A label indicating the beginning of the record's code.
*/
private final Label label;
/**
* Creates a new appending initialization handler with a drain and with an active record.
*
* @param methodVisitor The underlying method visitor.
* @param instrumentedType The instrumented type.
* @param record The method pool record for the type initializer.
* @param annotationValueFilterFactory The used annotation value filter factory.
* @param requireFrames {@code true} if the visitor is required to add frames.
* @param expandFrames {@code true} if the visitor is required to expand any added frame.
*/
protected WithActiveRecord(MethodVisitor methodVisitor,
TypeDescription instrumentedType,
MethodPool.Record record,
AnnotationValueFilter.Factory annotationValueFilterFactory,
boolean requireFrames,
boolean expandFrames) {
super(methodVisitor, instrumentedType, record, annotationValueFilterFactory, requireFrames, expandFrames);
label = new Label();
}
@Override
public void visitInsn(int opcode) {
if (opcode == Opcodes.RETURN) {
mv.visitJumpInsn(Opcodes.GOTO, label);
} else {
super.visitInsn(opcode);
}
}
@Override
protected void afterComplete(Implementation.Context implementationContext) {
mv.visitLabel(label);
frameWriter.emitFrame(mv);
ByteCodeAppender.Size size = record.applyCode(mv, implementationContext);
stackSize = Math.max(stackSize, size.getOperandStackSize());
localVariableLength = Math.max(localVariableLength, size.getLocalVariableSize());
}
}
}
}
}
/**
* A context registry allows to extract auxiliary types from a lazily created implementation context.
*/
protected static class ContextRegistry {
/**
* The implementation context that is used for creating a class or {@code null} if it was not registered.
*/
private Implementation.Context.ExtractableView implementationContext;
/**
* Registers the implementation context.
*
* @param implementationContext The implementation context.
*/
public void setImplementationContext(Implementation.Context.ExtractableView implementationContext) {
this.implementationContext = implementationContext;
}
/**
* Returns the auxiliary types that were registered during class creation. This method must only be called after
* a class was created.
*
* @return The auxiliary types that were registered during class creation
*/
@SuppressFBWarnings(value = "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", justification = "Lazy value definition is intended")
public List<DynamicType> getAuxiliaryTypes() {
return implementationContext.getAuxiliaryTypes();
}
}
/**
* A class visitor which is capable of applying a redefinition of an existing class file.
*/
@SuppressFBWarnings(value = "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", justification = "Field access order is implied by ASM")
protected class RedefinitionClassVisitor extends ClassVisitor {
/**
* The type initializer to apply.
*/
private final TypeInitializer typeInitializer;
/**
* A context registry to register the lazily created implementation context to.
*/
private final ContextRegistry contextRegistry;
/**
* The writer flags being used.
*/
private final int writerFlags;
/**
* The reader flags being used.
*/
private final int readerFlags;
/**
* A mapping of fields to write by their names.
*/
private final LinkedHashMap<String, FieldDescription> declarableFields;
/**
* A mapping of methods to write by a concatenation of internal name and descriptor.
*/
private final LinkedHashMap<String, MethodDescription> declarableMethods;
/**
* The method pool to use or {@code null} if the pool was not yet initialized.
*/
private MethodPool methodPool;
/**
* The initialization handler to use or {@code null} if the handler was not yet initialized.
*/
private InitializationHandler initializationHandler;
/**
* The implementation context for this class creation or {@code null} if it was not yet created.
*/
private Implementation.Context.ExtractableView implementationContext;
/**
* Creates a class visitor which is capable of redefining an existent class on the fly.
*
* @param classVisitor The underlying class visitor to which writes are delegated.
* @param typeInitializer The type initializer to apply.
* @param contextRegistry A context registry to register the lazily created implementation context to.
* @param writerFlags The writer flags being used.
* @param readerFlags The reader flags being used.
*/
protected RedefinitionClassVisitor(ClassVisitor classVisitor,
TypeInitializer typeInitializer,
ContextRegistry contextRegistry,
int writerFlags,
int readerFlags) {
super(Opcodes.ASM5, classVisitor);
this.typeInitializer = typeInitializer;
this.contextRegistry = contextRegistry;
this.writerFlags = writerFlags;
this.readerFlags = readerFlags;
declarableFields = new LinkedHashMap<String, FieldDescription>();
for (FieldDescription fieldDescription : fields) {
declarableFields.put(fieldDescription.getInternalName() + fieldDescription.getDescriptor(), fieldDescription);
}
declarableMethods = new LinkedHashMap<String, MethodDescription>();
for (MethodDescription methodDescription : instrumentedMethods) {
declarableMethods.put(methodDescription.getInternalName() + methodDescription.getDescriptor(), methodDescription);
}
}
@Override
public void visit(int classFileVersionNumber,
int modifiers,
String internalName,
String genericSignature,
String superClassInternalName,
String[] interfaceTypeInternalName) {
ClassFileVersion classFileVersion = ClassFileVersion.ofMinorMajor(classFileVersionNumber);
methodPool = methodRegistry.compile(implementationTargetFactory, classFileVersion);
initializationHandler = new InitializationHandler.Creating(instrumentedType, methodPool, annotationValueFilterFactory);
implementationContext = implementationContextFactory.make(instrumentedType,
auxiliaryTypeNamingStrategy,
typeInitializer,
classFileVersion,
ForInlining.this.classFileVersion);
contextRegistry.setImplementationContext(implementationContext);
cv = asmVisitorWrapper.wrap(instrumentedType,
cv,
implementationContext,
typePool,
fields,
methods,
writerFlags,
readerFlags);
super.visit(classFileVersionNumber,
instrumentedType.getActualModifiers((modifiers & Opcodes.ACC_SUPER) != 0 && !instrumentedType.isInterface())
// Anonymous types might not preserve their class file's final modifier via their inner class modifier.
| (((modifiers & Opcodes.ACC_FINAL) != 0 && instrumentedType.isAnonymousClass()) ? Opcodes.ACC_FINAL : 0),
instrumentedType.getInternalName(),
instrumentedType.getGenericSignature(),
instrumentedType.getSuperClass() == null
? (instrumentedType.isInterface() ? TypeDescription.OBJECT.getInternalName() : NO_SUPER_TYPE)
: instrumentedType.getSuperClass().asErasure().getInternalName(),
instrumentedType.getInterfaces().asErasures().toInternalNames());
typeAttributeAppender.apply(cv, instrumentedType, annotationValueFilterFactory.on(instrumentedType));
}
@Override
public void visitInnerClass(String internalName, String outerName, String innerName, int modifiers) {
if (internalName.equals(instrumentedType.getInternalName())) {
modifiers = instrumentedType.getModifiers();
}
super.visitInnerClass(internalName, outerName, innerName, modifiers);
}
@Override
public AnnotationVisitor visitTypeAnnotation(int typeReference, TypePath typePath, String descriptor, boolean visible) {
return annotationRetention.isEnabled()
? super.visitTypeAnnotation(typeReference, typePath, descriptor, visible)
: IGNORE_ANNOTATION;
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
return annotationRetention.isEnabled()
? super.visitAnnotation(descriptor, visible)
: IGNORE_ANNOTATION;
}
@Override
public FieldVisitor visitField(int modifiers,
String internalName,
String descriptor,
String genericSignature,
Object defaultValue) {
FieldDescription fieldDescription = declarableFields.remove(internalName + descriptor);
if (fieldDescription != null) {
FieldPool.Record record = fieldPool.target(fieldDescription);
if (!record.isImplicit()) {
return redefine(record, defaultValue);
}
}
return super.visitField(modifiers, internalName, descriptor, genericSignature, defaultValue);
}
/**
* Redefines a field using the given explicit field pool record and default value.
*
* @param record The field pool value to apply during visitation of the existing field.
* @param defaultValue The default value to write onto the field which might be {@code null}.
* @return A field visitor for visiting the existing field definition.
*/
protected FieldVisitor redefine(FieldPool.Record record, Object defaultValue) {
FieldDescription instrumentedField = record.getField();
FieldVisitor fieldVisitor = super.visitField(instrumentedField.getActualModifiers(),
instrumentedField.getInternalName(),
instrumentedField.getDescriptor(),
instrumentedField.getGenericSignature(),
record.resolveDefault(defaultValue));
return fieldVisitor == null
? IGNORE_FIELD
: new AttributeObtainingFieldVisitor(fieldVisitor, record);
}
@Override
public MethodVisitor visitMethod(int modifiers,
String internalName,
String descriptor,
String genericSignature,
String[] exceptionName) {
if (internalName.equals(MethodDescription.TYPE_INITIALIZER_INTERNAL_NAME)) {
MethodVisitor methodVisitor = super.visitMethod(modifiers, internalName, descriptor, genericSignature, exceptionName);
return methodVisitor == null
? IGNORE_METHOD
: (MethodVisitor) (initializationHandler = InitializationHandler.Appending.of(implementationContext.isEnabled(),
methodVisitor,
instrumentedType,
methodPool,
annotationValueFilterFactory,
(writerFlags & ClassWriter.COMPUTE_FRAMES) == 0 && implementationContext.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V6),
(readerFlags & ClassReader.EXPAND_FRAMES) != 0));
} else {
MethodDescription methodDescription = declarableMethods.remove(internalName + descriptor);
return methodDescription == null
? super.visitMethod(modifiers, internalName, descriptor, genericSignature, exceptionName)
: redefine(methodDescription, (modifiers & Opcodes.ACC_ABSTRACT) != 0);
}
}
/**
* Redefines a given method if this is required by looking up a potential implementation from the
* {@link net.bytebuddy.dynamic.scaffold.TypeWriter.MethodPool}.
*
* @param methodDescription The method being considered for redefinition.
* @param abstractOrigin {@code true} if the original method is abstract, i.e. there is no implementation
* to preserve.
* @return A method visitor which is capable of consuming the original method.
*/
protected MethodVisitor redefine(MethodDescription methodDescription, boolean abstractOrigin) {
MethodPool.Record record = methodPool.target(methodDescription);
if (!record.getSort().isDefined()) {
return super.visitMethod(methodDescription.getActualModifiers(),
methodDescription.getInternalName(),
methodDescription.getDescriptor(),
methodDescription.getGenericSignature(),
methodDescription.getExceptionTypes().asErasures().toInternalNames());
}
MethodDescription implementedMethod = record.getMethod();
MethodVisitor methodVisitor = super.visitMethod(ModifierContributor.Resolver.of(Collections.singleton(record.getVisibility()))
.resolve(implementedMethod.getActualModifiers(record.getSort().isImplemented())),
implementedMethod.getInternalName(),
implementedMethod.getDescriptor(),
implementedMethod.getGenericSignature(),
implementedMethod.getExceptionTypes().asErasures().toInternalNames());
if (methodVisitor == null) {
return IGNORE_METHOD;
} else if (abstractOrigin) {
return new AttributeObtainingMethodVisitor(methodVisitor, record);
} else if (methodDescription.isNative()) {
MethodRebaseResolver.Resolution resolution = methodRebaseResolver.resolve(implementedMethod.asDefined());
if (resolution.isRebased()) {
MethodVisitor rebasedMethodVisitor = super.visitMethod(resolution.getResolvedMethod().getActualModifiers(),
resolution.getResolvedMethod().getInternalName(),
resolution.getResolvedMethod().getDescriptor(),
resolution.getResolvedMethod().getGenericSignature(),
resolution.getResolvedMethod().getExceptionTypes().asErasures().toInternalNames());
if (rebasedMethodVisitor != null) {
rebasedMethodVisitor.visitEnd();
}
}
return new AttributeObtainingMethodVisitor(methodVisitor, record);
} else {
return new CodePreservingMethodVisitor(methodVisitor, record, methodRebaseResolver.resolve(implementedMethod.asDefined()));
}
}
@Override
public void visitEnd() {
for (FieldDescription fieldDescription : declarableFields.values()) {
fieldPool.target(fieldDescription).apply(cv, annotationValueFilterFactory);
}
for (MethodDescription methodDescription : declarableMethods.values()) {
methodPool.target(methodDescription).apply(cv, implementationContext, annotationValueFilterFactory);
}
initializationHandler.complete(cv, implementationContext);
super.visitEnd();
}
/**
* A field visitor that obtains all attributes and annotations of a field that is found in the
* class file but that discards all code.
*/
protected class AttributeObtainingFieldVisitor extends FieldVisitor {
/**
* The field pool record to apply onto the field visitor.
*/
private final FieldPool.Record record;
/**
* Creates a new attribute obtaining field visitor.
*
* @param fieldVisitor The field visitor to delegate to.
* @param record The field pool record to apply onto the field visitor.
*/
protected AttributeObtainingFieldVisitor(FieldVisitor fieldVisitor, FieldPool.Record record) {
super(Opcodes.ASM5, fieldVisitor);
this.record = record;
}
@Override
public AnnotationVisitor visitTypeAnnotation(int typeReference, TypePath typePath, String descriptor, boolean visible) {
return annotationRetention.isEnabled()
? super.visitTypeAnnotation(typeReference, typePath, descriptor, visible)
: IGNORE_ANNOTATION;
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
return annotationRetention.isEnabled()
? super.visitAnnotation(descriptor, visible)
: IGNORE_ANNOTATION;
}
@Override
public void visitEnd() {
record.apply(fv, annotationValueFilterFactory);
super.visitEnd();
}
}
/**
* A method visitor that preserves the code of a method in the class file by copying it into a rebased
* method while copying all attributes and annotations to the actual method.
*/
protected class CodePreservingMethodVisitor extends MethodVisitor {
/**
* The method visitor of the actual method.
*/
private final MethodVisitor actualMethodVisitor;
/**
* The method pool entry to apply.
*/
private final MethodPool.Record record;
/**
* The resolution of a potential rebased method.
*/
private final MethodRebaseResolver.Resolution resolution;
/**
* Creates a new code preserving method visitor.
*
* @param actualMethodVisitor The method visitor of the actual method.
* @param record The method pool entry to apply.
* @param resolution The resolution of the method rebase resolver in use.
*/
protected CodePreservingMethodVisitor(MethodVisitor actualMethodVisitor,
MethodPool.Record record,
MethodRebaseResolver.Resolution resolution) {
super(Opcodes.ASM5, actualMethodVisitor);
this.actualMethodVisitor = actualMethodVisitor;
this.record = record;
this.resolution = resolution;
record.applyHead(actualMethodVisitor);
}
@Override
public AnnotationVisitor visitAnnotationDefault() {
return IGNORE_ANNOTATION; // Annotation types can never be rebased.
}
@Override
public AnnotationVisitor visitTypeAnnotation(int typeReference, TypePath typePath, String descriptor, boolean visible) {
return annotationRetention.isEnabled()
? super.visitTypeAnnotation(typeReference, typePath, descriptor, visible)
: IGNORE_ANNOTATION;
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
return annotationRetention.isEnabled()
? super.visitAnnotation(descriptor, visible)
: IGNORE_ANNOTATION;
}
@Override
public AnnotationVisitor visitParameterAnnotation(int index, String descriptor, boolean visible) {
return annotationRetention.isEnabled()
? super.visitParameterAnnotation(index, descriptor, visible)
: IGNORE_ANNOTATION;
}
@Override
public void visitCode() {
record.applyBody(actualMethodVisitor, implementationContext, annotationValueFilterFactory);
actualMethodVisitor.visitEnd();
mv = resolution.isRebased()
? cv.visitMethod(resolution.getResolvedMethod().getActualModifiers(),
resolution.getResolvedMethod().getInternalName(),
resolution.getResolvedMethod().getDescriptor(),
resolution.getResolvedMethod().getGenericSignature(),
resolution.getResolvedMethod().getExceptionTypes().asErasures().toInternalNames())
: IGNORE_METHOD;
super.visitCode();
}
@Override
public void visitMaxs(int stackSize, int localVariableLength) {
super.visitMaxs(stackSize, Math.max(localVariableLength, resolution.getResolvedMethod().getStackSize()));
}
}
/**
* A method visitor that obtains all attributes and annotations of a method that is found in the
* class file but that discards all code.
*/
protected class AttributeObtainingMethodVisitor extends MethodVisitor {
/**
* The method visitor to which the actual method is to be written to.
*/
private final MethodVisitor actualMethodVisitor;
/**
* The method pool entry to apply.
*/
private final MethodPool.Record record;
/**
* Creates a new attribute obtaining method visitor.
*
* @param actualMethodVisitor The method visitor of the actual method.
* @param record The method pool entry to apply.
*/
protected AttributeObtainingMethodVisitor(MethodVisitor actualMethodVisitor, MethodPool.Record record) {
super(Opcodes.ASM5, actualMethodVisitor);
this.actualMethodVisitor = actualMethodVisitor;
this.record = record;
record.applyHead(actualMethodVisitor);
}
@Override
public AnnotationVisitor visitAnnotationDefault() {
return IGNORE_ANNOTATION;
}
@Override
public AnnotationVisitor visitTypeAnnotation(int typeReference, TypePath typePath, String descriptor, boolean visible) {
return annotationRetention.isEnabled()
? super.visitTypeAnnotation(typeReference, typePath, descriptor, visible)
: IGNORE_ANNOTATION;
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
return annotationRetention.isEnabled()
? super.visitAnnotation(descriptor, visible)
: IGNORE_ANNOTATION;
}
@Override
public AnnotationVisitor visitParameterAnnotation(int index, String descriptor, boolean visible) {
return annotationRetention.isEnabled()
? super.visitParameterAnnotation(index, descriptor, visible)
: IGNORE_ANNOTATION;
}
@Override
public void visitCode() {
mv = IGNORE_METHOD;
}
@Override
public void visitEnd() {
record.applyBody(actualMethodVisitor, implementationContext, annotationValueFilterFactory);
actualMethodVisitor.visitEnd();
}
}
}
}
/**
* A type writer that creates a class file that is not based upon another, existing class.
*
* @param <U> The best known loaded type for the dynamically created type.
*/
@EqualsAndHashCode(callSuper = true)
public static class ForCreation<U> extends Default<U> {
/**
* The method pool to use.
*/
private final MethodPool methodPool;
/**
* Creates a new default type writer for creating a new type that is not based on an existing class file.
*
* @param instrumentedType The instrumented type to be created.
* @param classFileVersion The class file version to write the instrumented type in and to apply when creating auxiliary types.
* @param fieldPool The field pool to use.
* @param methodPool The method pool to use.
* @param auxiliaryTypes A list of auxiliary types to add to the created type.
* @param fields The instrumented type's declared fields.
* @param methods The instrumented type's declared and virtually inherited methods.
* @param instrumentedMethods The instrumented methods relevant to this type creation.
* @param loadedTypeInitializer The loaded type initializer to apply onto the created type after loading.
* @param typeInitializer The type initializer to include in the created type's type initializer.
* @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
* @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
* @param annotationValueFilterFactory The annotation value filter factory to apply.
* @param annotationRetention The annotation retention to apply.
* @param auxiliaryTypeNamingStrategy The naming strategy for auxiliary types to apply.
* @param implementationContextFactory The implementation context factory to apply.
* @param typeValidation Determines if a type should be explicitly validated.
* @param typePool The type pool to use for computing stack map frames, if required.
*/
protected ForCreation(TypeDescription instrumentedType,
ClassFileVersion classFileVersion,
FieldPool fieldPool,
MethodPool methodPool,
List<? extends DynamicType> auxiliaryTypes,
FieldList<FieldDescription.InDefinedShape> fields,
MethodList<?> methods,
MethodList<?> instrumentedMethods,
LoadedTypeInitializer loadedTypeInitializer,
TypeInitializer typeInitializer,
TypeAttributeAppender typeAttributeAppender,
AsmVisitorWrapper asmVisitorWrapper,
AnnotationValueFilter.Factory annotationValueFilterFactory,
AnnotationRetention annotationRetention,
AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
Implementation.Context.Factory implementationContextFactory,
TypeValidation typeValidation,
TypePool typePool) {
super(instrumentedType,
classFileVersion,
fieldPool,
auxiliaryTypes,
fields,
methods,
instrumentedMethods,
loadedTypeInitializer,
typeInitializer,
typeAttributeAppender,
asmVisitorWrapper,
annotationValueFilterFactory,
annotationRetention,
auxiliaryTypeNamingStrategy,
implementationContextFactory,
typeValidation,
typePool);
this.methodPool = methodPool;
}
@Override
protected UnresolvedType create(TypeInitializer typeInitializer) {
int writerFlags = asmVisitorWrapper.mergeWriter(AsmVisitorWrapper.NO_FLAGS);
ClassWriter classWriter = new FrameComputingClassWriter(writerFlags, typePool);
Implementation.Context.ExtractableView implementationContext = implementationContextFactory.make(instrumentedType,
auxiliaryTypeNamingStrategy,
typeInitializer,
classFileVersion,
classFileVersion);
ClassVisitor classVisitor = asmVisitorWrapper.wrap(instrumentedType,
ValidatingClassVisitor.of(classWriter, typeValidation),
implementationContext,
typePool,
fields,
methods,
writerFlags,
asmVisitorWrapper.mergeReader(AsmVisitorWrapper.NO_FLAGS));
classVisitor.visit(classFileVersion.getMinorMajorVersion(),
instrumentedType.getActualModifiers(!instrumentedType.isInterface()),
instrumentedType.getInternalName(),
instrumentedType.getGenericSignature(),
(instrumentedType.getSuperClass() == null
? TypeDescription.OBJECT
: instrumentedType.getSuperClass().asErasure()).getInternalName(),
instrumentedType.getInterfaces().asErasures().toInternalNames());
typeAttributeAppender.apply(classVisitor, instrumentedType, annotationValueFilterFactory.on(instrumentedType));
for (FieldDescription fieldDescription : fields) {
fieldPool.target(fieldDescription).apply(classVisitor, annotationValueFilterFactory);
}
for (MethodDescription methodDescription : instrumentedMethods) {
methodPool.target(methodDescription).apply(classVisitor, implementationContext, annotationValueFilterFactory);
}
implementationContext.drain(new TypeInitializer.Drain.Default(instrumentedType,
methodPool,
annotationValueFilterFactory), classVisitor, annotationValueFilterFactory);
classVisitor.visitEnd();
return new UnresolvedType(classWriter.toByteArray(), implementationContext.getAuxiliaryTypes());
}
}
/**
* An action to write a class file to the dumping location.
*/
@EqualsAndHashCode
protected static class ClassDumpAction implements PrivilegedExceptionAction<Void> {
/**
* Indicates that nothing is returned from this action.
*/
private static final Void NOTHING = null;
/**
* The target folder for writing the class file to.
*/
private final String target;
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* The type's binary representation.
*/
private final byte[] binaryRepresentation;
/**
* Creates a new class dump action.
*
* @param target The target folder for writing the class file to.
* @param instrumentedType The instrumented type.
* @param binaryRepresentation The type's binary representation.
*/
protected ClassDumpAction(String target, TypeDescription instrumentedType, byte[] binaryRepresentation) {
this.target = target;
this.instrumentedType = instrumentedType;
this.binaryRepresentation = binaryRepresentation;
}
@Override
public Void run() throws Exception {
OutputStream outputStream = new FileOutputStream(new File(target, instrumentedType.getName() + "." + System.currentTimeMillis()));
try {
outputStream.write(binaryRepresentation);
return NOTHING;
} finally {
outputStream.close();
}
}
}
}
}