Skip to content

Commit

Permalink
Added better tests for the description strategies.
Browse files Browse the repository at this point in the history
  • Loading branch information
raphw committed Jul 27, 2016
1 parent a6182c9 commit bc6342f
Show file tree
Hide file tree
Showing 3 changed files with 267 additions and 337 deletions.
Expand Up @@ -5,10 +5,13 @@
import net.bytebuddy.ClassFileVersion; import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.asm.AsmVisitorWrapper; import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.field.FieldDescription; import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.method.ParameterDescription; import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.modifier.*; import net.bytebuddy.description.modifier.*;
import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.TypeList;
import net.bytebuddy.dynamic.ClassFileLocator; import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.NexusAccessor; import net.bytebuddy.dynamic.NexusAccessor;
Expand Down Expand Up @@ -2362,14 +2365,26 @@ public TypeDescription apply(String typeName, Class<?> type, TypePool typePool)
}, },


/** /**
* Applies the same logic as {@link Default#POOL_LAST} but only resolves the actual {@link TypeDescription} * A description strategy that applies the same resolution as {@link Default#POOL_LAST} but only resolves the actual
* if any property that cannot be derived by the type name is accessed. * {@link TypeDescription} if any property that cannot be derived by the type name is accessed.
*/ */
POOL_LAST_DEFERRED { POOL_LAST_DEFERRED {
@Override @Override
public TypeDescription apply(String typeName, Class<?> type, TypePool typePool) { public TypeDescription apply(String typeName, Class<?> type, TypePool typePool) {
return new LazyTypeDescriptionWithEagerProperties(typeName, type, typePool); return new LazyTypeDescriptionWithEagerProperties(typeName, type, typePool);
} }
},

/**
* A description strategy that falls back to the type pool if a field, method or name cannot be resolved from a loaded type.
*/
POOL_LAST_FALLBACK {
@Override
public TypeDescription apply(String typeName, Class<?> type, TypePool typePool) {
return type == null
? typePool.describe(typeName).resolve()
: new TypeDescriptionWithFallbackProperties(typeName, type, typePool);
}
}; };


@Override @Override
Expand All @@ -2395,7 +2410,7 @@ protected static class LazyTypeDescriptionWithEagerProperties extends TypeDescri
private final String typeName; private final String typeName;


/** /**
* The loaded version of the type being described. * The loaded version of the type being described or {@code null} if no such type is available.
*/ */
private final Class<?> type; private final Class<?> type;


Expand All @@ -2414,7 +2429,7 @@ protected static class LazyTypeDescriptionWithEagerProperties extends TypeDescri
* Creates a new lazy type description with eager properties. * Creates a new lazy type description with eager properties.
* *
* @param typeName The binary name of the type being described. * @param typeName The binary name of the type being described.
* @param type The loaded version of the type being described. * @param type The loaded version of the type being described or {@code null} if no such type is available.
* @param typePool The type pool to use. * @param typePool The type pool to use.
*/ */
protected LazyTypeDescriptionWithEagerProperties(String typeName, Class<?> type, TypePool typePool) { protected LazyTypeDescriptionWithEagerProperties(String typeName, Class<?> type, TypePool typePool) {
Expand All @@ -2438,6 +2453,61 @@ protected TypeDescription delegate() {
return delegate; return delegate;
} }
} }

/**
* A type description that falls back to a {@link TypePool} description if the type's declared types,
* fields or methods cannot be resolved successfully.
*/
protected static class TypeDescriptionWithFallbackProperties extends TypeDescription.ForLoadedType {

/**
* The type's binary name.
*/
private final String typeName;

/**
* The type pool for resolving the type description.
*/
private final TypePool typePool;

/**
* @param typeName The type's binary name.
* @param type The loaded type this instance represents.
* @param typePool The type pool for resolving the type description.
*/
protected TypeDescriptionWithFallbackProperties(String typeName, Class<?> type, TypePool typePool) {
super(type);
this.typeName = typeName;
this.typePool = typePool;
}

@Override
public TypeList getDeclaredTypes() {
try {
return super.getDeclaredTypes();
} catch (Throwable ignored) {
return typePool.describe(typeName).resolve().getDeclaredTypes();
}
}

@Override
public FieldList<FieldDescription.InDefinedShape> getDeclaredFields() {
try {
return super.getDeclaredFields();
} catch (Throwable ignored) {
return typePool.describe(typeName).resolve().getDeclaredFields();
}
}

@Override
public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
try {
return super.getDeclaredMethods();
} catch (Throwable ignored) {
return typePool.describe(typeName).resolve().getDeclaredMethods();
}
}
}
} }
} }


Expand Down
@@ -0,0 +1,189 @@
package net.bytebuddy.agent.builder;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.loading.PackageDefinitionStrategy;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.test.packaging.SimpleOptionalType;
import net.bytebuddy.test.packaging.SimpleType;
import net.bytebuddy.test.utility.AgentAttachmentRule;
import net.bytebuddy.test.utility.ClassFileExtraction;
import net.bytebuddy.test.utility.IntegrationRule;
import net.bytebuddy.test.utility.JavaVersionRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.MethodRule;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static net.bytebuddy.dynamic.loading.ClassInjector.DEFAULT_PROTECTION_DOMAIN;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.none;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;

@RunWith(Parameterized.class)
public class AgentBuilderDefaultApplicationRedefineTest {

private static final String FOO = "foo", BAR = "bar";

@Parameterized.Parameters
public static Collection<Object[]> data() {
List<Object[]> list = new ArrayList<Object[]>();
for (AgentBuilder.DescriptionStrategy descriptionStrategy : AgentBuilder.DescriptionStrategy.Default.values()) {
list.add(new Object[]{descriptionStrategy});
}
return list;
}

private final AgentBuilder.DescriptionStrategy descriptionStrategy;

public AgentBuilderDefaultApplicationRedefineTest(AgentBuilder.DescriptionStrategy descriptionStrategy) {
this.descriptionStrategy = descriptionStrategy;
}

@Rule
public MethodRule agentAttachmentRule = new AgentAttachmentRule();

@Rule
public MethodRule javaVersionRule = new JavaVersionRule();

@Rule
public MethodRule integrationRule = new IntegrationRule();

private ClassLoader simpleTypeLoader, optionalTypeLoader;

@Before
public void setUp() throws Exception {
simpleTypeLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
ClassFileExtraction.of(SimpleType.class),
DEFAULT_PROTECTION_DOMAIN,
ByteArrayClassLoader.PersistenceHandler.MANIFEST,
PackageDefinitionStrategy.NoOp.INSTANCE);
optionalTypeLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
ClassFileExtraction.of(SimpleOptionalType.class),
DEFAULT_PROTECTION_DOMAIN,
ByteArrayClassLoader.PersistenceHandler.MANIFEST,
PackageDefinitionStrategy.NoOp.INSTANCE);
}

@Test
@AgentAttachmentRule.Enforce(redefinesClasses = true)
@IntegrationRule.Enforce
public void testRedefinition() throws Exception {
// A redefinition reflects on loaded types which are eagerly validated types (Java 7- for redefinition).
// This causes type equality for outer/inner classes to fail which is why an external class is used.
assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
assertThat(simpleTypeLoader.loadClass(SimpleType.class.getName()).getName(), is(SimpleType.class.getName())); // ensure that class is loaded
ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
.ignore(none())
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
.with(descriptionStrategy)
.type(ElementMatchers.is(SimpleType.class), ElementMatchers.is(simpleTypeLoader)).transform(new FooTransformer())
.installOnByteBuddyAgent();
try {
Class<?> type = simpleTypeLoader.loadClass(SimpleType.class.getName());
assertThat(type.getDeclaredMethod(FOO).invoke(type.newInstance()), is((Object) BAR));
} finally {
ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
}
}

@Test
@AgentAttachmentRule.Enforce(redefinesClasses = true)
@IntegrationRule.Enforce
public void testRedefinitionOptionalType() throws Exception {
// A redefinition reflects on loaded types which are eagerly validated types (Java 7- for redefinition).
// This causes type equality for outer/inner classes to fail which is why an external class is used.
assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
assertThat(optionalTypeLoader.loadClass(SimpleOptionalType.class.getName()).getName(), is(SimpleOptionalType.class.getName())); // ensure that class is loaded
ClassFileTransformer classFileTransformer = new AgentBuilder.Default(new ByteBuddy().with(TypeValidation.DISABLED))
.ignore(none())
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
.with(descriptionStrategy)
.type(ElementMatchers.is(SimpleOptionalType.class), ElementMatchers.is(optionalTypeLoader)).transform(new FooTransformer())
.installOnByteBuddyAgent();
try {
Class<?> type = optionalTypeLoader.loadClass(SimpleOptionalType.class.getName());
// The hybrid strategy cannot transform optional types.
assertThat(type.getDeclaredMethod(FOO).invoke(type.newInstance()), is((Object) (descriptionStrategy == AgentBuilder.DescriptionStrategy.Default.HYBRID
? FOO
: BAR)));
} finally {
ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
}
}

@Test
@AgentAttachmentRule.Enforce(retransformsClasses = true)
@IntegrationRule.Enforce
public void testRetransformation() throws Exception {
// A redefinition reflects on loaded types which are eagerly validated types (Java 7- for redefinition).
// This causes type equality for outer/inner classes to fail which is why an external class is used.
assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
assertThat(simpleTypeLoader.loadClass(SimpleType.class.getName()).getName(), is(SimpleType.class.getName())); // ensure that class is loaded
ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
.ignore(none())
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
.with(descriptionStrategy)
.type(ElementMatchers.is(SimpleType.class), ElementMatchers.is(simpleTypeLoader)).transform(new FooTransformer())
.installOnByteBuddyAgent();
try {
Class<?> type = simpleTypeLoader.loadClass(SimpleType.class.getName());
assertThat(type.getDeclaredMethod(FOO).invoke(type.newInstance()), is((Object) BAR));
} finally {
ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
}
}

@Test
@AgentAttachmentRule.Enforce(retransformsClasses = true)
@IntegrationRule.Enforce
public void testRetransformationOptionalType() throws Exception {
// A redefinition reflects on loaded types which are eagerly validated types (Java 7- for redefinition).
// This causes type equality for outer/inner classes to fail which is why an external class is used.
assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
assertThat(optionalTypeLoader.loadClass(SimpleOptionalType.class.getName()).getName(), is(SimpleOptionalType.class.getName())); // ensure that class is loaded
ClassFileTransformer classFileTransformer = new AgentBuilder.Default(new ByteBuddy().with(TypeValidation.DISABLED))
.ignore(none())
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
.with(descriptionStrategy)
.type(ElementMatchers.is(SimpleOptionalType.class), ElementMatchers.is(optionalTypeLoader)).transform(new FooTransformer())
.installOnByteBuddyAgent();
try {
Class<?> type = optionalTypeLoader.loadClass(SimpleOptionalType.class.getName());
// The hybrid strategy cannot transform optional types.
assertThat(type.getDeclaredMethod(FOO).invoke(type.newInstance()), is((Object) (descriptionStrategy == AgentBuilder.DescriptionStrategy.Default.HYBRID
? FOO
: BAR)));
} finally {
ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
}
}

private static class FooTransformer implements AgentBuilder.Transformer {

@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader) {
return builder.method(named(FOO)).intercept(FixedValue.value(BAR));
}
}
}

0 comments on commit bc6342f

Please sign in to comment.