package net.bytebuddy.implementation.bytecode.member;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.StackSize;
import net.bytebuddy.test.utility.MockitoRule;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.mockito.Mock;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.util.Arrays;
import java.util.Collection;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.*;
@RunWith(Parameterized.class)
public class MethodInvocationTest {
private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
private static final int ARGUMENT_STACK_SIZE = 1;
private final StackSize stackSize;
private final int expectedSize;
@Rule
public TestRule mockitoRule = new MockitoRule(this);
@Mock
private MethodDescription.InDefinedShape methodDescription;
@Mock
private TypeDescription.Generic returnType, otherType;
@Mock
private TypeDescription declaringType, rawOtherType;
@Mock
private Implementation.Context implementationContext;
@Mock
private MethodVisitor methodVisitor;
public MethodInvocationTest(StackSize stackSize) {
this.stackSize = stackSize;
this.expectedSize = stackSize.getSize() - ARGUMENT_STACK_SIZE;
}
@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{StackSize.ZERO},
{StackSize.SINGLE},
{StackSize.DOUBLE}
});
}
@Before
public void setUp() throws Exception {
when(declaringType.asErasure()).thenReturn(declaringType);
when(methodDescription.getReturnType()).thenReturn(returnType);
when(methodDescription.getDeclaringType()).thenReturn(declaringType);
when(methodDescription.getStackSize()).thenReturn(ARGUMENT_STACK_SIZE);
when(declaringType.getInternalName()).thenReturn(FOO);
when(otherType.asErasure()).thenReturn(rawOtherType);
when(rawOtherType.getInternalName()).thenReturn(BAZ);
when(methodDescription.getInternalName()).thenReturn(BAR);
when(methodDescription.getDescriptor()).thenReturn(QUX);
when(returnType.getStackSize()).thenReturn(stackSize);
}
@After
public void tearDown() throws Exception {
verifyZeroInteractions(implementationContext);
}
@Test
public void testTypeInitializerInvocation() throws Exception {
when(methodDescription.isTypeInitializer()).thenReturn(true);
assertThat(MethodInvocation.invoke(methodDescription).isValid(), is(false));
}
@Test
public void testStaticMethodInvocation() throws Exception {
when(methodDescription.isStatic()).thenReturn(true);
assertInvocation(MethodInvocation.invoke(methodDescription), Opcodes.INVOKESTATIC, FOO, false);
}
@Test
public void testStaticPrivateMethodInvocation() throws Exception {
when(methodDescription.isStatic()).thenReturn(true);
when(methodDescription.isPrivate()).thenReturn(true);
assertInvocation(MethodInvocation.invoke(methodDescription), Opcodes.INVOKESTATIC, FOO, false);
}
@Test
public void testPrivateMethodInvocation() throws Exception {
when(methodDescription.isPrivate()).thenReturn(true);
assertInvocation(MethodInvocation.invoke(methodDescription), Opcodes.INVOKESPECIAL, FOO, false);
}
@Test
public void testConstructorMethodInvocation() throws Exception {
when(methodDescription.isConstructor()).thenReturn(true);
assertInvocation(MethodInvocation.invoke(methodDescription), Opcodes.INVOKESPECIAL, FOO, false);
}
@Test
public void testPublicMethodInvocation() throws Exception {
assertInvocation(MethodInvocation.invoke(methodDescription), Opcodes.INVOKEVIRTUAL, FOO, false);
}
@Test
public void testInterfaceMethodInvocation() throws Exception {
when(declaringType.isInterface()).thenReturn(true);
assertInvocation(MethodInvocation.invoke(methodDescription), Opcodes.INVOKEINTERFACE, FOO, true);
}
@Test
public void testStaticInterfaceMethodInvocation() throws Exception {
when(declaringType.isInterface()).thenReturn(true);
when(methodDescription.isStatic()).thenReturn(true);
assertInvocation(MethodInvocation.invoke(methodDescription), Opcodes.INVOKESTATIC, FOO, true);
}
@Test
public void testDefaultInterfaceMethodInvocation() throws Exception {
when(methodDescription.isDefaultMethod()).thenReturn(true);
when(declaringType.isInterface()).thenReturn(true);
assertInvocation(MethodInvocation.invoke(methodDescription), Opcodes.INVOKEINTERFACE, FOO, true);
}
@Test
public void testExplicitlySpecialDefaultInterfaceMethodInvocation() throws Exception {
when(methodDescription.isDefaultMethod()).thenReturn(true);
when(methodDescription.isSpecializableFor(declaringType)).thenReturn(true);
when(declaringType.isInterface()).thenReturn(true);
assertInvocation(MethodInvocation.invoke(methodDescription).special(declaringType), Opcodes.INVOKESPECIAL, FOO, true);
}
@Test
public void testExplicitlySpecialDefaultInterfaceMethodInvocationOnOther() throws Exception {
when(methodDescription.isDefaultMethod()).thenReturn(true);
when(methodDescription.isSpecializableFor(rawOtherType)).thenReturn(false);
assertThat(MethodInvocation.invoke(methodDescription).special(rawOtherType).isValid(), is(false));
}
@Test
public void testExplicitlySpecialMethodInvocation() throws Exception {
when(methodDescription.isSpecializableFor(rawOtherType)).thenReturn(true);
assertInvocation(MethodInvocation.invoke(methodDescription).special(rawOtherType), Opcodes.INVOKESPECIAL, BAZ, false);
}
@Test
public void testIllegalSpecialMethodInvocation() throws Exception {
assertThat(MethodInvocation.invoke(methodDescription).special(rawOtherType).isValid(), is(false));
}
@Test
public void testExplicitlyVirtualMethodInvocation() throws Exception {
when(declaringType.isAssignableFrom(rawOtherType)).thenReturn(true);
assertInvocation(MethodInvocation.invoke(methodDescription).virtual(rawOtherType), Opcodes.INVOKEVIRTUAL, BAZ, false);
}
@Test
public void testExplicitlyVirtualMethodInvocationOfInterface() throws Exception {
when(declaringType.isAssignableFrom(rawOtherType)).thenReturn(true);
when(rawOtherType.isInterface()).thenReturn(true);
assertInvocation(MethodInvocation.invoke(methodDescription).virtual(rawOtherType), Opcodes.INVOKEINTERFACE, BAZ, true);
}
@Test
public void testStaticVirtualInvocation() throws Exception {
when(methodDescription.isStatic()).thenReturn(true);
assertThat(MethodInvocation.invoke(methodDescription).virtual(rawOtherType).isValid(), is(false));
}
@Test
public void testPrivateVirtualInvocation() throws Exception {
when(methodDescription.isPrivate()).thenReturn(true);
assertThat(MethodInvocation.invoke(methodDescription).virtual(rawOtherType).isValid(), is(false));
}
@Test
public void testConstructorVirtualInvocation() throws Exception {
when(methodDescription.isConstructor()).thenReturn(true);
assertThat(MethodInvocation.invoke(methodDescription).virtual(rawOtherType).isValid(), is(false));
}
private void assertInvocation(StackManipulation stackManipulation,
int opcode,
String typeName,
boolean interfaceInvocation) {
assertThat(stackManipulation.isValid(), is(true));
StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
assertThat(size.getSizeImpact(), is(expectedSize));
assertThat(size.getMaximalSize(), is(Math.max(0, expectedSize)));
verify(methodVisitor).visitMethodInsn(opcode, typeName, BAR, QUX, interfaceInvocation);
verifyNoMoreInteractions(methodVisitor);
}
}