Version 1.2.40

Merge: Mark argument types as live.
CL: https://r8-review.googlesource.com/c/r8/+/25120

Merge: Reproduce b/112452064.
CL: https://r8-review.googlesource.com/c/r8/+/25000

Bug: 112452064
Change-Id: Icb24b6f5d7a18b1a917da64af94fb14f818fdedc
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 10d19a5..7fdd393 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.2.39";
+  public static final String LABEL = "1.2.40";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 2b51720..42f9174 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -785,8 +785,12 @@
     // Mark the type live here, so that the class exists at runtime. Note that this also marks all
     // supertypes as live, so even if the field is actually on a supertype, its class will be live.
     markTypeAsLive(field.clazz);
-    if (field.type.isClassType()) {
-      markTypeAsLive(field.type);
+    DexType fieldType = field.type;
+    if (fieldType.isArrayType()) {
+      fieldType = fieldType.toBaseType(appInfo.dexItemFactory);
+    }
+    if (fieldType.isClassType()) {
+      markTypeAsLive(fieldType);
     }
     // Find the actual field.
     DexEncodedField encodedField = appInfo.resolveFieldOn(field.clazz, field);
@@ -817,8 +821,12 @@
   private void markInstanceFieldAsLive(DexEncodedField field, KeepReason reason) {
     assert field != null;
     markTypeAsLive(field.field.clazz);
-    if (field.field.type.isClassType()) {
-      markTypeAsLive(field.field.type);
+    DexType fieldType = field.field.type;
+    if (fieldType.isArrayType()) {
+      fieldType = fieldType.toBaseType(appInfo.dexItemFactory);
+    }
+    if (fieldType.isClassType()) {
+      markTypeAsLive(fieldType);
     }
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Adding instance field `%s` to live set.", field.field);
@@ -1246,6 +1254,14 @@
           markVirtualMethodAsLive(superCallTarget, KeepReason.invokedViaSuperFrom(method));
         }
       }
+      for (DexType parameterType : method.method.proto.parameters.values) {
+        if (parameterType.isArrayType()) {
+          parameterType = parameterType.toBaseType(appInfo.dexItemFactory);
+        }
+        if (parameterType.isClassType()) {
+          markTypeAsLive(parameterType);
+        }
+      }
       processAnnotations(method.annotations.annotations);
       method.parameterAnnotationsList.forEachAnnotation(this::processAnnotation);
       if (protoLiteExtension != null && protoLiteExtension.appliesTo(method)) {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index d5d0558..cfeb11d 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -63,6 +63,7 @@
 import java.util.concurrent.Executors;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import org.junit.Assume;
 import org.junit.rules.TemporaryFolder;
@@ -747,9 +748,17 @@
   }
 
   public static List<Path> getClassFilesForTestPackage(Package pkg) throws IOException {
-    Path dir = ToolHelper.getPackageDirectoryForTestPackage(pkg);
-    return Files.walk(dir)
-        .filter(path -> path.toString().endsWith(".class"))
+    return getClassFilesForTestDirectory(ToolHelper.getPackageDirectoryForTestPackage(pkg));
+  }
+
+  public static List<Path> getClassFilesForTestDirectory(Path directory) throws IOException {
+    return getClassFilesForTestDirectory(directory, null);
+  }
+
+  public static List<Path> getClassFilesForTestDirectory(
+      Path directory, Predicate<Path> filter) throws IOException {
+    return Files.walk(directory)
+        .filter(path -> path.toString().endsWith(".class") && (filter == null || filter.test(path)))
         .collect(Collectors.toList());
   }
 
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..00af5f7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
@@ -0,0 +1,288 @@
+// 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.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.DexInspectorMatchers.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.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+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"));
+
+    DexInspector inspector = new DexInspector(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());
+  }
+
+  @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"));
+
+    DexInspector inspector = new DexInspector(processedApp);
+    ClassSubject subSubject = inspector.clazz(sub.name);
+    assertThat(subSubject, isPresent());
+  }
+
+  @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"));
+
+    DexInspector inspector = new DexInspector(processedApp);
+    ClassSubject subSubject = inspector.clazz(sub.name);
+    assertThat(subSubject, isPresent());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
index cae3b06..81fd6b0 100644
--- a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
@@ -94,10 +94,11 @@
 
       Result result = runTest(mainClass, proguardConfig);
 
-      // Without includedescriptorclasses return type and argument type are removed.
+      // Without includedescriptorclasses return type is removed.
       result.assertKept(ClassWithNativeMethods.class);
-      result.assertRemoved(NativeArgumentType.class);
       result.assertRemoved(NativeReturnType.class);
+      // Argument type is not removed due to the concern about the broken type hierarchy.
+      result.assertRenamed(NativeArgumentType.class);
       // Field type is not removed due to the concern about the broken type hierarchy.
       result.assertRenamed(InstanceFieldType.class);
       result.assertRenamed(StaticFieldType.class);