Skip to content

Commit

Permalink
Refactored fixed value instrumentation.
Browse files Browse the repository at this point in the history
  • Loading branch information
Rafael Winterhalter committed Jan 5, 2016
1 parent 72136b8 commit f8fefbb
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 119 deletions.
Expand Up @@ -47,47 +47,21 @@ protected FixedValue(Assigner assigner, Assigner.Typing typing) {
} }


/** /**
* Creates a fixed value implementation that returns {@code null} as a fixed value. This value is inlined into * <p>
* the method and does not create a field. * Returns a fixed value from any intercepted method. The fixed value is stored in the constant pool if this is possible.
* Java is capable of storing any primitive value, {@link String} values and {@link Class} references in the constant pool.
* Since Java 7, {@code MethodHandle} as well as {@code MethodType} references are also supported. Alternatively, the fixed
* value is stored in a static field.
* </p>
* <p>
* When a value is stored in the class's constant pool, its identity is lost. If an object's identity is important, the
* {@link FixedValue#reference(Object)} method should be used instead.
* </p>
* *
* @return An implementation that represents the {@code null} value. * @param fixedValue The fixed value to return from the method.
*/
public static Implementation nullValue() {
return value((Object) null);
}

/**
* Creates a fixed value implementation that returns a fixed value. If the value can be inlined into the created
* class, i.e. can be added to the constant pool of a class, no explicit type initialization will be required for
* the created dynamic class. Otherwise, a static field will be created in the dynamic type which will be initialized
* with the given value. The following Java types can be inlined:
* <ul>
* <li>The {@link java.lang.String} type.</li>
* <li>The {@link java.lang.Class} type.</li>
* <li>All primitive types and their wrapper type, i.e. {@link java.lang.Boolean}, {@link java.lang.Byte},
* {@link java.lang.Short}, {@link java.lang.Character}, {@link java.lang.Integer}, {@link java.lang.Long},
* {@link java.lang.Float} and {@link java.lang.Double}.</li>
* <li>A {@code null} reference.</li>
* <li>From Java 7 on, instances of {@code java.lang.invoke.MethodType} and {@code java.lang.invoke.MethodHandle}.</li>
* </ul>
* <p>&nbsp;</p>
* If possible, the constant pool value is substituted by a byte code instruction that creates the value. (This is
* possible for integer types and types that are presented by integers inside the JVM ({@code boolean}, {@code byte},
* {@code short}, {@code char}) and for the {@code null} value. Additionally, several common constants of
* the {@code float}, {@code double} and {@code long} types can be represented by opcode constants.
* <p>&nbsp;</p>
* Note that method handles or (method) types require to be visible to a class's class loader.
*
* @param fixedValue The fixed value to be returned by methods that are instrumented by this implementation.
* @return An implementation for the given {@code fixedValue}. * @return An implementation for the given {@code fixedValue}.
*/ */
public static AssignerConfigurable value(Object fixedValue) { public static AssignerConfigurable value(Object fixedValue) {
if (fixedValue == null) {
return new ForPoolValue(NullConstant.INSTANCE,
TypeDescription.OBJECT,
Assigner.DEFAULT,
Assigner.Typing.DYNAMIC);
}
Class<?> type = fixedValue.getClass(); Class<?> type = fixedValue.getClass();
if (type == String.class) { if (type == String.class) {
return new ForPoolValue(new TextConstant((String) fixedValue), return new ForPoolValue(new TextConstant((String) fixedValue),
Expand Down Expand Up @@ -158,36 +132,27 @@ public static AssignerConfigurable value(Object fixedValue) {
* Other than {@link net.bytebuddy.implementation.FixedValue#value(Object)}, this function * Other than {@link net.bytebuddy.implementation.FixedValue#value(Object)}, this function
* will create a fixed value implementation that will always defined a field in the instrumented class. As a result, * will create a fixed value implementation that will always defined a field in the instrumented class. As a result,
* object identity will be preserved between the given {@code fixedValue} and the value that is returned by * object identity will be preserved between the given {@code fixedValue} and the value that is returned by
* instrumented methods. * instrumented methods. The field name can be explicitly determined. The field name is generated from the fixed value's
* <p>&nbsp;</p> * hash code.
* As an exception, the {@code null} value is always presented by a constant value and is never stored in a static
* field.
* *
* @param fixedValue The fixed value to be returned by methods that are instrumented by this implementation. * @param fixedValue The fixed value to be returned by methods that are instrumented by this implementation.
* @return An implementation for the given {@code fixedValue}. * @return An implementation for the given {@code fixedValue}.
*/ */
public static AssignerConfigurable reference(Object fixedValue) { public static AssignerConfigurable reference(Object fixedValue) {
return fixedValue == null return new ForStaticField(fixedValue, Assigner.DEFAULT, Assigner.Typing.STATIC);
? new ForPoolValue(NullConstant.INSTANCE, TypeDescription.OBJECT, Assigner.DEFAULT, Assigner.Typing.DYNAMIC)
: new ForStaticField(fixedValue, Assigner.DEFAULT, Assigner.Typing.STATIC);
} }


/** /**
* Other than {@link net.bytebuddy.implementation.FixedValue#value(Object)}, this function * Other than {@link net.bytebuddy.implementation.FixedValue#value(Object)}, this function
* will create a fixed value implementation that will always defined a field in the instrumented class. As a result, * will create a fixed value implementation that will always defined a field in the instrumented class. As a result,
* object identity will be preserved between the given {@code fixedValue} and the value that is returned by * object identity will be preserved between the given {@code fixedValue} and the value that is returned by
* instrumented methods. The field name can be explicitly determined. * instrumented methods. The field name can be explicitly determined.
* <p>&nbsp;</p>
* As an exception, the {@code null} value cannot be used for this implementation but will cause an exception.
* *
* @param fixedValue The fixed value to be returned by methods that are instrumented by this implementation. * @param fixedValue The fixed value to be returned by methods that are instrumented by this implementation.
* @param fieldName The name of the field for storing the fixed value. * @param fieldName The name of the field for storing the fixed value.
* @return An implementation for the given {@code fixedValue}. * @return An implementation for the given {@code fixedValue}.
*/ */
public static AssignerConfigurable reference(Object fixedValue, String fieldName) { public static AssignerConfigurable reference(Object fixedValue, String fieldName) {
if (fixedValue == null) {
throw new IllegalArgumentException("The fixed value must not be null");
}
return new ForStaticField(fieldName, fixedValue, Assigner.DEFAULT, Assigner.Typing.STATIC); return new ForStaticField(fieldName, fixedValue, Assigner.DEFAULT, Assigner.Typing.STATIC);
} }


Expand Down Expand Up @@ -217,6 +182,24 @@ public static AssignerConfigurable value(JavaInstance fixedValue) {
Assigner.Typing.STATIC); Assigner.Typing.STATIC);
} }


/**
* Returns a null value from an instrumented method.
*
* @return An implementation that returns {@code null} from a method.
*/
public static Implementation nullValue() {
return ForNullValue.INSTANCE;
}

/**
* Returns the origin type from an instrumented method.
*
* @return An implementation that returns the origin type of the current instrumented type.
*/
public static AssignerConfigurable originType() {
return new ForOriginType(Assigner.DEFAULT, Assigner.Typing.STATIC);
}

/** /**
* Blueprint method that for applying the actual implementation. * Blueprint method that for applying the actual implementation.
* *
Expand Down Expand Up @@ -275,6 +258,142 @@ public interface AssignerConfigurable extends Implementation {
Implementation withAssigner(Assigner assigner, Assigner.Typing typing); Implementation withAssigner(Assigner assigner, Assigner.Typing typing);
} }


/**
* A fixed value that appends the origin type of the instrumented type.
*/
protected static class ForOriginType extends FixedValue implements AssignerConfigurable {

/**
* Creates a new fixed value appender for the origin type.
*
* @param assigner The assigner to use for assigning the fixed value to the return type of the instrumented value.
* @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
*/
protected ForOriginType(Assigner assigner, Assigner.Typing typing) {
super(assigner, typing);
}

@Override
public Implementation withAssigner(Assigner assigner, Assigner.Typing typing) {
return new ForOriginType(assigner, typing);
}

@Override
public ByteCodeAppender appender(Target implementationTarget) {
return new Appender(implementationTarget.getOriginType().asErasure());
}

@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}

@Override
public String toString() {
return "FixedValue.ForOriginType{" +
"assigner=" + assigner +
", typing=" + typing +
'}';
}

/**
* An appender for writing the origin type.
*/
protected class Appender implements ByteCodeAppender {

/**
* The instrumented type.
*/
private final TypeDescription originType;

/**
* Creates a new appender.
*
* @param originType The instrumented type.
*/
protected Appender(TypeDescription originType) {
this.originType = originType;
}

@Override
public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
return ForOriginType.this.apply(methodVisitor,
implementationContext,
instrumentedMethod,
TypeDescription.CLASS.asGenericType(),
ClassConstant.of(originType));
}

/**
* Returns the outer instance.
*
* @return The outer instance.
*/
private ForOriginType getOuter() {
return ForOriginType.this;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Appender appender = (Appender) o;
return originType.equals(appender.originType)
&& getOuter().equals(appender.getOuter());
}

@Override
public int hashCode() {
return 31 * getOuter().hashCode() + originType.hashCode();
}

@Override
public String toString() {
return "FixedValue.ForOriginType.Appender{" +
"outer=" + getOuter() +
", originType=" + originType +
'}';
}
}
}

/**
* A fixed value of {@code null}.
*/
protected enum ForNullValue implements Implementation, ByteCodeAppender {

/**
* The singleton instance.
*/
INSTANCE;

@Override
public ByteCodeAppender appender(Target implementationTarget) {
return this;
}

@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}

@Override
public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
if (instrumentedMethod.getReturnType().isPrimitive()) {
throw new IllegalStateException("Cannot return null from " + instrumentedMethod);
}
return new ByteCodeAppender.Simple(
NullConstant.INSTANCE,
MethodReturn.REFERENCE
).apply(methodVisitor, implementationContext, instrumentedMethod);
}

@Override
public String toString() {
return "FixedValue.ForNullValue." + name();
}
}

/** /**
* A fixed value implementation that represents its fixed value as a value that is written to the instrumented * A fixed value implementation that represents its fixed value as a value that is written to the instrumented
* class's constant pool. * class's constant pool.
Expand Down Expand Up @@ -384,8 +503,7 @@ protected static class ForStaticField extends FixedValue implements AssignerConf
* value. * value.
* *
* @param fixedValue The fixed value to be returned. * @param fixedValue The fixed value to be returned.
* @param assigner The assigner to use for assigning the fixed value to the return type of the * @param assigner The assigner to use for assigning the fixed value to the return type of the instrumented value.
* instrumented value.
* @param typing Indicates if dynamic type castings should be attempted for incompatible assignments. * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
*/ */
protected ForStaticField(Object fixedValue, Assigner assigner, Assigner.Typing typing) { protected ForStaticField(Object fixedValue, Assigner assigner, Assigner.Typing typing) {
Expand Down
Expand Up @@ -82,7 +82,7 @@ public void tearDown() throws Exception {


protected abstract DynamicType.Builder<?> createDisabledContext(); protected abstract DynamicType.Builder<?> createDisabledContext();


protected abstract DynamicType.Builder createDisabledRetention(Class<?> annotatedClass); protected abstract DynamicType.Builder<?> createDisabledRetention(Class<?> annotatedClass);


@Test @Test
public void testTypeInitializerRetention() throws Exception { public void testTypeInitializerRetention() throws Exception {
Expand Down Expand Up @@ -416,7 +416,6 @@ public void testDisabledAnnotationRetention() throws Exception {
} }


@Test @Test
@SuppressWarnings("unchecked")
public void testEnabledAnnotationRetention() throws Exception { public void testEnabledAnnotationRetention() throws Exception {
Class<?> type = create(Annotated.class) Class<?> type = create(Annotated.class)
.field(ElementMatchers.any()).annotateField(new Annotation[0]) .field(ElementMatchers.any()).annotateField(new Annotation[0])
Expand All @@ -429,12 +428,13 @@ public void testEnabledAnnotationRetention() throws Exception {
ByteArrayClassLoader.PersistenceHandler.LATENT, ByteArrayClassLoader.PersistenceHandler.LATENT,
PackageDefinitionStrategy.NoOp.INSTANCE), ClassLoadingStrategy.Default.WRAPPER) PackageDefinitionStrategy.NoOp.INSTANCE), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded(); .getLoaded();
@SuppressWarnings("unchecked")
Class<? extends Annotation> sampleAnnotation = (Class<? extends Annotation>) type.getClassLoader().loadClass(SampleAnnotation.class.getName()); Class<? extends Annotation> sampleAnnotation = (Class<? extends Annotation>) type.getClassLoader().loadClass(SampleAnnotation.class.getName());
assertThat(type.isAnnotationPresent(sampleAnnotation), is(true)); assertThat(type.isAnnotationPresent(sampleAnnotation), is(true));
assertThat(type.getDeclaredField(FOO).isAnnotationPresent(sampleAnnotation), is(true)); assertThat(type.getDeclaredField(FOO).isAnnotationPresent(sampleAnnotation), is(true));
assertThat(type.getDeclaredMethod(FOO, Void.class).isAnnotationPresent(sampleAnnotation), is(true)); assertThat(type.getDeclaredMethod(FOO, Void.class).isAnnotationPresent(sampleAnnotation), is(true));
assertThat(type.getDeclaredMethod(FOO, Void.class).getParameterAnnotations()[0].length, is(1)); assertThat(type.getDeclaredMethod(FOO, Void.class).getParameterAnnotations()[0].length, is(1));
assertThat(type.getDeclaredMethod(FOO, Void.class).getParameterAnnotations()[0][0].annotationType(), equalTo((Class) sampleAnnotation)); assertThat(type.getDeclaredMethod(FOO, Void.class).getParameterAnnotations()[0][0].annotationType(), is((Object) sampleAnnotation));
} }


public @interface Baz { public @interface Baz {
Expand Down
Expand Up @@ -12,7 +12,6 @@


import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.mock;


@RunWith(Parameterized.class) @RunWith(Parameterized.class)
public class FixedValueConstantPoolTypesTest<T extends CallTraceable> extends AbstractImplementationTest { public class FixedValueConstantPoolTypesTest<T extends CallTraceable> extends AbstractImplementationTest {
Expand Down Expand Up @@ -77,8 +76,7 @@ public static Collection<Object[]> data() {
{INT_VALUE, IntTarget.class}, {INT_VALUE, IntTarget.class},
{LONG_VALUE, LongTarget.class}, {LONG_VALUE, LongTarget.class},
{FLOAT_VALUE, FloatTarget.class}, {FLOAT_VALUE, FloatTarget.class},
{DOUBLE_VALUE, DoubleTarget.class}, {DOUBLE_VALUE, DoubleTarget.class}
{NULL_VALUE, NullTarget.class}
}); });
} }


Expand Down Expand Up @@ -235,18 +233,4 @@ public Double bar() {
return DOUBLE_DEFAULT_VALUE; return DOUBLE_DEFAULT_VALUE;
} }
} }

@SuppressWarnings("unused")
public static class NullTarget extends CallTraceable {

public Object foo() {
register(FOO);
return mock(Runnable.class);
}

public Runnable bar() {
register(BAR);
return mock(Runnable.class);
}
}
} }

This file was deleted.

0 comments on commit f8fefbb

Please sign in to comment.