blob: 96b135824f47d1ba970cd4ce322420422aa8be2b [file] [log] [blame]
// Copyright (c) 2018, 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.desugaring.interfacemethods;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.AsmTestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.VmTestRunner;
import com.android.tools.r8.VmTestRunner.IgnoreForRangeOfVmVersions;
import com.android.tools.r8.desugaring.interfacemethods.default0.TestMainDefault0;
import com.android.tools.r8.desugaring.interfacemethods.default1.Derived1;
import com.android.tools.r8.desugaring.interfacemethods.default1.DerivedComparator1;
import com.android.tools.r8.desugaring.interfacemethods.default1.TestMainDefault1;
import com.android.tools.r8.desugaring.interfacemethods.default2.Derived2;
import com.android.tools.r8.desugaring.interfacemethods.default2.DerivedComparator2;
import com.android.tools.r8.desugaring.interfacemethods.default2.TestMainDefault2;
import com.android.tools.r8.desugaring.interfacemethods.static0.TestMainStatic0;
import com.android.tools.r8.desugaring.interfacemethods.static1.TestMainStatic1;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@RunWith(VmTestRunner.class)
public class InterfaceMethodDesugaringTests extends AsmTestBase {
private static List<String> getArgs(int startWith) {
return Collections.singletonList(
String.valueOf(ToolHelper.getMinApiLevelForDexVm().getLevel() >= startWith));
}
@Test
public void testInvokeSpecialToDefaultMethod() throws Exception {
ensureSameOutput(
com.android.tools.r8.desugaring.interfacemethods.test0.TestMain.class.getCanonicalName(),
ToolHelper.getMinApiLevelForDexVm(),
ToolHelper.getClassAsBytes(
com.android.tools.r8.desugaring.interfacemethods.test0.TestMain.class),
patchInterfaceWithDefaults(ToolHelper.getClassAsBytes(
com.android.tools.r8.desugaring.interfacemethods.test0.InterfaceWithDefaults.class)));
}
@Test
public void testInvokeSpecialToDefaultMethodFromStatic() throws Exception {
ensureSameOutput(
com.android.tools.r8.desugaring.interfacemethods.test1.TestMain.class.getCanonicalName(),
ToolHelper.getMinApiLevelForDexVm(),
ToolHelper.getClassAsBytes(
com.android.tools.r8.desugaring.interfacemethods.test1.TestMain.class),
patchInterfaceWithDefaults(ToolHelper.getClassAsBytes(
com.android.tools.r8.desugaring.interfacemethods.test1.InterfaceWithDefaults.class)));
}
@Test
public void testInvokeSpecialToInheritedDefaultMethod() throws Exception {
ensureSameOutput(
com.android.tools.r8.desugaring.interfacemethods.test2.TestMain.class.getCanonicalName(),
ToolHelper.getMinApiLevelForDexVm(),
ToolHelper.getClassAsBytes(
com.android.tools.r8.desugaring.interfacemethods.test2.TestMain.class),
ToolHelper.getClassAsBytes(
com.android.tools.r8.desugaring.interfacemethods.test2.Test.class),
ToolHelper.getClassAsBytes(
com.android.tools.r8.desugaring.interfacemethods.test2.LeftTest.class),
ToolHelper.getClassAsBytes(
com.android.tools.r8.desugaring.interfacemethods.test2.RightTest.class),
ToolHelper.getClassAsBytes(
com.android.tools.r8.desugaring.interfacemethods.test2.Test2.class));
}
@Test
public void testInvokeStatic0a() throws Exception {
ensureSameOutput(
TestMainStatic0.class.getCanonicalName(),
AndroidApiLevel.K,
getArgs(AndroidApiLevel.N.getLevel()),
ToolHelper.getClassAsBytes(TestMainStatic0.class));
}
@Test
public void testInvokeStatic0b() throws Exception {
ensureSameOutput(
TestMainStatic0.class.getCanonicalName(),
ToolHelper.getMinApiLevelForDexVm(),
getArgs(AndroidApiLevel.N.getLevel()),
ToolHelper.getClassAsBytes(TestMainStatic0.class));
}
@Test
public void testInvokeStatic1a() throws Exception {
ensureSameOutput(
TestMainStatic1.class.getCanonicalName(),
AndroidApiLevel.K,
getArgs(AndroidApiLevel.N.getLevel()),
ToolHelper.getClassAsBytes(TestMainStatic1.class));
}
@Test
public void testInvokeStatic1b() throws Exception {
ensureSameOutput(
TestMainStatic1.class.getCanonicalName(),
ToolHelper.getMinApiLevelForDexVm(),
getArgs(AndroidApiLevel.N.getLevel()),
ToolHelper.getClassAsBytes(TestMainStatic1.class));
}
@Test
public void testInvokeDefault0a() throws Exception {
ensureSameOutput(
TestMainDefault0.class.getCanonicalName(),
AndroidApiLevel.K,
getArgs(AndroidApiLevel.N.getLevel()),
ToolHelper.getClassAsBytes(TestMainDefault0.class));
}
@Test
public void testInvokeDefault0b() throws Exception {
ensureSameOutput(
TestMainDefault0.class.getCanonicalName(),
ToolHelper.getMinApiLevelForDexVm(),
getArgs(AndroidApiLevel.N.getLevel()),
ToolHelper.getClassAsBytes(TestMainDefault0.class));
}
@Test
@IgnoreForRangeOfVmVersions(from = Version.V7_0_0, to = Version.V14_0_0) // No desugaring
public void testInvokeDefault1() throws Exception {
ensureCustomCheck(
(javaResult, d8Result, r8Result, r8ShakenResult) -> {
Assert.assertEquals(1, d8Result.exitCode);
Assert.assertTrue(d8Result.stderr.contains("NoSuchMethodError"));
Assert.assertEquals(1, r8Result.exitCode);
Assert.assertTrue(r8Result.stderr.contains("NoSuchMethodError"));
// R8 can determine that the super interface invokes are all overridden in the program
Assert.assertEquals(javaResult.stdout, r8ShakenResult.stdout);
},
TestMainDefault1.class.getCanonicalName(),
ToolHelper.getMinApiLevelForDexVm(),
getArgs(AndroidApiLevel.N.getLevel()),
ToolHelper.getClassAsBytes(TestMainDefault1.class),
ToolHelper.getClassAsBytes(Derived1.class),
ToolHelper.getClassAsBytes(DerivedComparator1.class));
}
@Test()
public void testInvokeDefault2a() throws Exception {
ensureSameOutput(
TestMainDefault2.class.getCanonicalName(),
AndroidApiLevel.K,
getArgs(AndroidApiLevel.N.getLevel()),
ToolHelper.getClassAsBytes(TestMainDefault2.class),
ToolHelper.getClassAsBytes(Derived2.class),
ToolHelper.getClassAsBytes(DerivedComparator2.class));
}
@Test()
public void testInvokeDefault2b() throws Exception {
ensureSameOutput(
TestMainDefault2.class.getCanonicalName(),
ToolHelper.getMinApiLevelForDexVm(),
getArgs(AndroidApiLevel.N.getLevel()),
ToolHelper.getClassAsBytes(TestMainDefault2.class),
ToolHelper.getClassAsBytes(Derived2.class),
ToolHelper.getClassAsBytes(DerivedComparator2.class));
}
private static class MutableInteger {
int value;
}
private byte[] patchInterfaceWithDefaults(byte[] classBytes) throws IOException {
MutableInteger patched = new MutableInteger();
try (InputStream input = new ByteArrayInputStream(classBytes)) {
ClassReader cr = new ClassReader(input);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
cr.accept(
new ClassVisitor(InternalOptions.ASM_VERSION, cw) {
@Override
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
MethodVisitor visitor = super.visitMethod(access, name, desc, signature, exceptions);
return new MethodVisitor(InternalOptions.ASM_VERSION, visitor) {
@Override
public void visitMethodInsn(
int opcode, String owner, String name, String desc, boolean itf) {
if (opcode == Opcodes.INVOKEINTERFACE &&
owner.endsWith("InterfaceWithDefaults") &&
name.equals("foo")) {
assertEquals(0, patched.value);
super.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, itf);
patched.value++;
} else {
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
};
}
}, 0);
assertEquals(1, patched.value);
return cw.toByteArray();
}
}
}