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.pool.TypePool;
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 net.bytebuddy.utility.JavaModule;
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 java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.none;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
@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});
}
list.add(new Object[]{new AgentBuilder.DescriptionStrategy.SuperTypeLoading(AgentBuilder.DescriptionStrategy.Default.POOL_ONLY)});
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),
ByteArrayClassLoader.PersistenceHandler.MANIFEST);
optionalTypeLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
ClassFileExtraction.of(SimpleOptionalType.class),
ByteArrayClassLoader.PersistenceHandler.MANIFEST);
}
@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.getDeclaredConstructor().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.getDeclaredConstructor().newInstance()), is((Object) 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.getDeclaredConstructor().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.getDeclaredConstructor().newInstance()), is((Object) 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,
JavaModule module) {
return builder.method(named(FOO)).intercept(FixedValue.value(BAR));
}
}
}