Specify wrapper types in the desugared library configuration.

Bug: 158645207
Change-Id: I2d55f89e378789e0b9e3256acbfd3b05c3fc41cb
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json
index 8c023aa..6477eff 100644
--- a/src/library_desugar/desugar_jdk_libs.json
+++ b/src/library_desugar/desugar_jdk_libs.json
@@ -1,10 +1,105 @@
 {
-  "configuration_format_version": 4,
+  "configuration_format_version": 5,
   "group_id" : "com.tools.android",
   "artifact_id" : "desugar_jdk_libs",
-  "version": "0.12.0",
+  "version": "1.0.7",
   "required_compilation_api_level": 26,
   "synthesized_library_classes_package_prefix": "j$.",
+  "common_flags": [
+    {
+      "api_level_below_or_equal": 25,
+      "custom_conversion": {
+        "java.time.ZonedDateTime": "java.time.TimeConversions",
+        "java.time.LocalDate": "java.time.TimeConversions",
+        "java.time.Duration": "java.time.TimeConversions",
+        "java.time.ZoneId": "java.time.TimeConversions",
+        "java.time.MonthDay": "java.time.TimeConversions",
+        "java.time.Instant": "java.time.TimeConversions"
+      },
+      "wrapper_conversion": [
+        "java.time.Clock"
+      ]
+    },
+    {
+      "api_level_below_or_equal": 23,
+      "dont_rewrite": [
+        "java.util.Iterator#remove"
+      ],
+      "emulate_interface": {
+        "java.lang.Iterable": "j$.lang.Iterable",
+        "java.util.Map$Entry": "j$.util.Map$Entry",
+        "java.util.Collection": "j$.util.Collection",
+        "java.util.Map": "j$.util.Map",
+        "java.util.Iterator": "j$.util.Iterator",
+        "java.util.Comparator": "j$.util.Comparator",
+        "java.util.List": "j$.util.List",
+        "java.util.SortedSet": "j$.util.SortedSet",
+        "java.util.Set": "j$.util.Set",
+        "java.util.concurrent.ConcurrentMap": "j$.util.concurrent.ConcurrentMap"
+      },
+      "custom_conversion": {
+        "java.util.Optional": "java.util.OptionalConversions",
+        "java.util.OptionalDouble": "java.util.OptionalConversions",
+        "java.util.OptionalInt": "java.util.OptionalConversions",
+        "java.util.OptionalLong": "java.util.OptionalConversions",
+        "java.util.LongSummaryStatistics": "java.util.LongSummaryStatisticsConversions",
+        "java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions",
+        "java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions"
+      },
+      "wrapper_conversion": [
+        "java.util.PrimitiveIterator$OfDouble",
+        "java.util.PrimitiveIterator$OfInt",
+        "java.util.PrimitiveIterator$OfLong",
+        "java.util.Spliterator",
+        "java.util.Spliterator$OfDouble",
+        "java.util.Spliterator$OfInt",
+        "java.util.Spliterator$OfLong",
+        "java.util.Spliterator$OfPrimitive",
+        "java.util.function.BiConsumer",
+        "java.util.function.BiFunction",
+        "java.util.function.BiPredicate",
+        "java.util.function.BinaryOperator",
+        "java.util.function.Consumer",
+        "java.util.function.DoubleBinaryOperator",
+        "java.util.function.DoubleConsumer",
+        "java.util.function.DoubleFunction",
+        "java.util.function.DoublePredicate",
+        "java.util.function.DoubleToIntFunction",
+        "java.util.function.DoubleToLongFunction",
+        "java.util.function.DoubleUnaryOperator",
+        "java.util.function.Function",
+        "java.util.function.IntBinaryOperator",
+        "java.util.function.IntConsumer",
+        "java.util.function.IntFunction",
+        "java.util.function.IntPredicate",
+        "java.util.function.IntToDoubleFunction",
+        "java.util.function.IntToLongFunction",
+        "java.util.function.IntUnaryOperator",
+        "java.util.function.LongBinaryOperator",
+        "java.util.function.LongConsumer",
+        "java.util.function.LongFunction",
+        "java.util.function.LongPredicate",
+        "java.util.function.LongToDoubleFunction",
+        "java.util.function.LongToIntFunction",
+        "java.util.function.LongUnaryOperator",
+        "java.util.function.ObjDoubleConsumer",
+        "java.util.function.ObjIntConsumer",
+        "java.util.function.ObjLongConsumer",
+        "java.util.function.Predicate",
+        "java.util.function.Supplier",
+        "java.util.function.ToDoubleFunction",
+        "java.util.function.ToIntFunction",
+        "java.util.function.ToLongFunction",
+        "java.util.function.UnaryOperator",
+        "java.util.stream.BaseStream",
+        "java.util.stream.Collector",
+        "java.util.stream.DoubleStream",
+        "java.util.stream.IntStream",
+        "java.util.stream.LongStream",
+        "java.util.stream.Stream"
+      ]
+    }
+  ],
   "library_flags": [
     {
       "api_level_below_or_equal": 25,
@@ -53,30 +148,6 @@
         "java.util.Arrays#stream": "java.util.DesugarArrays",
         "java.util.Arrays#spliterator": "java.util.DesugarArrays",
         "java.util.LinkedHashSet#spliterator": "java.util.DesugarLinkedHashSet"
-      },
-      "dont_rewrite": [
-        "java.util.Iterator#remove"
-      ],
-      "emulate_interface": {
-        "java.lang.Iterable": "j$.lang.Iterable",
-        "java.util.Map$Entry": "j$.util.Map$Entry",
-        "java.util.Collection": "j$.util.Collection",
-        "java.util.Map": "j$.util.Map",
-        "java.util.Iterator": "j$.util.Iterator",
-        "java.util.Comparator": "j$.util.Comparator",
-        "java.util.List": "j$.util.List",
-        "java.util.SortedSet": "j$.util.SortedSet",
-        "java.util.Set": "j$.util.Set",
-        "java.util.concurrent.ConcurrentMap": "j$.util.concurrent.ConcurrentMap"
-      },
-      "custom_conversion": {
-        "java.util.Optional": "java.util.OptionalConversions",
-        "java.util.OptionalDouble": "java.util.OptionalConversions",
-        "java.util.OptionalInt": "java.util.OptionalConversions",
-        "java.util.OptionalLong": "java.util.OptionalConversions",
-        "java.util.LongSummaryStatistics": "java.util.LongSummaryStatisticsConversions",
-        "java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions",
-        "java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions"
       }
     }
   ],
@@ -93,14 +164,6 @@
         "java.util.Date#toInstant": "java.util.DesugarDate",
         "java.util.GregorianCalendar#from": "java.util.DesugarGregorianCalendar",
         "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar"
-      },
-      "custom_conversion": {
-        "java.time.ZonedDateTime": "java.time.TimeConversions",
-        "java.time.LocalDate": "java.time.TimeConversions",
-        "java.time.Duration": "java.time.TimeConversions",
-        "java.time.ZoneId": "java.time.TimeConversions",
-        "java.time.MonthDay": "java.time.TimeConversions",
-        "java.time.Instant": "java.time.TimeConversions"
       }
     },
     {
@@ -137,30 +200,6 @@
         "java.util.concurrent.atomic.AtomicReference#accumulateAndGet": "java.util.concurrent.atomic.DesugarAtomicReference",
         "java.util.Collections#synchronizedMap": "java.util.DesugarCollections",
         "java.util.Collections#synchronizedSortedMap": "java.util.DesugarCollections"
-      },
-      "dont_rewrite": [
-        "java.util.Iterator#remove"
-      ],
-      "emulate_interface": {
-        "java.lang.Iterable": "j$.lang.Iterable",
-        "java.util.Map$Entry": "j$.util.Map$Entry",
-        "java.util.Collection": "j$.util.Collection",
-        "java.util.Map": "j$.util.Map",
-        "java.util.Iterator": "j$.util.Iterator",
-        "java.util.Comparator": "j$.util.Comparator",
-        "java.util.List": "j$.util.List",
-        "java.util.SortedSet": "j$.util.SortedSet",
-        "java.util.Set": "j$.util.Set",
-        "java.util.concurrent.ConcurrentMap": "j$.util.concurrent.ConcurrentMap"
-      },
-      "custom_conversion": {
-        "java.util.Optional": "java.util.OptionalConversions",
-        "java.util.OptionalDouble": "java.util.OptionalConversions",
-        "java.util.OptionalInt": "java.util.OptionalConversions",
-        "java.util.OptionalLong": "java.util.OptionalConversions",
-        "java.util.LongSummaryStatistics": "java.util.LongSummaryStatisticsConversions",
-        "java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions",
-        "java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions"
       }
     }
   ],
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 7d5b31e..dcf33f1 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -69,7 +69,7 @@
 
   private final Set<DexMethod> parallelMethods = Sets.newIdentityHashSet();
 
-  private GenerateLintFiles(String desugarConfigurationPath, String outputDirectory) {
+  public GenerateLintFiles(String desugarConfigurationPath, String outputDirectory) {
     this.desugaredLibraryConfiguration =
         readDesugaredLibraryConfiguration(desugarConfigurationPath);
     this.outputDirectory =
@@ -356,20 +356,24 @@
         apiLevel >= desugaredLibraryConfiguration.getRequiredCompilationApiLevel().getLevel();
         apiLevel--) {
       System.out.println("Generating lint files for compile API " + apiLevel);
-      generateLintFiles(
-          AndroidApiLevel.getAndroidApiLevel(apiLevel),
-          minApiLevel -> minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B,
-          (minApiLevel, method) -> {
-            assert minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B;
-            if (minApiLevel == AndroidApiLevel.L) {
-              return true;
-            }
-            assert minApiLevel == AndroidApiLevel.B;
-            return !parallelMethods.contains(method.method);
-          });
+      run(apiLevel);
     }
   }
 
+  public void run(int apiLevel) throws Exception {
+    generateLintFiles(
+        AndroidApiLevel.getAndroidApiLevel(apiLevel),
+        minApiLevel -> minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B,
+        (minApiLevel, method) -> {
+          assert minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B;
+          if (minApiLevel == AndroidApiLevel.L) {
+            return true;
+          }
+          assert minApiLevel == AndroidApiLevel.B;
+          return !parallelMethods.contains(method.method);
+        });
+  }
+
   public static void main(String[] args) throws Exception {
     if (args.length != 2) {
       System.out.println("Usage: GenerateLineFiles <desuage configuration> <output directory>");
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index ddca96a..4b35d17 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -121,9 +121,6 @@
 
       LazyLoadedDexApplication lazyApp =
           new ApplicationReader(inputApp, options, timing).read(executor);
-      // Store a direct lookup to determine actual library definitions for wrapper generation.
-      options.desugaredLibraryBootclasspathDefinitions =
-          t -> lazyApp.libraryDefintionFor(t) != null;
 
       PrefixRewritingMapper rewritePrefix =
           options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index 277ba09..401297e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -288,7 +288,7 @@
     SortedProgramMethodSet callbacks = generateCallbackMethods();
     irConverter.processMethodsConcurrently(callbacks, executorService);
     if (appView.options().isDesugaredLibraryCompilation()) {
-      wrapperSynthesizor.finalizeWrappersForD8(builder, irConverter, executorService);
+      wrapperSynthesizor.finalizeWrappersForL8(builder, irConverter, executorService);
     }
   }
 
@@ -349,26 +349,23 @@
   }
 
   public void reportInvalidInvoke(DexType type, DexMethod invokedMethod, String debugString) {
-    if (appView.options().isDesugaredLibraryCompilation()) {
-      // TODO(b/158645207): If wrappers are exactly specified this should fail both for normal
-      //  compilation and L8.
-      return;
-    }
     DexType desugaredType = appView.rewritePrefix.rewrittenType(type, appView);
-    appView
-        .options()
-        .reporter
-        .info(
-            new StringDiagnostic(
-                "Invoke to "
-                    + invokedMethod.holder
-                    + "#"
-                    + invokedMethod.name
-                    + " may not work correctly at runtime (Cannot convert "
-                    + debugString
-                    + "type "
-                    + desugaredType
-                    + ")."));
+    StringDiagnostic diagnostic =
+        new StringDiagnostic(
+            "Invoke to "
+                + invokedMethod.holder
+                + "#"
+                + invokedMethod.name
+                + " may not work correctly at runtime (Cannot convert "
+                + debugString
+                + "type "
+                + desugaredType
+                + ").");
+    if (appView.options().isDesugaredLibraryCompilation()) {
+      throw appView.options().reporter.fatalError(diagnostic);
+    } else {
+      appView.options().reporter.info(diagnostic);
+    }
   }
 
   public static DexType vivifiedTypeFor(DexType type, AppView<?> appView) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index 60cf4c6..494831e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -12,18 +12,26 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper.DesugarPrefixRewritingMapper;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 public class DesugaredLibraryConfiguration {
 
@@ -41,9 +49,10 @@
   private final Map<DexType, DexType> customConversions;
   private final List<Pair<DexType, DexString>> dontRewriteInvocation;
   private final List<String> extraKeepRules;
+  private final Set<DexType> wrapperConversions;
 
-  public static Builder builder(DexItemFactory dexItemFactory) {
-    return new Builder(dexItemFactory);
+  public static Builder builder(DexItemFactory dexItemFactory, Reporter reporter, Origin origin) {
+    return new Builder(dexItemFactory, reporter, origin);
   }
 
   public static DesugaredLibraryConfiguration withOnlyRewritePrefixForTesting(
@@ -58,6 +67,7 @@
         ImmutableMap.of(),
         ImmutableMap.of(),
         ImmutableMap.of(),
+        ImmutableSet.of(),
         ImmutableList.of(),
         ImmutableList.of());
   }
@@ -73,11 +83,12 @@
         ImmutableMap.of(),
         ImmutableMap.of(),
         ImmutableMap.of(),
+        ImmutableSet.of(),
         ImmutableList.of(),
         ImmutableList.of());
   }
 
-  public DesugaredLibraryConfiguration(
+  private DesugaredLibraryConfiguration(
       AndroidApiLevel requiredCompilationAPILevel,
       boolean libraryCompilation,
       String packagePrefix,
@@ -87,6 +98,7 @@
       Map<DexString, Map<DexType, DexType>> retargetCoreLibMember,
       Map<DexType, DexType> backportCoreLibraryMember,
       Map<DexType, DexType> customConversions,
+      Set<DexType> wrapperConversions,
       List<Pair<DexType, DexString>> dontRewriteInvocation,
       List<String> extraKeepRules) {
     this.requiredCompilationAPILevel = requiredCompilationAPILevel;
@@ -98,6 +110,7 @@
     this.retargetCoreLibMember = retargetCoreLibMember;
     this.backportCoreLibraryMember = backportCoreLibraryMember;
     this.customConversions = customConversions;
+    this.wrapperConversions = wrapperConversions;
     this.dontRewriteInvocation = dontRewriteInvocation;
     this.extraKeepRules = extraKeepRules;
   }
@@ -158,6 +171,10 @@
     return customConversions;
   }
 
+  public Set<DexType> getWrapperConversions() {
+    return wrapperConversions;
+  }
+
   public List<Pair<DexType, DexString>> getDontRewriteInvocation() {
     return dontRewriteInvocation;
   }
@@ -169,6 +186,8 @@
   public static class Builder {
 
     private final DexItemFactory factory;
+    private final Reporter reporter;
+    private final Origin origin;
 
     private AndroidApiLevel requiredCompilationAPILevel;
     private boolean libraryCompilation = false;
@@ -176,15 +195,34 @@
         FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX;
     private String identifier;
     private Map<String, String> rewritePrefix = new HashMap<>();
-    private Map<DexType, DexType> emulateLibraryInterface = new HashMap<>();
+    private Map<DexType, DexType> emulateLibraryInterface = new IdentityHashMap<>();
     private Map<DexString, Map<DexType, DexType>> retargetCoreLibMember = new IdentityHashMap<>();
-    private Map<DexType, DexType> backportCoreLibraryMember = new HashMap<>();
-    private Map<DexType, DexType> customConversions = new HashMap<>();
+    private Map<DexType, DexType> backportCoreLibraryMember = new IdentityHashMap<>();
+    private Map<DexType, DexType> customConversions = new IdentityHashMap<>();
+    private Set<DexType> wrapperConversions = Sets.newIdentityHashSet();
     private List<Pair<DexType, DexString>> dontRewriteInvocation = new ArrayList<>();
     private List<String> extraKeepRules = Collections.emptyList();
 
-    public Builder(DexItemFactory dexItemFactory) {
+    private Builder(DexItemFactory dexItemFactory, Reporter reporter, Origin origin) {
       this.factory = dexItemFactory;
+      this.reporter = reporter;
+      this.origin = origin;
+    }
+
+    // Utility to set values. Currently assumes the key is fresh.
+    private <K, V> void put(Map<K, V> map, K key, V value, String desc) {
+      if (map.containsKey(key)) {
+        throw reporter.fatalError(
+            new StringDiagnostic(
+                "Invalid desugared library configuration. "
+                    + " Duplicate assignment of key: '"
+                    + key
+                    + "' in sections for '"
+                    + desc
+                    + "'",
+                origin));
+      }
+      map.put(key, value);
     }
 
     public Builder setSynthesizedLibraryClassesPackagePrefix(String prefix) {
@@ -218,7 +256,11 @@
     }
 
     public Builder putRewritePrefix(String prefix, String rewrittenPrefix) {
-      rewritePrefix.put(prefix, rewrittenPrefix);
+      put(
+          rewritePrefix,
+          prefix,
+          rewrittenPrefix,
+          DesugaredLibraryConfigurationParser.REWRITE_PREFIX_KEY);
       return this;
     }
 
@@ -226,14 +268,28 @@
         String emulateLibraryItf, String rewrittenEmulateLibraryItf) {
       DexType interfaceType = stringClassToDexType(emulateLibraryItf);
       DexType rewrittenType = stringClassToDexType(rewrittenEmulateLibraryItf);
-      emulateLibraryInterface.put(interfaceType, rewrittenType);
+      put(
+          emulateLibraryInterface,
+          interfaceType,
+          rewrittenType,
+          DesugaredLibraryConfigurationParser.EMULATE_INTERFACE_KEY);
       return this;
     }
 
     public Builder putCustomConversion(String type, String conversionHolder) {
       DexType dexType = stringClassToDexType(type);
       DexType conversionType = stringClassToDexType(conversionHolder);
-      customConversions.put(dexType, conversionType);
+      put(
+          customConversions,
+          dexType,
+          conversionType,
+          DesugaredLibraryConfigurationParser.CUSTOM_CONVERSION_KEY);
+      return this;
+    }
+
+    public Builder addWrapperConversion(String type) {
+      DexType dexType = stringClassToDexType(type);
+      wrapperConversions.add(dexType);
       return this;
     }
 
@@ -245,14 +301,22 @@
       DexType originalType = stringClassToDexType(retarget.substring(0, index));
       DexType finalType = stringClassToDexType(rewrittenRetarget);
       assert !typeMap.containsKey(originalType);
-      typeMap.put(originalType, finalType);
+      put(
+          typeMap,
+          originalType,
+          finalType,
+          DesugaredLibraryConfigurationParser.RETARGET_LIB_MEMBER_KEY);
       return this;
     }
 
     public Builder putBackportCoreLibraryMember(String backport, String rewrittenBackport) {
       DexType backportType = stringClassToDexType(backport);
       DexType rewrittenBackportType = stringClassToDexType(rewrittenBackport);
-      backportCoreLibraryMember.put(backportType, rewrittenBackportType);
+      put(
+          backportCoreLibraryMember,
+          backportType,
+          rewrittenBackportType,
+          DesugaredLibraryConfigurationParser.BACKPORT_KEY);
       return this;
     }
 
@@ -279,6 +343,7 @@
     }
 
     public DesugaredLibraryConfiguration build() {
+      validate();
       return new DesugaredLibraryConfiguration(
           requiredCompilationAPILevel,
           libraryCompilation,
@@ -289,8 +354,22 @@
           ImmutableMap.copyOf(retargetCoreLibMember),
           ImmutableMap.copyOf(backportCoreLibraryMember),
           ImmutableMap.copyOf(customConversions),
+          ImmutableSet.copyOf(wrapperConversions),
           ImmutableList.copyOf(dontRewriteInvocation),
           ImmutableList.copyOf(extraKeepRules));
     }
+
+    private void validate() {
+      SetView<DexType> dups = Sets.intersection(customConversions.keySet(), wrapperConversions);
+      if (!dups.isEmpty()) {
+        throw reporter.fatalError(
+            new StringDiagnostic(
+                "Invalid desugared library configuration. "
+                    + "Duplicate types in custom conversions and wrapper conversions: "
+                    + String.join(
+                        ", ", dups.stream().map(DexType::toString).collect(Collectors.toSet())),
+                origin));
+      }
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
index 8896170..5b57033 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
@@ -6,7 +6,9 @@
 
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.gson.JsonArray;
@@ -19,158 +21,184 @@
 
 public class DesugaredLibraryConfigurationParser {
 
-  private static final int MAX_SUPPORTED_VERSION = 4;
+  public static final int MAX_SUPPORTED_VERSION = 5;
+  private static final int MIN_SUPPORTED_VERSION = 5;
 
-  private final DesugaredLibraryConfiguration.Builder configurationBuilder;
+  private static final String MIN_DESUGARED_LIBRARY_WITH_SUPPORTED_VERSION = "1.0.7";
+
+  static final String CONFIGURATION_FORMAT_VERSION_KEY = "configuration_format_version";
+  static final String VERSION_KEY = "version";
+  static final String GROUP_ID_KEY = "group_id";
+  static final String ARTIFACT_ID_KEY = "artifact_id";
+  static final String REQUIRED_COMPILATION_API_LEVEL_KEY = "required_compilation_api_level";
+  static final String SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY =
+      "synthesized_library_classes_package_prefix";
+
+  static final String COMMON_FLAGS_KEY = "common_flags";
+  static final String LIBRARY_FLAGS_KEY = "library_flags";
+  static final String PROGRAM_FLAGS_KEY = "program_flags";
+
+  static final String API_LEVEL_BELOW_OR_EQUAL_KEY = "api_level_below_or_equal";
+  static final String WRAPPER_CONVERSION_KEY = "wrapper_conversion";
+  static final String CUSTOM_CONVERSION_KEY = "custom_conversion";
+  static final String REWRITE_PREFIX_KEY = "rewrite_prefix";
+  static final String RETARGET_LIB_MEMBER_KEY = "retarget_lib_member";
+  static final String EMULATE_INTERFACE_KEY = "emulate_interface";
+  static final String DONT_REWRITE_KEY = "dont_rewrite";
+  static final String BACKPORT_KEY = "backport";
+  static final String SHRINKER_CONFIG_KEY = "shrinker_config";
+
+  private final DexItemFactory dexItemFactory;
   private final Reporter reporter;
   private final boolean libraryCompilation;
   private final int minAPILevel;
 
+  private DesugaredLibraryConfiguration.Builder configurationBuilder = null;
+  private Origin origin;
+
   public DesugaredLibraryConfigurationParser(
       DexItemFactory dexItemFactory,
       Reporter reporter,
       boolean libraryCompilation,
       int minAPILevel) {
+    assert MIN_SUPPORTED_VERSION <= MAX_SUPPORTED_VERSION;
+    this.dexItemFactory = dexItemFactory;
     this.reporter = reporter;
-    this.configurationBuilder = DesugaredLibraryConfiguration.builder(dexItemFactory);
     this.minAPILevel = minAPILevel;
     this.libraryCompilation = libraryCompilation;
+  }
+
+  private JsonElement required(JsonObject json, String key) {
+    if (!json.has(key)) {
+      throw reporter.fatalError(
+          new StringDiagnostic(
+              "Invalid desugared library configuration. " + "Expected required key '" + key + "'",
+              origin));
+    }
+    return json.get(key);
+  }
+
+  public DesugaredLibraryConfiguration parse(StringResource stringResource) {
+    origin = stringResource.getOrigin();
+    assert origin != null;
+    configurationBuilder = DesugaredLibraryConfiguration.builder(dexItemFactory, reporter, origin);
     if (libraryCompilation) {
       configurationBuilder.setLibraryCompilation();
     } else {
       configurationBuilder.setProgramCompilation();
     }
-  }
-
-  public DesugaredLibraryConfiguration parse(StringResource stringResource) {
-    String jsonConfigString;
+    JsonObject jsonConfig;
     try {
-      jsonConfigString = stringResource.getString();
+      String jsonConfigString = stringResource.getString();
+      JsonParser parser = new JsonParser();
+      jsonConfig = parser.parse(jsonConfigString).getAsJsonObject();
     } catch (Exception e) {
-      throw reporter.fatalError(
-          new StringDiagnostic("Cannot access desugared library configuration."));
+      throw reporter.fatalError(new ExceptionDiagnostic(e, origin));
     }
-    JsonParser parser = new JsonParser();
-    JsonObject jsonConfig = parser.parse(jsonConfigString).getAsJsonObject();
 
-    int formatVersion = jsonConfig.get("configuration_format_version").getAsInt();
+    JsonElement formatVersionElement = required(jsonConfig, CONFIGURATION_FORMAT_VERSION_KEY);
+    int formatVersion = formatVersionElement.getAsInt();
     if (formatVersion > MAX_SUPPORTED_VERSION) {
       throw reporter.fatalError(
           new StringDiagnostic(
               "Unsupported desugared library configuration version, please upgrade the D8/R8"
-                  + " compiler."));
+                  + " compiler.",
+              origin));
     }
-    if (formatVersion == 1) {
-      reporter.warning(
+    if (formatVersion < MIN_SUPPORTED_VERSION) {
+      throw reporter.fatalError(
           new StringDiagnostic(
-              "You are using an experimental version of the desugared library configuration, "
-                  + "distributed only in the early canary versions. Please update for "
-                  + "production releases and to fix this warning."));
+              "Unsupported desugared library version, please upgrade the"
+                  + " desugared library to at least version "
+                  + MIN_DESUGARED_LIBRARY_WITH_SUPPORTED_VERSION
+                  + ".",
+              origin));
     }
 
-    String version = jsonConfig.get("version").getAsString();
-    String groupID;
-    String artifactID;
-    if (formatVersion < 4) {
-      groupID = "com.tools.android";
-      artifactID = "desugar_jdk_libs";
-    } else {
-      groupID = jsonConfig.get("group_id").getAsString();
-      artifactID = jsonConfig.get("artifact_id").getAsString();
-    }
+    String version = required(jsonConfig, VERSION_KEY).getAsString();
+    String groupID = required(jsonConfig, GROUP_ID_KEY).getAsString();
+    String artifactID = required(jsonConfig, ARTIFACT_ID_KEY).getAsString();
     String identifier = String.join(":", groupID, artifactID, version);
     configurationBuilder.setDesugaredLibraryIdentifier(identifier);
+    configurationBuilder.setSynthesizedLibraryClassesPackagePrefix(
+        required(jsonConfig, SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY).getAsString());
 
-    if (jsonConfig.has("synthesized_library_classes_package_prefix")) {
-      configurationBuilder.setSynthesizedLibraryClassesPackagePrefix(
-          jsonConfig.get("synthesized_library_classes_package_prefix").getAsString());
-    } else {
-      reporter.warning(
-          new StringDiagnostic(
-              "Missing package_prefix, falling back to "
-                  + DesugaredLibraryConfiguration.FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX
-                  + " prefix, update desugared library configuration."));
-    }
     int required_compilation_api_level =
-        jsonConfig.get("required_compilation_api_level").getAsInt();
+        required(jsonConfig, REQUIRED_COMPILATION_API_LEVEL_KEY).getAsInt();
     configurationBuilder.setRequiredCompilationAPILevel(
         AndroidApiLevel.getAndroidApiLevel(required_compilation_api_level));
-    JsonArray jsonFlags =
-        libraryCompilation
-            ? jsonConfig.getAsJsonArray("library_flags")
-            : jsonConfig.getAsJsonArray("program_flags");
-    for (JsonElement jsonFlagSet : jsonFlags) {
-      int api_level_below_or_equal =
-          jsonFlagSet.getAsJsonObject().get("api_level_below_or_equal").getAsInt();
-      if (minAPILevel > api_level_below_or_equal) {
-        continue;
-      }
-      parseFlags(jsonFlagSet.getAsJsonObject());
-    }
-    if (libraryCompilation && formatVersion <= 4) {
-      // Read custom conversions from program flags.
-      for (JsonElement jsonFlagSet : jsonConfig.getAsJsonArray("program_flags")) {
-        JsonObject jsonFlagSetObject = jsonFlagSet.getAsJsonObject();
-        int api_level_below_or_equal = jsonFlagSetObject.get("api_level_below_or_equal").getAsInt();
-        if (minAPILevel > api_level_below_or_equal) {
-          continue;
-        }
-        String customConversionKey = "custom_conversion";
-        if (jsonFlagSetObject.has(customConversionKey)) {
-          JsonObject backportedFlags = new JsonObject();
-          backportedFlags.add(customConversionKey, jsonFlagSetObject.get(customConversionKey));
-          parseFlags(backportedFlags);
-        }
-      }
-    }
-    if (jsonConfig.has("shrinker_config")) {
-      JsonArray jsonKeepRules = jsonConfig.get("shrinker_config").getAsJsonArray();
+    JsonElement commonFlags = required(jsonConfig, COMMON_FLAGS_KEY);
+    JsonElement libraryFlags = required(jsonConfig, LIBRARY_FLAGS_KEY);
+    JsonElement programFlags = required(jsonConfig, PROGRAM_FLAGS_KEY);
+    parseFlagsList(commonFlags.getAsJsonArray());
+    parseFlagsList(
+        libraryCompilation ? libraryFlags.getAsJsonArray() : programFlags.getAsJsonArray());
+    if (jsonConfig.has(SHRINKER_CONFIG_KEY)) {
+      JsonArray jsonKeepRules = jsonConfig.get(SHRINKER_CONFIG_KEY).getAsJsonArray();
       List<String> extraKeepRules = new ArrayList<>(jsonKeepRules.size());
       for (JsonElement keepRule : jsonKeepRules) {
         extraKeepRules.add(keepRule.getAsString());
       }
       configurationBuilder.setExtraKeepRules(extraKeepRules);
     }
-    return configurationBuilder.build();
+    DesugaredLibraryConfiguration config = configurationBuilder.build();
+    configurationBuilder = null;
+    origin = null;
+    return config;
+  }
+
+  private void parseFlagsList(JsonArray jsonFlags) {
+    for (JsonElement jsonFlagSet : jsonFlags) {
+      JsonObject flag = jsonFlagSet.getAsJsonObject();
+      int api_level_below_or_equal = required(flag, API_LEVEL_BELOW_OR_EQUAL_KEY).getAsInt();
+      if (minAPILevel <= api_level_below_or_equal) {
+        parseFlags(flag);
+      }
+    }
   }
 
   private void parseFlags(JsonObject jsonFlagSet) {
-    if (jsonFlagSet.has("rewrite_prefix")) {
+    if (jsonFlagSet.has(REWRITE_PREFIX_KEY)) {
       for (Map.Entry<String, JsonElement> rewritePrefix :
-          jsonFlagSet.get("rewrite_prefix").getAsJsonObject().entrySet()) {
+          jsonFlagSet.get(REWRITE_PREFIX_KEY).getAsJsonObject().entrySet()) {
         configurationBuilder.putRewritePrefix(
             rewritePrefix.getKey(), rewritePrefix.getValue().getAsString());
       }
     }
-    if (jsonFlagSet.has("retarget_lib_member")) {
+    if (jsonFlagSet.has(RETARGET_LIB_MEMBER_KEY)) {
       for (Map.Entry<String, JsonElement> retarget :
-          jsonFlagSet.get("retarget_lib_member").getAsJsonObject().entrySet()) {
+          jsonFlagSet.get(RETARGET_LIB_MEMBER_KEY).getAsJsonObject().entrySet()) {
         configurationBuilder.putRetargetCoreLibMember(
             retarget.getKey(), retarget.getValue().getAsString());
       }
     }
-    if (jsonFlagSet.has("backport")) {
+    if (jsonFlagSet.has(BACKPORT_KEY)) {
       for (Map.Entry<String, JsonElement> backport :
-          jsonFlagSet.get("backport").getAsJsonObject().entrySet()) {
+          jsonFlagSet.get(BACKPORT_KEY).getAsJsonObject().entrySet()) {
         configurationBuilder.putBackportCoreLibraryMember(
             backport.getKey(), backport.getValue().getAsString());
       }
     }
-    if (jsonFlagSet.has("emulate_interface")) {
+    if (jsonFlagSet.has(EMULATE_INTERFACE_KEY)) {
       for (Map.Entry<String, JsonElement> itf :
-          jsonFlagSet.get("emulate_interface").getAsJsonObject().entrySet()) {
+          jsonFlagSet.get(EMULATE_INTERFACE_KEY).getAsJsonObject().entrySet()) {
         configurationBuilder.putEmulateLibraryInterface(itf.getKey(), itf.getValue().getAsString());
       }
     }
-    if (jsonFlagSet.has("custom_conversion")) {
+    if (jsonFlagSet.has(CUSTOM_CONVERSION_KEY)) {
       for (Map.Entry<String, JsonElement> conversion :
-          jsonFlagSet.get("custom_conversion").getAsJsonObject().entrySet()) {
+          jsonFlagSet.get(CUSTOM_CONVERSION_KEY).getAsJsonObject().entrySet()) {
         configurationBuilder.putCustomConversion(
             conversion.getKey(), conversion.getValue().getAsString());
       }
     }
-    if (jsonFlagSet.has("dont_rewrite")) {
-      JsonArray dontRewrite = jsonFlagSet.get("dont_rewrite").getAsJsonArray();
+    if (jsonFlagSet.has(WRAPPER_CONVERSION_KEY)) {
+      for (JsonElement wrapper : jsonFlagSet.get(WRAPPER_CONVERSION_KEY).getAsJsonArray()) {
+        configurationBuilder.addWrapperConversion(wrapper.getAsString());
+      }
+    }
+    if (jsonFlagSet.has(DONT_REWRITE_KEY)) {
+      JsonArray dontRewrite = jsonFlagSet.get(DONT_REWRITE_KEY).getAsJsonArray();
       for (JsonElement rewrite : dontRewrite) {
         configurationBuilder.addDontRewriteInvocation(rewrite.getAsString());
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index c911d6b..23a6465 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -112,7 +112,6 @@
   private final Set<DexType> invalidWrappers = Sets.newConcurrentHashSet();
   private final DexItemFactory factory;
   private final DesugaredLibraryAPIConverter converter;
-  private final DexString vivifiedSourceFile;
 
   DesugaredLibraryWrapperSynthesizer(AppView<?> appView, DesugaredLibraryAPIConverter converter) {
     this.appView = appView;
@@ -126,7 +125,6 @@
             + WRAPPER_PREFIX;
     dexWrapperPrefixDexString = factory.createString(dexWrapperPrefixString);
     this.converter = converter;
-    this.vivifiedSourceFile = appView.dexItemFactory().createString("vivified");
   }
 
   boolean hasSynthesized(DexType type) {
@@ -134,96 +132,7 @@
   }
 
   boolean canGenerateWrapper(DexType type) {
-    return typeWrappers.containsKey(type) || canGenerateWrapper(appView.definitionFor(type));
-  }
-
-  /**
-   * Wrappers can/are generated for all types that appear to support wrapper generation. The
-   * assumptions right now are that the 'type' must be specified in the bootclasspath library, that
-   * means the library component for any build (all of D8, R8 *and* L8). For L8 the type lookup
-   * needs to bypass the usual definitionsFor and look directly in the library on the initial app.
-   *
-   * <p>In addition (or rather before doing the lookup) the class to be wrapped must satisfy several
-   * requirements: its super types can also be wrapped, it has no instance fields, all instance
-   * methods are non-final and public and the types in them are also library defined. If all of that
-   * holds wrappers for the type are created.
-   *
-   * <p>TODO(b/158645207): Consider adding wrappers to the spec and have this be precise.
-   */
-  boolean canGenerateWrapper(DexClass clazz) {
-    if (typeWrappers.containsKey(clazz.type)) {
-      return true;
-    }
-    if (clazz == null
-        || clazz.accessFlags.isFinal()
-        || !clazz.accessFlags.isPublic()
-        || clazz.isAnonymousClass()) {
-      return false;
-    }
-    if (!clazz.isLibraryClass()) {
-      if (!appView.options().isDesugaredLibraryCompilation()) {
-        return false;
-      }
-      // If doing the desugared library compilation, then the class to be wrapped must be defined on
-      // the actual bootclasspath (not the desugared library). The definition lookup must thus
-      // bypass the usual lookup in this case.
-      if (!appView.options().desugaredLibraryBootclasspathDefinitions.test(clazz.type)) {
-        return false;
-      }
-    }
-    if (clazz.superType == null) {
-      return false;
-    }
-    if (clazz.superType != factory.objectType) {
-      if (!canGenerateWrapper(clazz.superType)) {
-        return false;
-      }
-    }
-    for (DexType iface : clazz.interfaces.values) {
-      if (!canGenerateWrapper(iface)) {
-        return false;
-      }
-    }
-    for (DexEncodedField field : clazz.instanceFields()) {
-      return false;
-    }
-    for (DexEncodedMethod method : clazz.virtualMethods()) {
-      if (method.isFinal() || !method.isPublic()) {
-        return false;
-      }
-      if (isUndefinedOrNonLibraryType(method.proto().returnType)) {
-        return false;
-      }
-      for (DexType param : method.proto().parameters.values) {
-        if (isUndefinedOrNonLibraryType(param)) {
-          return false;
-        }
-      }
-    }
-    return true;
-  }
-
-  private boolean isUndefinedOrNonLibraryType(DexType type) {
-    if (type.isPrimitiveType()) {
-      return false;
-    }
-    DexType baseType = type.toBaseType(appView.dexItemFactory());
-    if (!baseType.isClassType()) {
-      return false;
-    }
-    DexClass clazz = appView.definitionFor(baseType);
-    if (clazz == null) {
-      return true;
-    }
-    if (clazz.isLibraryClass()) {
-      return false;
-    }
-    if (!appView.options().isDesugaredLibraryCompilation()) {
-      return true;
-    }
-    // The wrapper referenced type is also considered undefined if not on the bootclasspath.
-    // TODO(b/158718959): Notice use of 'type' vs 'clazz.type' as emulated interfaces are mutated.
-    return !appView.options().desugaredLibraryBootclasspathDefinitions.test(type);
+    return appView.options().desugaredLibraryConfiguration.getWrapperConversions().contains(type);
   }
 
   DexType getTypeWrapper(DexType type) {
@@ -248,7 +157,7 @@
     return wrappers.computeIfAbsent(
         type,
         t -> {
-          assert canGenerateWrapper(type);
+          assert canGenerateWrapper(type) : type;
           DexType wrapperType = createWrapperType(type, suffix);
           assert converter.canGenerateWrappersAndCallbacks()
                   || appView.definitionFor(wrapperType).isClasspathClass()
@@ -544,7 +453,7 @@
         true);
   }
 
-  void finalizeWrappersForD8(
+  void finalizeWrappersForL8(
       DexApplication.Builder<?> builder, IRConverter irConverter, ExecutorService executorService)
       throws ExecutionException {
     List<DexProgramClass> synthesizedWrappers = synthesizeWrappers();
@@ -552,16 +461,13 @@
   }
 
   private List<DexProgramClass> synthesizeWrappers() {
+    DesugaredLibraryConfiguration conf = appView.options().desugaredLibraryConfiguration;
+    for (DexType type : conf.getWrapperConversions()) {
+      assert !conf.getCustomConversions().containsKey(type);
+      getTypeWrapper(type);
+    }
     Map<DexType, DexClass> synthesizedWrappers = new IdentityHashMap<>();
     List<DexProgramClass> additions = new ArrayList<>();
-    DesugaredLibraryConfiguration conf = appView.options().desugaredLibraryConfiguration;
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      if (conf.getCustomConversions().get(clazz.type) == null
-          && appView.rewritePrefix.hasRewrittenType(clazz.type, appView)
-          && canGenerateWrapper(clazz)) {
-        getTypeWrapper(clazz.type);
-      }
-    }
     int total = typeWrappers.size() + vivifiedTypeWrappers.size();
     generateWrappers(
         ClassKind.PROGRAM,
@@ -587,7 +493,7 @@
         });
   }
 
-  void generateWrappers(
+  private void generateWrappers(
       ClassKind classKind,
       Set<DexType> synthesized,
       BiConsumer<DexType, DexClass> generatedCallback) {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index f83359a..35e4614 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -680,9 +680,6 @@
   public DesugaredLibraryConfiguration desugaredLibraryConfiguration =
       DesugaredLibraryConfiguration.empty();
 
-  // Method to lookup library definitions.
-  public Predicate<DexType> desugaredLibraryBootclasspathDefinitions;
-
   public boolean relocatorCompilation = false;
 
   // If null, no keep rules are recorded.
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
index 70518d7..a800801 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
+import org.hamcrest.CoreMatchers;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
@@ -71,15 +72,20 @@
   }
 
   public static Matcher<Diagnostic> diagnosticPosition(Position position) {
+    return diagnosticPosition(CoreMatchers.equalTo(position));
+  }
+
+  public static Matcher<Diagnostic> diagnosticPosition(Matcher<Position> positionMatcher) {
     return new DiagnosticsMatcher() {
       @Override
       protected boolean eval(Diagnostic diagnostic) {
-        return diagnostic.getPosition().equals(position);
+        return positionMatcher.matches(diagnostic.getPosition());
       }
 
       @Override
       protected void explain(Description description) {
-        description.appendText("position ").appendText(position.getDescription());
+        description.appendText("position ");
+        positionMatcher.describeTo(description);
       }
     };
   }
diff --git a/src/test/java/com/android/tools/r8/PositionMatcher.java b/src/test/java/com/android/tools/r8/PositionMatcher.java
new file mode 100644
index 0000000..4fca1c6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/PositionMatcher.java
@@ -0,0 +1,27 @@
+// 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;
+
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.position.TextPosition;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public abstract class PositionMatcher extends TypeSafeMatcher<Position> {
+
+  public static Matcher<Position> positionLine(int line) {
+    return new PositionMatcher() {
+      @Override
+      protected boolean matchesSafely(Position position) {
+        return position instanceof TextPosition && ((TextPosition) position).getLine() == line;
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("with line " + line);
+      }
+    };
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryConfigurationParsingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryConfigurationParsingTest.java
new file mode 100644
index 0000000..c7d0c92
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryConfigurationParsingTest.java
@@ -0,0 +1,314 @@
+// 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.desugar.desugaredlibrary;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.NoneRuntime;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AbortException;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DesugaredLibraryConfigurationParsingTest extends TestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public DesugaredLibraryConfigurationParsingTest(TestParameters parameters) {
+    assertEquals(NoneRuntime.getInstance(), parameters.getRuntime());
+  }
+
+  final AndroidApiLevel minApi = AndroidApiLevel.B;
+  final boolean libraryCompilation = true;
+
+  final DexItemFactory factory = new DexItemFactory();
+  final Origin origin =
+      new Origin(Origin.root()) {
+        @Override
+        public String part() {
+          return "Test Origin";
+        }
+      };
+
+  final Map<String, Object> TEMPLATE =
+      ImmutableMap.<String, Object>builder()
+          .put(
+              "configuration_format_version",
+              DesugaredLibraryConfigurationParser.MAX_SUPPORTED_VERSION)
+          .put("group_id", "com.tools.android")
+          .put("artifact_id", "desugar_jdk_libs")
+          .put("version", "0.0.0")
+          .put("required_compilation_api_level", 1)
+          .put("synthesized_library_classes_package_prefix", "j$.")
+          .put("common_flags", Collections.emptyList())
+          .put("program_flags", Collections.emptyList())
+          .put("library_flags", Collections.emptyList())
+          .build();
+
+  private LinkedHashMap<String, Object> template() {
+    return new LinkedHashMap<>(TEMPLATE);
+  }
+
+  private DesugaredLibraryConfigurationParser parser(DiagnosticsHandler handler) {
+    return new DesugaredLibraryConfigurationParser(
+        factory, new Reporter(handler), libraryCompilation, minApi.getLevel());
+  }
+
+  private DesugaredLibraryConfiguration runPassing(String resource) {
+    return runPassing(StringResource.fromString(resource, origin));
+  }
+
+  private DesugaredLibraryConfiguration runPassing(StringResource resource) {
+    TestDiagnosticMessagesImpl handler = new TestDiagnosticMessagesImpl();
+    DesugaredLibraryConfiguration config = parser(handler).parse(resource);
+    handler.assertNoMessages();
+    return config;
+  }
+
+  private void runFailing(String json, Consumer<TestDiagnosticMessages> checker) {
+    TestDiagnosticMessagesImpl handler = new TestDiagnosticMessagesImpl();
+    try {
+      parser(handler).parse(StringResource.fromString(json, origin));
+      fail("Expected failure");
+    } catch (AbortException e) {
+      checker.accept(handler);
+    }
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    // Just test that the reference file parses without issues.
+    DesugaredLibraryConfiguration config =
+        runPassing(StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+    assertEquals(libraryCompilation, config.isLibraryCompilation());
+  }
+
+  @Test
+  public void testEmpty() {
+    runFailing(
+        "",
+        diagnostics -> {
+          diagnostics.assertErrorsMatch(
+              allOf(
+                  diagnosticMessage(containsString("Not a JSON Object")),
+                  diagnosticOrigin(origin)));
+        });
+  }
+
+  @Test
+  public void testRequiredKeys() {
+    ImmutableList<String> requiredKeys =
+        ImmutableList.of(
+            "configuration_format_version",
+            "group_id",
+            "artifact_id",
+            "version",
+            "required_compilation_api_level",
+            "synthesized_library_classes_package_prefix",
+            "common_flags",
+            "program_flags",
+            "library_flags");
+    for (String key : requiredKeys) {
+      Map<String, Object> data = template();
+      data.remove(key);
+      runFailing(
+          toJson(data),
+          diagnostics ->
+              diagnostics.assertErrorsMatch(
+                  allOf(
+                      diagnosticMessage(containsString("Invalid desugared library configuration")),
+                      diagnosticMessage(containsString("Expected required key '" + key + "'")),
+                      diagnosticOrigin(origin))));
+    }
+  }
+
+  @Test
+  public void testUnsupportedBelow() {
+    LinkedHashMap<String, Object> data = template();
+    data.put("configuration_format_version", 4);
+    runFailing(
+        toJson(data),
+        diagnostics ->
+            diagnostics.assertErrorsMatch(
+                allOf(
+                    diagnosticMessage(containsString("upgrade the configuration file")),
+                    diagnosticOrigin(origin))));
+  }
+
+  @Test
+  public void testUnsupportedAbove() {
+    LinkedHashMap<String, Object> data = template();
+    data.put("configuration_format_version", 100000);
+    runFailing(
+        toJson(data),
+        diagnostics ->
+            diagnostics.assertErrorsMatch(
+                allOf(
+                    diagnosticMessage(containsString("upgrade the D8/R8 compiler")),
+                    diagnosticOrigin(origin))));
+  }
+
+  @Test
+  public void testCustomAndWrapperOverlap() {
+    LinkedHashMap<String, Object> data = template();
+    data.put(
+        "common_flags",
+        ImmutableList.of(
+            ImmutableMap.of(
+                "api_level_below_or_equal",
+                100000,
+                "custom_conversion",
+                ImmutableMap.of("java.util.Foo", "j$.util.FooConv"),
+                "wrapper_conversion",
+                ImmutableList.of("java.util.Foo"))));
+    runFailing(
+        toJson(data),
+        diagnostics ->
+            diagnostics.assertErrorsMatch(
+                allOf(
+                    diagnosticMessage(containsString("Duplicate types")),
+                    diagnosticMessage(containsString("java.util.Foo")),
+                    diagnosticOrigin(origin))));
+  }
+
+  @Test
+  public void testRedefinition() {
+    LinkedHashMap<String, Object> data = template();
+    data.put(
+        "common_flags",
+        ImmutableList.of(
+            ImmutableMap.of(
+                "api_level_below_or_equal",
+                100000,
+                "custom_conversion",
+                ImmutableMap.of("java.util.Foo", "j$.util.FooConv1"))));
+    data.put(
+        "library_flags",
+        ImmutableList.of(
+            ImmutableMap.of(
+                "api_level_below_or_equal",
+                100000,
+                "custom_conversion",
+                ImmutableMap.of("java.util.Foo", "j$.util.FooConv2"))));
+    runFailing(
+        toJson(data),
+        diagnostics ->
+            diagnostics.assertErrorsMatch(
+                allOf(
+                    diagnosticMessage(containsString("Duplicate assignment of key")),
+                    diagnosticMessage(containsString("java.util.Foo")),
+                    diagnosticMessage(containsString("custom_conversion")),
+                    diagnosticOrigin(origin))));
+  }
+
+  @Test
+  public void testDuplicate() {
+    LinkedHashMap<String, Object> data = template();
+    data.put(
+        "common_flags",
+        ImmutableList.of(
+            ImmutableMap.of(
+                "api_level_below_or_equal",
+                100000,
+                "custom_conversion",
+                ImmutableMap.of(
+                    "java.util.Foo", "j$.util.FooConv1",
+                    "java.util.Foo2", "j$.util.FooConv2"))));
+    // The gson parser will overwrite the key in order during parsing, thus hiding potential issues.
+    DesugaredLibraryConfiguration config = runPassing(toJson(data).replace("Foo2", "Foo"));
+    assertEquals(
+        Collections.singletonList("java.util.Foo"),
+        config.getCustomConversions().keySet().stream()
+            .map(DexType::toString)
+            .collect(Collectors.toList()));
+    assertEquals(
+        Collections.singletonList("j$.util.FooConv2"),
+        config.getCustomConversions().values().stream()
+            .map(DexType::toString)
+            .collect(Collectors.toList()));
+  }
+
+  // JSON building helpers.
+  // This does not use gson to make the text input construction independent of gson.
+
+  private static String toJson(Map<String, Object> data) {
+    StringBuilder builder = new StringBuilder();
+    toJsonObject(data, builder);
+    return builder.toString();
+  }
+
+  private static void toJsonObject(Map<String, Object> data, StringBuilder builder) {
+    StringUtils.append(
+        builder,
+        ListUtils.map(
+            data.entrySet(),
+            entry -> "\n  " + quote(entry.getKey()) + ": " + toJsonElement(entry.getValue())),
+        ", ",
+        BraceType.TUBORG);
+  }
+
+  private static String toJsonElement(Object element) {
+    StringBuilder builder = new StringBuilder();
+    toJsonElement(element, builder);
+    return builder.toString();
+  }
+
+  private static void toJsonElement(Object element, StringBuilder builder) {
+    if (element instanceof String) {
+      builder.append(quote((String) element));
+    } else if (element instanceof Integer) {
+      builder.append(element);
+    } else if (element instanceof List) {
+      toJsonList((List<Object>) element, builder);
+    } else if (element instanceof Map) {
+      toJsonObject((Map<String, Object>) element, builder);
+    } else {
+      throw new IllegalStateException("Unexpected object type: " + element.getClass());
+    }
+  }
+
+  private static void toJsonList(List<Object> element, StringBuilder builder) {
+    StringUtils.append(
+        builder, ListUtils.map(element, o -> "\n  " + toJsonElement(o)), ", ", BraceType.SQUARE);
+  }
+
+  private static String quote(String str) {
+    return "\"" + str + "\"";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
new file mode 100644
index 0000000..846b65d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
@@ -0,0 +1,304 @@
+// 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.desugar.desugaredlibrary;
+
+import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.GenerateLintFiles;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.NoneRuntime;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.WorkList;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ExtractWrapperTypesTest extends TestBase {
+
+  // Filter on types that do not need to be considered for wrapping.
+  private static boolean doesNotNeedWrapper(String type, Set<String> customConversions) {
+    return excludePackage(type)
+        || NOT_NEEDED_NOT_IN_DOCS.contains(type)
+        || FINAL_CLASSES.contains(type)
+        || customConversions.contains(type);
+  }
+
+  private static boolean excludePackage(String type) {
+    return type.startsWith("java.lang.")
+        || type.startsWith("java.nio.")
+        || type.startsWith("java.security.")
+        || type.startsWith("java.net.")
+        || type.startsWith("java.awt.")
+        || type.startsWith("java.util.concurrent.");
+  }
+
+  // Types not picked up by the android.jar scan but for which wrappers are needed.
+  private static final Set<String> ADDITIONAL_WRAPPERS = ImmutableSet.of();
+
+  // Types not in API docs, referenced in android.jar and must be wrapped.
+  private static final Set<String> NEEDED_BUT_NOT_IN_DOCS = ImmutableSet.of();
+
+  // Types not in API docs, referenced in android.jar but need not be wrapped.
+  private static final Set<String> NOT_NEEDED_NOT_IN_DOCS =
+      ImmutableSet.of(
+          "java.util.Base64$Decoder",
+          "java.util.Base64$Encoder",
+          "java.util.Calendar$Builder",
+          "java.util.Locale$Builder",
+          "java.util.Locale$Category",
+          "java.util.Locale$FilteringMode",
+          "java.util.SplittableRandom");
+
+  // List of referenced final classes (cannot be wrapper converted) with no custom conversions.
+  private static final Set<String> FINAL_CLASSES =
+      ImmutableSet.of(
+          // TODO(b/159304624): Does this need custom conversion?
+          "java.time.Period");
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  // TODO: parameterize to check both api<=23 as well as 23<api<26 for which the spec differs.
+  private final AndroidApiLevel minApi = AndroidApiLevel.B;
+  private final AndroidApiLevel targetApi = AndroidApiLevel.Q;
+
+  public ExtractWrapperTypesTest(TestParameters parameters) {
+    assertEquals(NoneRuntime.getInstance(), parameters.getRuntime());
+  }
+
+  @Test
+  public void checkConsistency() {
+    List<Set<String>> sets =
+        ImmutableList.of(
+            ADDITIONAL_WRAPPERS, NEEDED_BUT_NOT_IN_DOCS, NOT_NEEDED_NOT_IN_DOCS, FINAL_CLASSES);
+    for (Set<String> set1 : sets) {
+      for (Set<String> set2 : sets) {
+        if (set1 != set2) {
+          assertEquals(Collections.emptySet(), Sets.intersection(set1, set2));
+        }
+      }
+    }
+    for (Set<String> set : sets) {
+      for (String type : set) {
+        assertFalse(excludePackage(type));
+      }
+    }
+  }
+
+  @Test
+  public void test() throws Exception {
+    CodeInspector desugaredApiJar = getDesugaredApiJar();
+    Set<ClassReference> preDesugarTypes = getPreDesugarTypes();
+
+    DesugaredLibraryConfiguration conf = getDesugaredLibraryConfiguration();
+    Set<String> wrappersInSpec =
+        conf.getWrapperConversions().stream().map(DexType::toString).collect(Collectors.toSet());
+    Set<String> customConversionsInSpec =
+        conf.getCustomConversions().keySet().stream()
+            .map(DexType::toString)
+            .collect(Collectors.toSet());
+    assertEquals(
+        Collections.emptySet(), Sets.intersection(wrappersInSpec, customConversionsInSpec));
+    assertEquals(Collections.emptySet(), Sets.intersection(FINAL_CLASSES, customConversionsInSpec));
+
+    CodeInspector nonDesugaredJar = new CodeInspector(ToolHelper.getAndroidJar(targetApi));
+    Map<ClassReference, Set<MethodReference>> directWrappers =
+        getDirectlyReferencedWrapperTypes(
+            desugaredApiJar, preDesugarTypes, nonDesugaredJar, customConversionsInSpec);
+    Map<ClassReference, Set<ClassReference>> indirectWrappers =
+        getIndirectlyReferencedWrapperTypes(
+            directWrappers, preDesugarTypes, nonDesugaredJar, customConversionsInSpec);
+
+    {
+      Set<String> missingWrappers = getMissingWrappers(directWrappers, wrappersInSpec);
+      assertTrue(
+          "Missing direct wrappers:\n" + String.join("\n", missingWrappers),
+          missingWrappers.isEmpty());
+    }
+
+    {
+      Set<String> missingWrappers = getMissingWrappers(indirectWrappers, wrappersInSpec);
+      assertTrue(
+          "Missing indirect wrappers:\n" + String.join("\n", missingWrappers),
+          missingWrappers.isEmpty());
+    }
+
+    Set<String> additionalWrappers = new TreeSet<>();
+    for (String wrapper : wrappersInSpec) {
+      ClassReference item = Reference.classFromTypeName(wrapper);
+      if (!directWrappers.containsKey(item)
+          && !indirectWrappers.containsKey(item)
+          && !ADDITIONAL_WRAPPERS.contains(wrapper)) {
+        additionalWrappers.add(wrapper);
+      }
+    }
+    assertTrue(
+        "Additional wrapper:\n" + String.join("\n", additionalWrappers),
+        additionalWrappers.isEmpty());
+
+    assertEquals(
+        directWrappers.size() + indirectWrappers.size() + ADDITIONAL_WRAPPERS.size(),
+        wrappersInSpec.size());
+  }
+
+  private static <T> Set<String> getMissingWrappers(
+      Map<ClassReference, Set<T>> expected, Set<String> wrappersInSpec) {
+    Set<String> missingWrappers = new TreeSet<>();
+    for (ClassReference addition : expected.keySet()) {
+      String item = descriptorToJavaType(addition.getDescriptor());
+      if (!wrappersInSpec.contains(item)) {
+        missingWrappers.add(item + " referenced from: " + expected.get(addition));
+      }
+    }
+    return missingWrappers;
+  }
+
+  private DesugaredLibraryConfiguration getDesugaredLibraryConfiguration() {
+    DesugaredLibraryConfigurationParser parser =
+        new DesugaredLibraryConfigurationParser(
+            new DexItemFactory(), null, true, minApi.getLevel());
+    return parser.parse(StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+  }
+
+  private Map<ClassReference, Set<MethodReference>> getDirectlyReferencedWrapperTypes(
+      CodeInspector desugaredApiJar,
+      Set<ClassReference> preDesugarTypes,
+      CodeInspector nonDesugaredJar,
+      Set<String> customConversions) {
+    Map<ClassReference, Set<MethodReference>> directWrappers = new HashMap<>();
+    nonDesugaredJar.forAllClasses(
+        clazz -> {
+          clazz.forAllMethods(
+              method -> {
+                if (!method.isPublic() && !method.isProtected()) {
+                  return;
+                }
+                if (desugaredApiJar.method(method.asMethodReference()).isPresent()) {
+                  return;
+                }
+                Consumer<ClassReference> adder =
+                    t ->
+                        directWrappers
+                            .computeIfAbsent(t, k -> new HashSet<>())
+                            .add(method.asMethodReference());
+                MethodSignature signature = method.getFinalSignature().asMethodSignature();
+                addType(adder, signature.type, preDesugarTypes, customConversions);
+                for (String parameter : signature.parameters) {
+                  addType(adder, parameter, preDesugarTypes, customConversions);
+                }
+              });
+        });
+    return directWrappers;
+  }
+
+  private Map<ClassReference, Set<ClassReference>> getIndirectlyReferencedWrapperTypes(
+      Map<ClassReference, Set<MethodReference>> directWrappers,
+      Set<ClassReference> existing,
+      CodeInspector latest,
+      Set<String> customConversions) {
+    Map<ClassReference, Set<ClassReference>> indirectWrappers = new HashMap<>();
+    WorkList<ClassReference> worklist = WorkList.newEqualityWorkList(directWrappers.keySet());
+    while (worklist.hasNext()) {
+      ClassReference reference = worklist.next();
+      ClassSubject clazz = latest.clazz(reference);
+      clazz.forAllVirtualMethods(
+          method -> {
+            assertTrue(method.toString(), method.isPublic() || method.isProtected());
+            MethodSignature signature = method.getFinalSignature().asMethodSignature();
+            Consumer<ClassReference> adder =
+                t -> {
+                  if (worklist.addIfNotSeen(t)) {
+                    indirectWrappers.computeIfAbsent(t, k -> new HashSet<>()).add(reference);
+                  }
+                };
+            addType(adder, signature.type, existing, customConversions);
+            for (String parameter : signature.parameters) {
+              addType(adder, parameter, existing, customConversions);
+            }
+          });
+    }
+    return indirectWrappers;
+  }
+
+  private Set<ClassReference> getPreDesugarTypes() throws IOException {
+    Set<ClassReference> existing = new HashSet<>();
+    Path androidJar = ToolHelper.getAndroidJar(minApi);
+    new CodeInspector(androidJar)
+        .forAllClasses(
+            clazz -> {
+              if (clazz.getFinalName().startsWith("java.")) {
+                existing.add(Reference.classFromTypeName(clazz.getFinalName()));
+              }
+            });
+    return existing;
+  }
+
+  private CodeInspector getDesugaredApiJar() throws Exception {
+    Path out = temp.newFolder().toPath();
+    GenerateLintFiles desugaredApi =
+        new GenerateLintFiles(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString(), out.toString());
+    desugaredApi.run(targetApi.getLevel());
+    return new CodeInspector(
+        out.resolve("compile_api_level_" + targetApi.getLevel())
+            .resolve("desugared_apis_" + targetApi.getLevel() + "_" + minApi.getLevel() + ".jar"));
+  }
+
+  private void addType(
+      Consumer<ClassReference> additions,
+      String type,
+      Set<ClassReference> preDesugarTypes,
+      Set<String> customConversions) {
+    if (type.equals("void")) {
+      return;
+    }
+    TypeReference typeReference = Reference.typeFromTypeName(type);
+    if (typeReference.isArray()) {
+      typeReference = typeReference.asArray().getBaseType();
+    }
+    if (typeReference.isClass()) {
+      ClassReference clazz = typeReference.asClass();
+      String clazzType = descriptorToJavaType(clazz.getDescriptor());
+      if (clazzType.startsWith("java.")
+          && !doesNotNeedWrapper(clazzType, customConversions)
+          && !preDesugarTypes.contains(clazz)) {
+        additions.accept(clazz);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
index 106dd72..e8aa581 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
@@ -20,6 +20,7 @@
 import java.util.function.IntSupplier;
 import java.util.function.LongConsumer;
 import java.util.function.LongSupplier;
+import java.util.function.Supplier;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -67,6 +68,7 @@
         .addLibraryClasses(CustomLibClass.class)
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
+        .assertNoMessages()
         .addDesugaredCoreLibraryRunClassPath(
             this::buildDesugaredLibrary,
             parameters.getApiLevel(),
@@ -106,16 +108,16 @@
       BiFunction<String, String, Character> biFunction =
           CustomLibClass.mixBiFunctions((String i, String j) -> i + j, (String s) -> s.charAt(1));
       System.out.println(biFunction.apply("1", "2"));
-      BooleanSupplier booleanSupplier = CustomLibClass.mixBoolSuppliers(() -> true, () -> false);
+      BooleanSupplier booleanSupplier =
+          () -> CustomLibClass.mixBoolSuppliers(() -> true, () -> false).get();
       System.out.println(booleanSupplier.getAsBoolean());
       LongConsumer longConsumer = CustomLibClass.mixLong(() -> 1L, System.out::println);
       longConsumer.accept(2L);
       DoublePredicate doublePredicate =
           CustomLibClass.mixPredicate(d -> d > 1.0, d -> d == 2.0, d -> d < 3.0);
       System.out.println(doublePredicate.test(2.0));
-      // Reverse wrapper should not exist.
       System.out.println(CustomLibClass.extractInt(() -> 5));
-      System.out.println(CustomLibClass.getDoubleSupplier().getAsDouble());
+      System.out.println(CustomLibClass.getDoubleSupplier().get());
     }
 
     static class Object1 {}
@@ -163,13 +165,18 @@
       return operator.andThen(function);
     }
 
-    public static BooleanSupplier mixBoolSuppliers(
-        BooleanSupplier supplier1, BooleanSupplier supplier2) {
-      return () -> supplier1.getAsBoolean() && supplier2.getAsBoolean();
+    // BooleanSupplier is not a wrapped type, so it can't be placed on the boundary.
+    public static Supplier<Boolean> mixBoolSuppliers(
+        Supplier<Boolean> supplier1, Supplier<Boolean> supplier2) {
+      BooleanSupplier wrap1 = supplier1::get;
+      BooleanSupplier wrap2 = supplier2::get;
+      return () -> wrap1.getAsBoolean() && wrap2.getAsBoolean();
     }
 
-    public static LongConsumer mixLong(LongSupplier supplier, LongConsumer consumer) {
-      return l -> consumer.accept(l + supplier.getAsLong());
+    // LongSupplier is not a wrapped type, so it can't be placed on the boundary.
+    public static LongConsumer mixLong(Supplier<Long> supplier, LongConsumer consumer) {
+      LongSupplier wrap = supplier::get;
+      return l -> consumer.accept(l + wrap.getAsLong());
     }
 
     public static DoublePredicate mixPredicate(
@@ -177,12 +184,16 @@
       return predicate1.and(predicate2).and(predicate3);
     }
 
-    public static int extractInt(IntSupplier supplier) {
-      return supplier.getAsInt();
+    // IntSupplier is not a wrapped type, so it can't be placed on the boundary.
+    public static int extractInt(Supplier<Integer> supplier) {
+      IntSupplier wrap = supplier::get;
+      return wrap.getAsInt();
     }
 
-    public static DoubleSupplier getDoubleSupplier() {
-      return () -> 42.0;
+    // DoubleSupplier is not a wrapped type, so it can't be placed on the boundary.
+    public static Supplier<Double> getDoubleSupplier() {
+      DoubleSupplier supplier = () -> 42.0;
+      return supplier::getAsDouble;
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperPlacementTest.java
similarity index 93%
rename from src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
rename to src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperPlacementTest.java
index c95420c..cbab10c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperPlacementTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestParameters;
@@ -26,7 +27,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class WrapperMergeTest extends DesugaredLibraryTestBase {
+public class WrapperPlacementTest extends DesugaredLibraryTestBase {
 
   private static final String EXPECTED = StringUtils.lines("[1, 2, 3]", "[2, 3, 4]");
 
@@ -37,7 +38,7 @@
 
   private final TestParameters parameters;
 
-  public WrapperMergeTest(TestParameters parameters) {
+  public WrapperPlacementTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -96,11 +97,8 @@
 
   private void assertCoreLibContainsWrappers(Path coreLib) throws IOException {
     CodeInspector inspector = new CodeInspector(coreLib);
-    // TODO(b/158645207): Consider having wrappers in the spec and avoiding this issue.
-    assertEquals(
-        "Number of generated wrappers changes. Manually verify validity of the change.",
-        144,
-        getWrappers(inspector).count());
+    Stream<FoundClassSubject> wrappers = getWrappers(inspector);
+    assertNotEquals(0, wrappers.count());
   }
 
   private void assertNoWrappers(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java b/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java
index 1363bbd..c81a2d6 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java
@@ -75,12 +75,10 @@
                 com.android.tools.r8.memberrebinding.b135627418.runtime.InsetDrawable.class)
             .setMinApi(parameters.getRuntime())
             .addOptionsModification(
-                options -> {
-                  options.desugaredLibraryConfiguration =
-                      DesugaredLibraryConfiguration.withOnlyRewritePrefixForTesting(
-                          ImmutableMap.of(packageName + ".runtime", packageName + ".library"));
-                  options.desugaredLibraryBootclasspathDefinitions = type -> false;
-                })
+                options ->
+                    options.desugaredLibraryConfiguration =
+                        DesugaredLibraryConfiguration.withOnlyRewritePrefixForTesting(
+                            ImmutableMap.of(packageName + ".runtime", packageName + ".library")))
             .compile();
 
     testForR8(parameters.getBackend())