From f8fefbbd4dcab38ef2ffb51a41d9b31291e40fe4 Mon Sep 17 00:00:00 2001 From: Rafael Winterhalter Date: Tue, 5 Jan 2016 14:30:53 +0100 Subject: [PATCH] Refactored fixed value instrumentation. --- .../bytebuddy/implementation/FixedValue.java | 220 ++++++++++++++---- ...ractDynamicTypeBuilderForInliningTest.java | 6 +- .../FixedValueConstantPoolTypesTest.java | 18 +- .../FixedValueObjectPropertiesTest.java | 47 ---- .../implementation/FixedValueTest.java | 63 ++++- 5 files changed, 235 insertions(+), 119 deletions(-) delete mode 100644 byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueObjectPropertiesTest.java diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/FixedValue.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/FixedValue.java index d88c24de7cd..a263183fc5b 100644 --- a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/FixedValue.java +++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/FixedValue.java @@ -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 - * 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. + *

+ *

+ * 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. + *

* - * @return An implementation that represents the {@code null} value. - */ - 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: - * - *

 

- * 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. - *

 

- * 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. + * @param fixedValue The fixed value to return from the method. * @return An implementation for the given {@code 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(); if (type == String.class) { return new ForPoolValue(new TextConstant((String) fixedValue), @@ -158,18 +132,14 @@ public static AssignerConfigurable value(Object fixedValue) { * 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, * object identity will be preserved between the given {@code fixedValue} and the value that is returned by - * instrumented methods. - *

 

- * As an exception, the {@code null} value is always presented by a constant value and is never stored in a static - * field. + * instrumented methods. The field name can be explicitly determined. The field name is generated from the fixed value's + * hash code. * * @param fixedValue The fixed value to be returned by methods that are instrumented by this implementation. * @return An implementation for the given {@code fixedValue}. */ public static AssignerConfigurable reference(Object fixedValue) { - return fixedValue == null - ? new ForPoolValue(NullConstant.INSTANCE, TypeDescription.OBJECT, Assigner.DEFAULT, Assigner.Typing.DYNAMIC) - : new ForStaticField(fixedValue, Assigner.DEFAULT, Assigner.Typing.STATIC); + return new ForStaticField(fixedValue, Assigner.DEFAULT, Assigner.Typing.STATIC); } /** @@ -177,17 +147,12 @@ public static AssignerConfigurable reference(Object fixedValue) { * 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 * instrumented methods. The field name can be explicitly determined. - *

 

- * 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 fieldName The name of the field for storing the fixed value. * @return An implementation for the given {@code fixedValue}. */ 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); } @@ -217,6 +182,24 @@ public static AssignerConfigurable value(JavaInstance fixedValue) { 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. * @@ -275,6 +258,142 @@ public interface AssignerConfigurable extends Implementation { 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 * class's constant pool. @@ -384,8 +503,7 @@ protected static class ForStaticField extends FixedValue implements AssignerConf * value. * * @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 - * instrumented value. + * @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 ForStaticField(Object fixedValue, Assigner assigner, Assigner.Typing typing) { diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/AbstractDynamicTypeBuilderForInliningTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/AbstractDynamicTypeBuilderForInliningTest.java index ae420a81f42..b7a0f65be65 100644 --- a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/AbstractDynamicTypeBuilderForInliningTest.java +++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/AbstractDynamicTypeBuilderForInliningTest.java @@ -82,7 +82,7 @@ public void tearDown() throws Exception { protected abstract DynamicType.Builder createDisabledContext(); - protected abstract DynamicType.Builder createDisabledRetention(Class annotatedClass); + protected abstract DynamicType.Builder createDisabledRetention(Class annotatedClass); @Test public void testTypeInitializerRetention() throws Exception { @@ -416,7 +416,6 @@ public void testDisabledAnnotationRetention() throws Exception { } @Test - @SuppressWarnings("unchecked") public void testEnabledAnnotationRetention() throws Exception { Class type = create(Annotated.class) .field(ElementMatchers.any()).annotateField(new Annotation[0]) @@ -429,12 +428,13 @@ public void testEnabledAnnotationRetention() throws Exception { ByteArrayClassLoader.PersistenceHandler.LATENT, PackageDefinitionStrategy.NoOp.INSTANCE), ClassLoadingStrategy.Default.WRAPPER) .getLoaded(); + @SuppressWarnings("unchecked") Class sampleAnnotation = (Class) type.getClassLoader().loadClass(SampleAnnotation.class.getName()); assertThat(type.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).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 { diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueConstantPoolTypesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueConstantPoolTypesTest.java index 42fe8d5067a..06af2e6e121 100644 --- a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueConstantPoolTypesTest.java +++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueConstantPoolTypesTest.java @@ -12,7 +12,6 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.Mockito.mock; @RunWith(Parameterized.class) public class FixedValueConstantPoolTypesTest extends AbstractImplementationTest { @@ -77,8 +76,7 @@ public static Collection data() { {INT_VALUE, IntTarget.class}, {LONG_VALUE, LongTarget.class}, {FLOAT_VALUE, FloatTarget.class}, - {DOUBLE_VALUE, DoubleTarget.class}, - {NULL_VALUE, NullTarget.class} + {DOUBLE_VALUE, DoubleTarget.class} }); } @@ -235,18 +233,4 @@ public Double bar() { 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); - } - } } diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueObjectPropertiesTest.java deleted file mode 100644 index 21aae6d27a7..00000000000 --- a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueObjectPropertiesTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package net.bytebuddy.implementation; - -import net.bytebuddy.test.utility.ObjectPropertyAssertion; -import org.junit.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.MatcherAssert.assertThat; - -public class FixedValueObjectPropertiesTest { - - private static final String FOO = "foo", BAR = "bar", QUX = "qux"; - - @Test - public void testConstantPoolValue() throws Exception { - assertThat(FixedValue.value(FOO).hashCode(), is(FixedValue.value(FOO).hashCode())); - assertThat(FixedValue.value(FOO), is(FixedValue.value(FOO))); - assertThat(FixedValue.value(FOO).hashCode(), not(FixedValue.value(BAR).hashCode())); - assertThat(FixedValue.value(FOO), not(FixedValue.value(BAR))); - assertThat(FixedValue.value(FOO).hashCode(), not(FixedValue.reference(FOO).hashCode())); - assertThat(FixedValue.value(FOO), not(FixedValue.reference(FOO))); - } - - @Test - public void testReferenceValue() throws Exception { - assertThat(FixedValue.reference(FOO).hashCode(), is(FixedValue.reference(FOO).hashCode())); - assertThat(FixedValue.reference(FOO), is(FixedValue.reference(FOO))); - assertThat(FixedValue.reference(FOO).hashCode(), not(FixedValue.value(FOO).hashCode())); - assertThat(FixedValue.reference(FOO), not(FixedValue.value(FOO))); - assertThat(FixedValue.reference(FOO).hashCode(), not(FixedValue.reference(BAR).hashCode())); - assertThat(FixedValue.reference(FOO), not(FixedValue.reference(BAR))); - } - - @Test - public void testReferenceValueWithExplicitFieldName() throws Exception { - assertThat(FixedValue.reference(FOO, QUX).hashCode(), is(FixedValue.reference(FOO, QUX).hashCode())); - assertThat(FixedValue.reference(FOO, QUX), is(FixedValue.reference(FOO, QUX))); - assertThat(FixedValue.reference(FOO, QUX).hashCode(), not(FixedValue.reference(BAR, QUX).hashCode())); - assertThat(FixedValue.reference(FOO, QUX), not(FixedValue.reference(BAR, QUX))); - } - - @Test - public void testObjectProperties() throws Exception { - ObjectPropertyAssertion.of(FixedValue.ForPoolValue.class).skipSynthetic().apply(); - ObjectPropertyAssertion.of(FixedValue.ForStaticField.class).apply(); - } -} diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueTest.java index f579a21460b..8b799ff5991 100644 --- a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueTest.java +++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueTest.java @@ -4,6 +4,7 @@ import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.test.utility.CallTraceable; import net.bytebuddy.test.utility.JavaVersionRule; +import net.bytebuddy.test.utility.ObjectPropertyAssertion; import net.bytebuddy.utility.JavaInstance; import net.bytebuddy.utility.JavaType; import org.hamcrest.CoreMatchers; @@ -17,7 +18,7 @@ public class FixedValueTest extends AbstractImplementationTest { - private static final String BAR = "bar"; + private static final String FOO = "foo", BAR = "bar", QUX = "qux"; @Rule public MethodRule javaVersionRule = new JavaVersionRule(); @@ -95,6 +96,59 @@ public void testValueCall() throws Exception { assertType(implement(Foo.class, FixedValue.value(bar))); } + @Test + public void testNullValue() throws Exception { + Class type = implement(Foo.class, FixedValue.nullValue()).getLoaded(); + assertThat(type.getDeclaredFields().length, is(0)); + assertThat(type.getDeclaredMethods().length, is(1)); + assertThat(type.getDeclaredMethod(BAR).invoke(type.newInstance()), nullValue(Object.class)); + } + + @Test + public void testOriginType() throws Exception { + Class type = implement(Baz.class, FixedValue.originType()).getLoaded(); + assertThat(type.getDeclaredFields().length, is(0)); + assertThat(type.getDeclaredMethods().length, is(1)); + assertThat(type.getDeclaredMethod(BAR).invoke(type.newInstance()), is((Object) Baz.class)); + } + + @Test + public void testConstantPoolValue() throws Exception { + assertThat(FixedValue.value(FOO).hashCode(), is(FixedValue.value(FOO).hashCode())); + assertThat(FixedValue.value(FOO), is(FixedValue.value(FOO))); + assertThat(FixedValue.value(FOO).hashCode(), not(FixedValue.value(BAR).hashCode())); + assertThat(FixedValue.value(FOO), not(FixedValue.value(BAR))); + assertThat(FixedValue.value(FOO).hashCode(), not(FixedValue.reference(FOO).hashCode())); + assertThat(FixedValue.value(FOO), not(FixedValue.reference(FOO))); + } + + @Test + public void testReferenceValue() throws Exception { + assertThat(FixedValue.reference(FOO).hashCode(), is(FixedValue.reference(FOO).hashCode())); + assertThat(FixedValue.reference(FOO), is(FixedValue.reference(FOO))); + assertThat(FixedValue.reference(FOO).hashCode(), not(FixedValue.value(FOO).hashCode())); + assertThat(FixedValue.reference(FOO), not(FixedValue.value(FOO))); + assertThat(FixedValue.reference(FOO).hashCode(), not(FixedValue.reference(BAR).hashCode())); + assertThat(FixedValue.reference(FOO), not(FixedValue.reference(BAR))); + } + + @Test + public void testReferenceValueWithExplicitFieldName() throws Exception { + assertThat(FixedValue.reference(FOO, QUX).hashCode(), is(FixedValue.reference(FOO, QUX).hashCode())); + assertThat(FixedValue.reference(FOO, QUX), is(FixedValue.reference(FOO, QUX))); + assertThat(FixedValue.reference(FOO, QUX).hashCode(), not(FixedValue.reference(BAR, QUX).hashCode())); + assertThat(FixedValue.reference(FOO, QUX), not(FixedValue.reference(BAR, QUX))); + } + + @Test + public void testObjectProperties() throws Exception { + ObjectPropertyAssertion.of(FixedValue.ForPoolValue.class).skipSynthetic().apply(); + ObjectPropertyAssertion.of(FixedValue.ForStaticField.class).apply(); + ObjectPropertyAssertion.of(FixedValue.ForOriginType.class).apply(); + ObjectPropertyAssertion.of(FixedValue.ForOriginType.Appender.class).apply(); + ObjectPropertyAssertion.of(FixedValue.ForNullValue.class).apply(); + } + private void assertType(DynamicType.Loaded loaded) throws Exception { assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0)); assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1)); @@ -125,4 +179,11 @@ public Object bar() { return null; } } + + public static class Baz { + + public Class bar() { + return null; + } + } }