Support for adding synthetic classpath classes to lazy app

This is used to retain the classes that D8 synthesizes on the classpath as a result of desugaring. These classes are needed in R8 partial to avoid missing class errors.

Change-Id: Id81b86a03cc0a03a3246c47843b32ee4ea5a5761
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index bed4416..f993bbb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -257,6 +257,8 @@
       return self();
     }
 
+    public abstract T addClasspathClass(DexClasspathClass clazz);
+
     public List<DexProgramClass> getProgramClasses() {
       return programClasses;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
index 57225a5..8a4946c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
@@ -66,7 +66,9 @@
         annotations,
         origin,
         skipNameValidationForTesting);
-    assert kind == Kind.CF : "Invalid kind " + kind + " for class-path class " + type;
+    // Allow moving synthetic classes to the classpath (R8 partial only).
+    assert kind == null || kind == Kind.CF
+        : "Invalid kind " + kind + " for class-path class " + type;
   }
 
   public static DexClasspathClass toClasspathClass(DexProgramClass programClass) {
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 3e0ccb4..f61ce8e 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -260,11 +260,17 @@
       return this;
     }
 
+    @Override
     public Builder addClasspathClass(DexClasspathClass clazz) {
       pendingClasspathClasses.add(clazz);
       return self();
     }
 
+    public Builder addClasspathClasses(Collection<DexClasspathClass> classes) {
+      pendingClasspathClasses.addAll(classes);
+      return self();
+    }
+
     private void commitPendingClasspathClasses() {
       if (!pendingClasspathClasses.isEmpty()) {
         classpathClasses =
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index 0fc1f96..2652634 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -19,6 +19,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
 import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -31,6 +32,7 @@
 
   private final ProgramClassCollection programClasses;
   private final ClasspathClassCollection classpathClasses;
+  private final Map<DexType, DexClasspathClass> synthesizedClasspathClasses;
   private final LibraryClassCollection libraryClasses;
   private final List<KeepDeclaration> keepDeclarations;
 
@@ -41,6 +43,7 @@
       ProgramClassCollection programClasses,
       ImmutableList<DataResourceProvider> dataResourceProviders,
       ClasspathClassCollection classpathClasses,
+      Map<DexType, DexClasspathClass> synthesizedClasspathClasses,
       LibraryClassCollection libraryClasses,
       List<KeepDeclaration> keepDeclarations,
       InternalOptions options,
@@ -48,6 +51,7 @@
     super(proguardMap, flags, dataResourceProviders, options, timing);
     this.programClasses = programClasses;
     this.classpathClasses = classpathClasses;
+    this.synthesizedClasspathClasses = synthesizedClasspathClasses;
     this.libraryClasses = libraryClasses;
     this.keepDeclarations = keepDeclarations;
   }
@@ -58,8 +62,7 @@
 
   @Override
   List<DexProgramClass> programClasses() {
-    programClasses.forceLoad(t -> true);
-    return programClasses.getAllClasses();
+    return programClasses.forceLoad().getAllClasses();
   }
 
   @Override
@@ -82,8 +85,20 @@
         || !addClassToBuilderIfNotNull(programClasses.get(type), builder::add)) {
       // When looking up a type that exists both on program path and classpath, we assume the
       // program class is taken and only if not present will look at classpath.
+      DexClasspathClass classpathClass;
       if (classpathClasses != null) {
-        addClassToBuilderIfNotNull(classpathClasses.get(type), builder::add);
+        classpathClass = classpathClasses.get(type);
+        addClassToBuilderIfNotNull(classpathClass, builder::add);
+      } else {
+        classpathClass = null;
+      }
+
+      if (synthesizedClasspathClasses != null) {
+        if (classpathClass == null) {
+          addClassToBuilderIfNotNull(synthesizedClasspathClasses.get(type), builder::add);
+        } else {
+          assert !synthesizedClasspathClasses.containsKey(type);
+        }
       }
     }
     return builder.build();
@@ -105,6 +120,9 @@
     if (clazz == null && classpathClasses != null) {
       clazz = classpathClasses.get(type);
     }
+    if (clazz == null && synthesizedClasspathClasses != null) {
+      clazz = synthesizedClasspathClasses.get(type);
+    }
     if (clazz == null && libraryClasses != null) {
       clazz = libraryClasses.get(type);
     }
@@ -129,6 +147,7 @@
     AllClasses(
         LibraryClassCollection libraryClassesLoader,
         ClasspathClassCollection classpathClassesLoader,
+        Map<DexType, DexClasspathClass> synthesizedClasspathClasses,
         ProgramClassCollection programClassesLoader,
         InternalOptions options) {
 
@@ -152,18 +171,20 @@
       // Program classes should be fully loaded.
       assert programClassesLoader != null;
       assert programClassesLoader.isFullyLoaded();
-      programClassesLoader.forceLoad(type -> true);
       ImmutableMap<DexType, DexProgramClass> allProgramClasses =
-          programClassesLoader.getAllClassesInMap();
+          programClassesLoader.forceLoad().getAllClassesInMap();
 
       // Force-load classpath classes.
-      ImmutableMap<DexType, DexClasspathClass> allClasspathClasses;
+      ImmutableMap.Builder<DexType, DexClasspathClass> allClasspathClassesBuilder =
+          ImmutableMap.builder();
       if (classpathClassesLoader != null) {
-        classpathClassesLoader.forceLoad(type -> true);
-        allClasspathClasses = classpathClassesLoader.getAllClassesInMap();
-      } else {
-        allClasspathClasses = ImmutableMap.of();
+        classpathClassesLoader.forceLoad().forEach(allClasspathClassesBuilder::put);
       }
+      if (synthesizedClasspathClasses != null) {
+        allClasspathClassesBuilder.putAll(synthesizedClasspathClasses);
+      }
+      ImmutableMap<DexType, DexClasspathClass> allClasspathClasses =
+          allClasspathClassesBuilder.build();
 
       // Collect loaded classes in the precedence order library classes, program classes and
       // class path classes or program classes, classpath classes and library classes depending
@@ -254,24 +275,28 @@
    * Force load all classes and return type -> class map containing all the classes.
    */
   public AllClasses loadAllClasses() {
-    return new AllClasses(libraryClasses, classpathClasses, programClasses, options);
+    return new AllClasses(
+        libraryClasses, classpathClasses, synthesizedClasspathClasses, programClasses, options);
   }
 
   public static class Builder extends DexApplication.Builder<Builder> {
 
     private ClasspathClassCollection classpathClasses;
+    private Map<DexType, DexClasspathClass> synthesizedClasspathClasses;
     private LibraryClassCollection libraryClasses;
     private List<KeepDeclaration> keepDeclarations = Collections.emptyList();
 
     Builder(InternalOptions options, Timing timing) {
       super(options, timing);
       this.classpathClasses = ClasspathClassCollection.empty();
+      this.synthesizedClasspathClasses = null;
       this.libraryClasses = LibraryClassCollection.empty();
     }
 
     private Builder(LazyLoadedDexApplication application) {
       super(application);
       this.classpathClasses = application.classpathClasses;
+      this.synthesizedClasspathClasses = application.synthesizedClasspathClasses;
       this.libraryClasses = application.libraryClasses;
     }
 
@@ -303,6 +328,17 @@
     }
 
     @Override
+    public Builder addClasspathClass(DexClasspathClass clazz) {
+      if (synthesizedClasspathClasses == null) {
+        synthesizedClasspathClasses = new IdentityHashMap<>();
+      }
+      assert classpathClasses.get(clazz.getType()) == null;
+      assert !synthesizedClasspathClasses.containsKey(clazz.getType());
+      synthesizedClasspathClasses.put(clazz.getType(), clazz);
+      return this;
+    }
+
+    @Override
     public LazyLoadedDexApplication build() {
       ProgramClassConflictResolver resolver =
           options.programClassConflictResolver == null
@@ -314,6 +350,7 @@
           ProgramClassCollection.create(getProgramClasses(), resolver),
           ImmutableList.copyOf(dataResourceProviders),
           classpathClasses,
+          synthesizedClasspathClasses,
           libraryClasses,
           keepDeclarations,
           options,
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 146633f..17e5a83 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -1203,9 +1203,9 @@
             } else {
               appBuilder.addProgramClass(definition.asProgramDefinition().getHolder());
             }
-          } else if (appBuilder.isDirect()) {
+          } else {
             assert definition.isClasspathDefinition();
-            appBuilder.asDirect().addClasspathClass(definition.asClasspathDefinition().getHolder());
+            appBuilder.addClasspathClass(definition.asClasspathDefinition().getHolder());
           }
           builder.addItem(definition);
         }
diff --git a/src/main/java/com/android/tools/r8/utils/ClassMap.java b/src/main/java/com/android/tools/r8/utils/ClassMap.java
index ea5e988..6871538 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassMap.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassMap.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import static com.google.common.base.Predicates.alwaysTrue;
+
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.ClassKind;
@@ -19,6 +21,7 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 
@@ -148,15 +151,17 @@
   }
 
   public ImmutableMap<DexType, T> getAllClassesInMap() {
+    ImmutableMap.Builder<DexType, T> builder = ImmutableMap.builder();
+    // This is fully loaded, so the class map will no longer change.
+    forEach(builder::put);
+    return builder.build();
+  }
+
+  public void forEach(BiConsumer<DexType, T> consumer) {
     if (classProvider.get() != null) {
       throw new Unreachable("Getting all classes from not fully loaded collection.");
     }
-    ImmutableMap.Builder<DexType, T> builder = ImmutableMap.builder();
-    // This is fully loaded, so the class map will no longer change.
-    for (Map.Entry<DexType, Supplier<T>> entry : classes.entrySet()) {
-      builder.put(entry.getKey(), entry.getValue().get());
-    }
-    return builder.build();
+    classes.forEach((type, supplier) -> consumer.accept(type, supplier.get()));
   }
 
   public Iterable<DexType> getAllTypes() {
@@ -172,6 +177,10 @@
         "Cannot access all types since the classProvider is no longer available");
   }
 
+  public ClassMap<T> forceLoad() {
+    return forceLoad(alwaysTrue());
+  }
+
   /**
    * Forces loading of all the classes satisfying the criteria specified.
    *
@@ -180,13 +189,13 @@
    * these classes will never be loaded.
    */
   @SuppressWarnings("ReferenceEquality")
-  public void forceLoad(Predicate<DexType> load) {
+  public ClassMap<T> forceLoad(Predicate<DexType> load) {
     Set<DexType> knownClasses;
     ClassProvider<T> classProvider;
 
     // Cache value of class provider, as it might change concurrently.
     if (isFullyLoaded()) {
-      return;
+      return this;
     }
     classProvider = this.classProvider.get();
 
@@ -213,7 +222,7 @@
     // only one thread proceeds to rewriting the map.
     synchronized (this) {
       if (this.classProvider.get() == null) {
-        return; // Has been force-loaded concurrently.
+        return this; // Has been force-loaded concurrently.
       }
 
       // We avoid calling get() on a class supplier unless we know it was loaded.
@@ -244,6 +253,8 @@
       // classes by blocking on 'this' and hence wait for the loading operation to finish.
       this.classProvider.set(null);
     }
+
+    return this;
   }
 
   public boolean isFullyLoaded() {