package net.bytebuddy.asm; import net.bytebuddy.ByteBuddy; import net.bytebuddy.ClassFileVersion; import net.bytebuddy.description.annotation.AnnotationDescription; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.modifier.Ownership; import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.dynamic.scaffold.InstrumentedType; import net.bytebuddy.implementation.FixedValue; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.bytecode.ByteCodeAppender; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.util.Arrays; import java.util.Collection; import static net.bytebuddy.matcher.ElementMatchers.named; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; @RunWith(Parameterized.class) public class AdviceDeadCodeTest { private static final String FOO = "foo"; @Parameterized.Parameters public static Collection<Object[]> data() { return Arrays.asList(new Object[][]{ {ClassFileVersion.JAVA_V5}, {ClassFileVersion.JAVA_V6} }); } private final ClassFileVersion classFileVersion; public AdviceDeadCodeTest(ClassFileVersion classFileVersion) { this.classFileVersion = classFileVersion; } @Test public void testAdviceProcessesDeadCode() throws Exception { Class<?> type = new ByteBuddy(classFileVersion) .subclass(Object.class) .defineMethod(FOO, String.class, Visibility.PUBLIC) .intercept(new DeadStringAppender()) .make() .load(null, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT) .getLoaded(); Class<?> redefined = new ByteBuddy() .redefine(type) .visit(Advice.to(ExitAdvice.class).on(named(FOO))) .make() .load(null, ClassLoadingStrategy.Default.WRAPPER) .getLoaded(); assertThat(redefined.getDeclaredMethod(FOO).invoke(redefined.getDeclaredConstructor().newInstance()), is((Object) FOO)); } @Test public void testAdviceContainsDeadCode() throws Exception { Class<?> advice = new ByteBuddy(classFileVersion) .subclass(Object.class) .defineMethod(FOO, void.class, Ownership.STATIC) .intercept(new DeadVoidAppender()) .annotateMethod(AnnotationDescription.Builder.ofType(Advice.OnMethodEnter.class).define("suppress", RuntimeException.class).build()) .annotateMethod(AnnotationDescription.Builder.ofType(Advice.OnMethodExit.class).define("suppress", RuntimeException.class).build()) .make() .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER_PERSISTENT) .getLoaded(); Class<?> foo = new ByteBuddy(classFileVersion) .subclass(Object.class) .defineMethod("foo", String.class, Visibility.PUBLIC) .intercept(FixedValue.value(FOO)) .make() .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT) .getLoaded(); Class<?> redefined = new ByteBuddy() .redefine(foo) .visit(Advice.to(advice).on(named(FOO))) .make() .load(null, ClassLoadingStrategy.Default.CHILD_FIRST) .getLoaded(); assertThat(redefined, not(sameInstance((Object) foo))); assertThat(redefined.getDeclaredMethod(FOO).invoke(redefined.getDeclaredConstructor().newInstance()), is((Object) FOO)); } @Test public void testAdviceWithExchangeDuplicationDeadCode() throws Exception { Class<?> type = new ByteBuddy(classFileVersion) .subclass(Object.class) .defineMethod(FOO, String.class, Visibility.PUBLIC) .intercept(new DeadExchangeAppender()) .make() .load(null, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT) .getLoaded(); Class<?> redefined = new ByteBuddy() .redefine(type) .visit(Advice.to(ExitAdvice.class).on(named(FOO))) .make() .load(null, ClassLoadingStrategy.Default.WRAPPER) .getLoaded(); assertThat(redefined.getDeclaredMethod(FOO).invoke(redefined.getDeclaredConstructor().newInstance()), is((Object) FOO)); } @SuppressWarnings("all") private static class ExitAdvice { @Advice.OnMethodExit private static void exit(@Advice.Return(readOnly = false) String value) { value = FOO; } } private static class DeadStringAppender implements Implementation, ByteCodeAppender { @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) { methodVisitor.visitInsn(Opcodes.ACONST_NULL); methodVisitor.visitInsn(Opcodes.ARETURN); methodVisitor.visitInsn(Opcodes.POP); // dead code methodVisitor.visitInsn(Opcodes.ACONST_NULL); methodVisitor.visitInsn(Opcodes.ARETURN); return new Size(1, instrumentedMethod.getStackSize()); } } private static class DeadVoidAppender implements Implementation, ByteCodeAppender { @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) { methodVisitor.visitInsn(Opcodes.RETURN); methodVisitor.visitInsn(Opcodes.POP); // dead code methodVisitor.visitInsn(Opcodes.RETURN); return new Size(1, instrumentedMethod.getStackSize()); } } private static class DeadExchangeAppender implements Implementation, ByteCodeAppender { @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) { methodVisitor.visitInsn(Opcodes.ACONST_NULL); methodVisitor.visitInsn(Opcodes.ARETURN); methodVisitor.visitInsn(Opcodes.DUP_X1); // dead code methodVisitor.visitInsn(Opcodes.ARETURN); return new Size(1, instrumentedMethod.getStackSize()); } } }