blob: 830283a1544983163fee1063ec60d7ecebcb9011 [file] [log] [blame]
// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph.invokespecial;
import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.utils.StringUtils;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.objectweb.asm.Opcodes;
@RunWith(Parameterized.class)
public class InvokeSpecialToVirtualMethodTest extends TestBase {
static final String EXPECTED = StringUtils.lines("Bar::foo", "Foo::foo", "Foo::foo", "Foo::foo");
private final TestParameters parameters;
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimes().withAllApiLevels().build();
}
public InvokeSpecialToVirtualMethodTest(TestParameters parameters) {
this.parameters = parameters;
}
@Test
public void testJvm() throws Exception {
assumeTrue(parameters.isCfRuntime());
testForJvm()
.addProgramClasses(Base.class, Bar.class, TestClass.class)
.addProgramClassFileData(getFooTransform())
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED);
}
@Test
public void testD8() throws Exception {
assumeTrue(parameters.isDexRuntime());
testForD8()
.addProgramClasses(Base.class, Bar.class, TestClass.class)
.addProgramClassFileData(getFooTransform())
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED);
}
@Test
public void testR8() throws Exception {
testForR8(parameters.getBackend())
.addProgramClasses(Base.class, Bar.class, TestClass.class)
.addProgramClassFileData(getFooTransform())
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(TestClass.class)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED);
}
@Test
public void testDX() throws Exception {
assumeTrue(parameters.isDexRuntime());
testForDX()
.addProgramClasses(Base.class, Bar.class, TestClass.class)
.addProgramClassFileData(getFooTransform())
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
.assertFailureWithErrorThatMatches(containsString(getExpectedOutput()));
}
private String getExpectedOutput() {
if (parameters.getRuntime().asDex().getVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
return "VFY: unable to resolve direct method";
}
return "was expected to be of type direct but instead was found to be of type virtual";
}
private byte[] getFooTransform() throws IOException {
return transformer(Foo.class)
.transformMethodInsnInMethod(
"foo",
(opcode, owner, name, descriptor, isInterface, visitor) -> {
if (Opcodes.INVOKESPECIAL == opcode) {
assertEquals(getBinaryNameFromJavaType(Base.class.getName()), owner);
assertEquals("foo", name);
visitor.visitMethodInsn(
opcode,
getBinaryNameFromJavaType(Foo.class.getName()),
name,
descriptor,
isInterface);
} else {
visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
})
.transform();
}
static class Base {
// Base method is never hit.
public void foo(int i) {
System.out.println("Base::foo");
}
}
static class Foo extends Base {
public void foo(int i) {
if (i > 0) {
System.out.println("Foo::foo");
// Will be converted to invoke-special Foo.foo.
super.foo(i - 1);
}
}
}
static class Bar extends Foo {
// Subclass override is only hit initially.
@Override
public void foo(int i) {
System.out.println("Bar::foo");
super.foo(3);
}
}
static class TestClass {
public static void main(String[] args) {
new Bar().foo(0);
}
}
}