Version 2.0.47

Cherry pick: Determine the use of not mapped types lazily for
applymapping
CL: https://r8-review.googlesource.com/49480

Bug: 149946708
Change-Id: Ia8f168e43e4889fd2f8173da91270b3d0267345e
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index c54e218..edc6674 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 = "2.0.46";
+  public static final String LABEL = "2.0.47";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 993363c..3d8d17a 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -246,7 +246,9 @@
       if (options.encodeChecksums) {
         encodeChecksums(virtualFiles);
       }
+      namingLens.setIsSortingBeforeWriting(true);
       application.dexItemFactory.sort(namingLens);
+      namingLens.setIsSortingBeforeWriting(false);
       assert markers == null
           || markers.isEmpty()
           || application.dexItemFactory.extractMarkers() != null;
diff --git a/src/main/java/com/android/tools/r8/naming/ApplyMappingError.java b/src/main/java/com/android/tools/r8/naming/ApplyMappingError.java
index 7b76acb..2ef940e 100644
--- a/src/main/java/com/android/tools/r8/naming/ApplyMappingError.java
+++ b/src/main/java/com/android/tools/r8/naming/ApplyMappingError.java
@@ -14,8 +14,9 @@
       "'%s' cannot be mapped to '%s' because it is in conflict with an existing ";
   private static final String EXISTING_MESSAGE_END =
       ". This usually happens when compiling a test application against a source application and "
-          + "having short generic names in the test application. Try giving '%s' a more specific "
-          + "name or add a keep rule to keep '%s'.";
+          + "there are used classes in the test that was not given a -keep rule when compiling the "
+          + "app. Try either renaming '%s' such that it will not collide or add a keep rule to "
+          + "keep '%s'.";
 
   protected static final String EXISTING_CLASS_MESSAGE =
       EXISTING_MESSAGE_START + "class with the same name" + EXISTING_MESSAGE_END;
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index 0c85641..3c599e6 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -33,7 +33,7 @@
 
 class MinifiedRenaming extends NamingLens {
 
-  private final AppView<?> appView;
+  final AppView<?> appView;
   private final Map<String, String> packageRenaming;
   private final Map<DexItem, DexString> renaming = new IdentityHashMap<>();
 
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index f1a7d84..d1c195f 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -41,6 +41,8 @@
  */
 public abstract class NamingLens {
 
+  protected boolean isSortingBeforeWriting;
+
   public abstract String lookupPackageName(String packageName);
 
   public abstract DexString lookupDescriptor(DexType type);
@@ -163,6 +165,10 @@
     return true;
   }
 
+  public void setIsSortingBeforeWriting(boolean isSorting) {
+    isSortingBeforeWriting = isSorting;
+  }
+
   private static class IdentityLens extends NamingLens {
 
     private IdentityLens() {
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 4fd6274..1a07b28 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -37,6 +37,8 @@
 import com.google.common.collect.Maps;
 import java.util.ArrayDeque;
 import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
@@ -92,6 +94,7 @@
   public NamingLens run(ExecutorService executorService, Timing timing) throws ExecutionException {
 
     ArrayDeque<Map<DexReference, MemberNaming>> nonPrivateMembers = new ArrayDeque<>();
+    Set<DexReference> notMappedReferences = new HashSet<>();
 
     timing.begin("MappingInterfaces");
     Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type));
@@ -100,7 +103,7 @@
       if (dexClass.isInterface()) {
         // Only visit top level interfaces because computeMapping will visit the hierarchy.
         if (dexClass.interfaces.isEmpty()) {
-          computeMapping(dexClass.type, nonPrivateMembers);
+          computeMapping(dexClass.type, nonPrivateMembers, notMappedReferences);
         }
         interfaces.add(dexClass);
       }
@@ -114,7 +117,7 @@
         subType -> {
           DexClass dexClass = appView.definitionFor(subType);
           if (dexClass != null && !dexClass.isInterface()) {
-            computeMapping(subType, nonPrivateMembers);
+            computeMapping(subType, nonPrivateMembers, notMappedReferences);
           }
         });
     assert nonPrivateMembers.isEmpty();
@@ -160,7 +163,9 @@
 
     appView.options().reporter.failIfPendingErrors();
 
-    NamingLens lens = new MinifiedRenaming(appView, classRenaming, methodRenaming, fieldRenaming);
+    NamingLens lens =
+        new ProguardMapMinifiedRenaming(
+            appView, classRenaming, methodRenaming, fieldRenaming, notMappedReferences);
 
     timing.begin("MinifyIdentifiers");
     new IdentifierMinifier(appView, lens).run(executorService);
@@ -173,7 +178,10 @@
     return lens;
   }
 
-  private void computeMapping(DexType type, Deque<Map<DexReference, MemberNaming>> buildUpNames) {
+  private void computeMapping(
+      DexType type,
+      Deque<Map<DexReference, MemberNaming>> buildUpNames,
+      Set<DexReference> notMappedReferences) {
     ClassNamingForMapApplier classNaming = seedMapper.getClassNaming(type);
     DexClass dexClass = appView.definitionFor(type);
 
@@ -191,16 +199,15 @@
       if (dexClass != null) {
         KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, dexClass);
       }
-
       classNaming.forAllMemberNaming(
           memberNaming -> addMemberNamings(type, memberNaming, nonPrivateMembers, false));
     } else {
       // We have to ensure we do not rename to an existing member, that cannot be renamed.
       if (dexClass == null || !appView.options().isMinifying()) {
-        checkAndAddMappedNames(type, type.descriptor, Position.UNKNOWN);
+        notMappedReferences.add(type);
       } else if (appView.options().isMinifying()
           && appView.rootSet().mayNotBeMinified(type, appView)) {
-        checkAndAddMappedNames(type, type.descriptor, Position.UNKNOWN);
+        notMappedReferences.add(type);
       }
     }
 
@@ -249,12 +256,14 @@
       buildUpNames.addLast(nonPrivateMembers);
       appView
           .appInfo()
-          .forAllImmediateExtendsSubtypes(type, subType -> computeMapping(subType, buildUpNames));
+          .forAllImmediateExtendsSubtypes(
+              type, subType -> computeMapping(subType, buildUpNames, notMappedReferences));
       buildUpNames.removeLast();
     } else {
       appView
           .appInfo()
-          .forAllImmediateExtendsSubtypes(type, subType -> computeMapping(subType, buildUpNames));
+          .forAllImmediateExtendsSubtypes(
+              type, subType -> computeMapping(subType, buildUpNames, notMappedReferences));
     }
   }
 
@@ -555,4 +564,52 @@
       // reporter.error(applyMappingError);
     }
   }
+
+  public static class ProguardMapMinifiedRenaming extends MinifiedRenaming {
+
+    private final Set<DexReference> unmappedReferences;
+    private final Map<DexString, DexType> classRenamingsMappingToDifferentName;
+
+    ProguardMapMinifiedRenaming(
+        AppView<?> appView,
+        ClassRenaming classRenaming,
+        MethodRenaming methodRenaming,
+        FieldRenaming fieldRenaming,
+        Set<DexReference> unmappedReferences) {
+      super(appView, classRenaming, methodRenaming, fieldRenaming);
+      this.unmappedReferences = unmappedReferences;
+      classRenamingsMappingToDifferentName = new HashMap<>();
+      classRenaming.classRenaming.forEach(
+          (type, dexString) -> {
+            if (type.descriptor != dexString) {
+              classRenamingsMappingToDifferentName.put(dexString, type);
+            }
+          });
+    }
+
+    @Override
+    public DexString lookupDescriptor(DexType type) {
+      if (!isSortingBeforeWriting) {
+        checkForUseOfNotMappedReference(type);
+      }
+      return super.lookupDescriptor(type);
+    }
+
+    private void checkForUseOfNotMappedReference(DexType type) {
+      if (unmappedReferences.contains(type)
+          && classRenamingsMappingToDifferentName.containsKey(type.descriptor)) {
+        // Type is an unmapped reference and there is a mapping from some other type to this one.
+        // We are emitting a warning here, since this will generally be undesired behavior.
+        DexType mappedType = classRenamingsMappingToDifferentName.get(type.descriptor);
+        appView
+            .options()
+            .reporter
+            .error(
+                ApplyMappingError.mapToExistingClass(
+                    mappedType.toString(), type.toSourceString(), Position.UNKNOWN));
+        // Remove the type to ensure us only reporting the error once.
+        unmappedReferences.remove(type);
+      }
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/ClassNameMinifierOriginalClassNameTest.java b/src/test/java/com/android/tools/r8/naming/ClassNameMinifierOriginalClassNameTest.java
new file mode 100644
index 0000000..8d6e7c5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/ClassNameMinifierOriginalClassNameTest.java
@@ -0,0 +1,124 @@
+// 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.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.testclasses.A;
+import com.android.tools.r8.naming.testclasses.B;
+import com.android.tools.r8.utils.FileUtils;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** This test is designed to test the workaround for b/149946708. */
+@RunWith(Parameterized.class)
+public class ClassNameMinifierOriginalClassNameTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassNameMinifierOriginalClassNameTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static Function<TestParameters, R8TestCompileResult> compilationResults =
+      memoizeFunction(ClassNameMinifierOriginalClassNameTest::compile);
+
+  private static R8TestCompileResult compile(TestParameters parameters)
+      throws CompilationFailedException, IOException, ExecutionException {
+    // Adding the obfuscation dictionary just ensures that we assign a name to B that will collide
+    // independent of minification scheme.
+    Path dictionary = getStaticTemp().newFolder().toPath().resolve("dictionary.txt");
+    FileUtils.writeTextFile(dictionary, "A");
+    return testForR8(getStaticTemp(), parameters.getBackend())
+        .addProgramClasses(A.class, B.class)
+        .addKeepClassAndMembersRulesWithAllowObfuscation(B.class)
+        .addKeepRules("-classobfuscationdictionary " + dictionary.toString(), "-keeppackagenames")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              assertEquals(1, inspector.allClasses().size());
+              assertThat(inspector.clazz(B.class), isRenamed());
+              assertEquals(A.class.getTypeName(), inspector.clazz(B.class).getFinalName());
+            });
+  }
+
+  @Test
+  public void testR8() throws ExecutionException, CompilationFailedException, IOException {
+    R8TestCompileResult libraryCompileResult = compilationResults.apply(parameters);
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .noMinification()
+        .addClasspathClasses(A.class, B.class)
+        .addApplyMapping(libraryCompileResult.getProguardMap())
+        .compile()
+        .addRunClasspathFiles(libraryCompileResult.writeToZip())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("B.foo");
+  }
+
+  @Test
+  public void testR8WithReferenceToNotMapped() {
+    assumeTrue(parameters.isDexRuntime());
+    R8TestCompileResult libraryCompileResult = compilationResults.apply(parameters);
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForR8(parameters.getBackend())
+                .addProgramClasses(MainWithReferenceToNotMapped.class)
+                .setMinApi(parameters.getApiLevel())
+                .addKeepMainRule(MainWithReferenceToNotMapped.class)
+                .noMinification()
+                .addClasspathClasses(A.class, B.class)
+                .addApplyMapping(libraryCompileResult.getProguardMap())
+                .compileWithExpectedDiagnostics(
+                    diagnosticMessages ->
+                        diagnosticMessages.assertErrorMessageThatMatches(
+                            containsString(
+                                "'"
+                                    + B.class.getTypeName()
+                                    + "' cannot be mapped to '"
+                                    + A.class.getTypeName()
+                                    + "' because it is in conflict"))));
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new B().foo();
+    }
+  }
+
+  public static class MainWithReferenceToNotMapped {
+
+    public static void main(String[] args) {
+      System.out.println(new A());
+      new B().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java
index 0b37f73..3466e21 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java
@@ -401,21 +401,6 @@
   }
 
   @Test
-  public void testR8_originalLibClassRenamedToExistingName() throws Exception {
-    FileUtils.writeTextFile(mappingFile, mappingToExistingClassName());
-    try {
-      testR8_originalLibraryJar(mappingFile);
-      fail("Expect compilation failure.");
-    } catch (CompilationFailedException e) {
-      assertThat(e.getCause().getMessage(), containsString("cannot be mapped to"));
-      assertThat(
-          e.getCause().getMessage(),
-          containsString("because it is in conflict with an existing class with the same name."));
-      assertThat(e.getCause().getMessage(), containsString(ProgramClass.class.getTypeName()));
-    }
-  }
-
-  @Test
   public void testProguard_minifiedLib() throws Exception {
     FileUtils.writeTextFile(mappingFile, invertedMapping());
     try {
diff --git a/src/test/java/com/android/tools/r8/naming/testclasses/A.java b/src/test/java/com/android/tools/r8/naming/testclasses/A.java
new file mode 100644
index 0000000..28a93a6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/testclasses/A.java
@@ -0,0 +1,7 @@
+// 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.naming.testclasses;
+
+public class A {}
diff --git a/src/test/java/com/android/tools/r8/naming/testclasses/B.java b/src/test/java/com/android/tools/r8/naming/testclasses/B.java
new file mode 100644
index 0000000..a364510
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/testclasses/B.java
@@ -0,0 +1,12 @@
+// 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.naming.testclasses;
+
+public class B extends A {
+
+  public void foo() {
+    System.out.println("B.foo");
+  }
+}