Reproduce b/112452064.
interface Super
interface Sub extends Super
class Impl implements Super // not Sub
v <- instance of Impl
invoke ...v mtd(...; Sub)
ART tries to verify the actual argument (v) is an instance of parameter
type Sub. R8 drops Sub as it is not implemented.
This broken type relation can happen:
interface Super { foo() }
interface Sub extends Super
v <- new Sub() { foo() { ... } }
invoke ...v mtd(...; Sub)
where that anonymous class is instantiated to a class definition,
implementing Sub's super interfaces directly.
Bug: 112452064
Change-Id: Icb24b6f5d7a18b1a917da64af94fb14f818fdedc
diff --git a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
new file mode 100644
index 0000000..97042b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
@@ -0,0 +1,291 @@
+// 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.shaking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+interface B112452064SuperInterface1 {
+ void foo();
+}
+
+interface B112452064SuperInterface2 {
+ void bar();
+}
+
+interface B112452064SubInterface extends B112452064SuperInterface1, B112452064SuperInterface2 {
+}
+
+class B112452064TestMain {
+
+ static void bazSuper1(B112452064SuperInterface1 instance) {
+ instance.foo();
+ }
+
+ static void bazSub(B112452064SubInterface instance) {
+ instance.foo();
+ }
+
+ public static void main(String[] args) {
+ bazSuper1(new B112452064SuperInterface1() {
+ @Override
+ public void foo() {
+ System.out.println("Anonymous1::foo");
+ }
+ });
+ bazSub(new B112452064SubInterface() {
+ @Override
+ public void foo() {
+ System.out.println("Anonymous2::foo");
+ }
+ @Override
+ public void bar() {
+ System.out.println("Anonymous2::bar");
+ }
+ });
+ }
+}
+
+@RunWith(VmTestRunner.class)
+public class ParameterTypeTest extends TestBase {
+
+ @Test
+ public void test_fromJavac() throws Exception {
+ String mainName = B112452064TestMain.class.getCanonicalName();
+ ProcessResult javaResult = ToolHelper.runJava(ToolHelper.getClassPathForTests(), mainName);
+ assertEquals(0, javaResult.exitCode);
+ assertThat(javaResult.stdout, containsString("Anonymous"));
+ assertThat(javaResult.stdout, containsString("::foo"));
+ assertEquals(-1, javaResult.stderr.indexOf("ClassNotFoundException"));
+
+ List<String> config = ImmutableList.of(
+ "-printmapping",
+ "-keep class " + mainName + " {",
+ " public static void main(...);",
+ "}"
+ );
+ R8Command.Builder builder = R8Command.builder();
+ builder.addProgramFiles(ToolHelper.getClassFilesForTestDirectory(
+ ToolHelper.getPackageDirectoryForTestPackage(B112452064TestMain.class.getPackage()),
+ path -> path.getFileName().toString().startsWith("B112452064")));
+ builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+ builder.addProguardConfiguration(config, Origin.unknown());
+ AndroidApp processedApp = ToolHelper.runR8(builder.build(), options -> {
+ options.enableInlining = false;
+ });
+
+ Path outDex = temp.getRoot().toPath().resolve("dex.zip");
+ processedApp.writeToZip(outDex, OutputMode.DexIndexed);
+ ProcessResult artResult = ToolHelper.runArtNoVerificationErrorsRaw(outDex.toString(), mainName);
+ assertEquals(0, artResult.exitCode);
+ assertEquals(javaResult.stdout, artResult.stdout);
+ assertEquals(-1, artResult.stderr.indexOf("ClassNotFoundException"));
+
+ CodeInspector inspector = new CodeInspector(processedApp);
+ ClassSubject superInterface1 = inspector.clazz(B112452064SuperInterface1.class);
+ assertThat(superInterface1, isRenamed());
+ MethodSubject foo = superInterface1.method("void", "foo", ImmutableList.of());
+ assertThat(foo, isRenamed());
+ ClassSubject superInterface2 = inspector.clazz(B112452064SuperInterface2.class);
+ assertThat(superInterface2, isRenamed());
+ MethodSubject bar = superInterface1.method("void", "bar", ImmutableList.of());
+ assertThat(bar, not(isPresent()));
+ ClassSubject subInterface = inspector.clazz(B112452064SubInterface.class);
+ assertThat(subInterface, isRenamed());
+ }
+
+ @Ignore("b/112452064")
+ @Test
+ public void test_brokenTypeHierarchy_singleInterface() throws Exception {
+ JasminBuilder jasminBuilder = new JasminBuilder();
+ // interface SuperInterface {
+ // void foo();
+ // }
+ ClassBuilder sup = jasminBuilder.addInterface("SuperInterface");
+ MethodSignature foo = sup.addAbstractMethod("foo", ImmutableList.of(), "V");
+ // interface SubInterface extends SuperInterface
+ ClassBuilder sub = jasminBuilder.addInterface("SubInterface", sup.name);
+
+ // class Foo implements SuperInterface /* supposed to implement SubInterface */
+ ClassBuilder impl = jasminBuilder.addClass("Foo", "java/lang/Object", sup.name);
+ impl.addDefaultConstructor();
+ impl.addVirtualMethod(foo.name, ImmutableList.of(), "V",
+ ".limit locals 2",
+ ".limit stack 2",
+ "getstatic java/lang/System/out Ljava/io/PrintStream;",
+ "ldc \"" + foo.name + "\"",
+ "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ "return");
+
+ // class TestMain {
+ // static bar(SubInterface instance) {
+ // // instance.foo();
+ // }
+ // public static void main(String[] args) {
+ // // ART verifies the argument (Foo) is an instance of the parameter type (SubInterface).
+ // bar(new Foo());
+ // }
+ // }
+ ClassBuilder mainClass = jasminBuilder.addClass("Main");
+ MethodSignature bar =
+ mainClass.addStaticMethod("bar", ImmutableList.of(sub.getDescriptor()), "V",
+ ".limit locals 2",
+ ".limit stack 2",
+ "getstatic java/lang/System/out Ljava/io/PrintStream;",
+ "ldc \"" + "bar" + "\"",
+ "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ "return");
+ mainClass.addMainMethod(
+ ".limit locals 1",
+ ".limit stack 2",
+ "new " + impl.name,
+ "dup",
+ "invokespecial " + impl.name + "/<init>()V",
+ "invokestatic " + mainClass.name + "/bar(" + sub.getDescriptor() + ")V",
+ "return");
+
+
+ final String mainClassName = mainClass.name;
+ String proguardConfig = keepMainProguardConfiguration(mainClassName, false, false);
+
+ // Run input program on java.
+ Path outputDirectory = temp.newFolder().toPath();
+ jasminBuilder.writeClassFiles(outputDirectory);
+ ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName);
+ assertEquals(0, javaResult.exitCode);
+ assertThat(javaResult.stdout, containsString(bar.name));
+ assertEquals(-1, javaResult.stderr.indexOf("ClassNotFoundException"));
+
+ AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig,
+ // Disable inlining to avoid the (short) tested method from being inlined and then removed.
+ internalOptions -> internalOptions.enableInlining = false);
+
+ // Run processed (output) program on ART
+ ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
+ assertEquals(0, artResult.exitCode);
+ assertThat(artResult.stdout, containsString(bar.name));
+ assertEquals(-1, artResult.stderr.indexOf("ClassNotFoundException"));
+
+ CodeInspector inspector = new CodeInspector(processedApp);
+ ClassSubject subSubject = inspector.clazz(sub.name);
+ assertThat(subSubject, isPresent());
+ }
+
+ @Ignore("b/112452064")
+ @Test
+ public void test_brokenTypeHierarchy_doubleInterfaces() throws Exception {
+ JasminBuilder jasminBuilder = new JasminBuilder();
+ // interface SuperInterface1 {
+ // void foo();
+ // }
+ ClassBuilder sup1 = jasminBuilder.addInterface("SuperInterface1");
+ MethodSignature foo = sup1.addAbstractMethod("foo", ImmutableList.of(), "V");
+ // interface SuperInterface2 {
+ // void bar();
+ // }
+ ClassBuilder sup2 = jasminBuilder.addInterface("SuperInterface2");
+ MethodSignature bar = sup1.addAbstractMethod("bar", ImmutableList.of(), "V");
+ // interface SubInterface extends SuperInterface1, SuperInterface2
+ ClassBuilder sub = jasminBuilder.addInterface("SubInterface", sup1.name, sup2.name);
+
+ // class Foo implements SuperInterface1, SuperInterface2
+ // /* supposed to implement SubInterface */
+ ClassBuilder impl = jasminBuilder.addClass("Foo", "java/lang/Object", sup1.name, sup2.name);
+ impl.addDefaultConstructor();
+ impl.addVirtualMethod(foo.name, ImmutableList.of(), "V",
+ ".limit locals 2",
+ ".limit stack 2",
+ "getstatic java/lang/System/out Ljava/io/PrintStream;",
+ "ldc \"" + foo.name + "\"",
+ "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ "return");
+ impl.addVirtualMethod(bar.name, ImmutableList.of(), "V",
+ ".limit locals 2",
+ ".limit stack 2",
+ "getstatic java/lang/System/out Ljava/io/PrintStream;",
+ "ldc \"" + bar.name + "\"",
+ "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ "return");
+
+ // class TestMain {
+ // static bar(SubInterface instance) {
+ // // instance.foo();
+ // }
+ // public static void main(String[] args) {
+ // // ART verifies the argument (Foo) is an instance of the parameter type (SubInterface).
+ // bar(new Foo());
+ // }
+ // }
+ ClassBuilder mainClass = jasminBuilder.addClass("Main");
+ MethodSignature baz =
+ mainClass.addStaticMethod("baz", ImmutableList.of(sub.getDescriptor()), "V",
+ ".limit locals 2",
+ ".limit stack 2",
+ "getstatic java/lang/System/out Ljava/io/PrintStream;",
+ "ldc \"" + "baz" + "\"",
+ "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ "return");
+ mainClass.addMainMethod(
+ ".limit locals 1",
+ ".limit stack 2",
+ "new " + impl.name,
+ "dup",
+ "invokespecial " + impl.name + "/<init>()V",
+ "invokestatic " + mainClass.name + "/baz(" + sub.getDescriptor() + ")V",
+ "return");
+
+ final String mainClassName = mainClass.name;
+ String proguardConfig = keepMainProguardConfiguration(mainClassName, false, false);
+
+ // Run input program on java.
+ Path outputDirectory = temp.newFolder().toPath();
+ jasminBuilder.writeClassFiles(outputDirectory);
+ ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName);
+ assertEquals(0, javaResult.exitCode);
+ assertThat(javaResult.stdout, containsString(baz.name));
+ assertEquals(-1, javaResult.stderr.indexOf("ClassNotFoundException"));
+
+ AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig,
+ // Disable inlining to avoid the (short) tested method from being inlined and then removed.
+ internalOptions -> internalOptions.enableInlining = false);
+
+ // Run processed (output) program on ART
+ ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
+ assertEquals(0, artResult.exitCode);
+ assertThat(artResult.stdout, containsString(baz.name));
+ assertEquals(javaResult.stdout, artResult.stdout);
+ assertEquals(-1, artResult.stderr.indexOf("ClassNotFoundException"));
+
+ CodeInspector inspector = new CodeInspector(processedApp);
+ ClassSubject subSubject = inspector.clazz(sub.name);
+ assertThat(subSubject, isPresent());
+ }
+}