package net.bytebuddy.asm; import net.bytebuddy.ByteBuddy; 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 net.bytebuddy.implementation.bytecode.StackSize; import net.bytebuddy.implementation.bytecode.assign.Assigner; import net.bytebuddy.test.utility.DebuggingWrapper; 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.io.Serializable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; 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 AdviceInconsistentStackSizeTest { private static final String FOO = "foo"; private final Class<?> type; private final Serializable original, replaced; private final int opcode; public AdviceInconsistentStackSizeTest(Class<?> type, Serializable original, Serializable replaced, int opcode) { this.type = type; this.original = original; this.replaced = replaced; this.opcode = opcode; } @Parameterized.Parameters public static Collection<Object[]> data() { return Arrays.asList(new Object[][]{ {String.class, "foo", "bar", Opcodes.ARETURN}, {boolean.class, 0, false, Opcodes.IRETURN}, {byte.class, 0, (byte) 42, Opcodes.IRETURN}, {short.class, 0, (short) 42, Opcodes.IRETURN}, {char.class, 0, (char) 42, Opcodes.IRETURN}, {int.class, 0, 42, Opcodes.IRETURN}, {long.class, 0L, 42L, Opcodes.LRETURN}, {float.class, 0f, 42f, Opcodes.FRETURN}, {double.class, 0d, 42d, Opcodes.DRETURN}, {void.class, null, null, Opcodes.RETURN}, }); } @Test public void testInconsistentStackSize() throws Exception { Class<?> atypical = new ByteBuddy() .subclass(Object.class) .defineMethod(FOO, type, Visibility.PUBLIC) .intercept(new InconsistentSizeAppender()) .make() .load(null, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT) .getLoaded(); Class<?> adviced = new ByteBuddy() .redefine(atypical) .visit(Advice.withCustomMapping().bind(Value.class, replaced).to(ExitAdvice.class).on(named(FOO))) .make() .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER) .getLoaded(); assertThat(adviced.getDeclaredMethod(FOO).invoke(adviced.getDeclaredConstructor().newInstance()), is((Object) replaced)); } @Test public void testInconsistentStackSizeAdvice() throws Exception { Class<?> advice = new ByteBuddy() .subclass(Object.class) .defineMethod(FOO, type, Ownership.STATIC) .intercept(new InconsistentSizeAppender()) .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() .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)); } @SuppressWarnings("all") private static class ExitAdvice { @Advice.OnMethodExit private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object returned, @Value Object value) { returned = value; } } @Retention(RetentionPolicy.RUNTIME) private @interface Value { /* empty */ } private class InconsistentSizeAppender 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) { if (original != null) { methodVisitor.visitLdcInsn(original); } methodVisitor.visitInsn(opcode); methodVisitor.visitFrame(Opcodes.F_SAME, 0, new Object[0], 0, new Object[0]); if (original != null) { methodVisitor.visitLdcInsn(original); methodVisitor.visitLdcInsn(original); } methodVisitor.visitInsn(opcode); return new Size(StackSize.of(type).getSize() * 2, instrumentedMethod.getStackSize()); } } }