Test missing classes from lambda signatures

Bug: 179466825
Change-Id: I5e2ca3b944f2b2fd91aa71417d875a141f234b2d
diff --git a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingClassAccessContexts.java b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingClassAccessContexts.java
index 3406c0d..23abebb 100644
--- a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingClassAccessContexts.java
+++ b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingClassAccessContexts.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDerivedContext;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
@@ -68,8 +69,10 @@
 
     private final Set<DexReference> contexts = Sets.newIdentityHashSet();
 
-    Builder addAll(Set<DexReference> contexts) {
-      this.contexts.addAll(contexts);
+    Builder addAll(Set<ProgramDerivedContext> contexts) {
+      for (ProgramDerivedContext context : contexts) {
+        this.contexts.add(context.getContext().getReference());
+      }
       return this;
     }
 
diff --git a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java
index 044b1d5..ab2d603 100644
--- a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java
+++ b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java
@@ -7,8 +7,8 @@
 import com.android.tools.r8.diagnostic.MissingDefinitionInfo;
 import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
 import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDerivedContext;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.references.ClassReference;
@@ -24,13 +24,11 @@
 
 public class MissingDefinitionsDiagnosticImpl implements MissingDefinitionsDiagnostic {
 
-  private final boolean fatal;
   private final SortedMap<ClassReference, MissingClassAccessContexts> missingClasses;
 
   private MissingDefinitionsDiagnosticImpl(
-      boolean fatal, SortedMap<ClassReference, MissingClassAccessContexts> missingClasses) {
+      SortedMap<ClassReference, MissingClassAccessContexts> missingClasses) {
     assert !missingClasses.isEmpty();
-    this.fatal = fatal;
     this.missingClasses = missingClasses;
   }
 
@@ -62,30 +60,6 @@
 
   @Override
   public String getDiagnosticMessage() {
-    return fatal ? getFatalDiagnosticMessage() : getNonFatalDiagnosticMessage();
-  }
-
-  private String getFatalDiagnosticMessage() {
-    if (missingClasses.size() == 1) {
-      StringBuilder builder =
-          new StringBuilder(
-              "Compilation can't be completed because the following class is missing: ");
-      writeMissingClass(builder, missingClasses.entrySet().iterator().next());
-      return builder.append(".").toString();
-    }
-
-    StringBuilder builder =
-        new StringBuilder("Compilation can't be completed because the following ")
-            .append(missingClasses.size())
-            .append(" classes are missing:");
-    missingClasses.forEach(
-        (missingClass, contexts) ->
-            writeMissingClass(
-                builder.append(System.lineSeparator()).append("- "), missingClass, contexts));
-    return builder.toString();
-  }
-
-  private String getNonFatalDiagnosticMessage() {
     StringBuilder builder = new StringBuilder();
     Iterator<Entry<ClassReference, MissingClassAccessContexts>> missingClassesIterator =
         missingClasses.entrySet().iterator();
@@ -119,12 +93,11 @@
 
   public static class Builder {
 
-    private boolean fatal;
     private ImmutableSortedMap.Builder<ClassReference, MissingClassAccessContexts>
         missingClassesBuilder =
             ImmutableSortedMap.orderedBy(Comparator.comparing(ClassReference::getDescriptor));
 
-    public Builder addMissingClasses(Map<DexType, Set<DexReference>> missingClasses) {
+    public Builder addMissingClasses(Map<DexType, Set<ProgramDerivedContext>> missingClasses) {
       missingClasses.forEach(
           (missingClass, contexts) ->
               missingClassesBuilder.put(
@@ -133,13 +106,8 @@
       return this;
     }
 
-    public Builder setFatal(boolean fatal) {
-      this.fatal = fatal;
-      return this;
-    }
-
     public MissingDefinitionsDiagnostic build() {
-      return new MissingDefinitionsDiagnosticImpl(fatal, missingClassesBuilder.build());
+      return new MissingDefinitionsDiagnosticImpl(missingClassesBuilder.build());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/errors/dontwarn/DontWarnConfiguration.java b/src/main/java/com/android/tools/r8/errors/dontwarn/DontWarnConfiguration.java
index ff4a73d..d14300b 100644
--- a/src/main/java/com/android/tools/r8/errors/dontwarn/DontWarnConfiguration.java
+++ b/src/main/java/com/android/tools/r8/errors/dontwarn/DontWarnConfiguration.java
@@ -4,11 +4,10 @@
 
 package com.android.tools.r8.errors.dontwarn;
 
-import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.Definition;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.utils.InternalOptions;
-import java.util.Set;
 
 public abstract class DontWarnConfiguration {
 
@@ -24,10 +23,8 @@
     return new EmptyDontWarnConfiguration();
   }
 
-  public abstract Set<DexType> getNonMatches(Set<DexType> types);
-
-  public final boolean matches(DexClass clazz) {
-    return matches(clazz.getType());
+  public final boolean matches(Definition clazz) {
+    return matches(clazz.getContextType());
   }
 
   public abstract boolean matches(DexType type);
diff --git a/src/main/java/com/android/tools/r8/errors/dontwarn/EmptyDontWarnConfiguration.java b/src/main/java/com/android/tools/r8/errors/dontwarn/EmptyDontWarnConfiguration.java
index e115168..4cc7f04 100644
--- a/src/main/java/com/android/tools/r8/errors/dontwarn/EmptyDontWarnConfiguration.java
+++ b/src/main/java/com/android/tools/r8/errors/dontwarn/EmptyDontWarnConfiguration.java
@@ -6,16 +6,10 @@
 
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.InternalOptions;
-import java.util.Set;
 
 public class EmptyDontWarnConfiguration extends DontWarnConfiguration {
 
   @Override
-  public Set<DexType> getNonMatches(Set<DexType> types) {
-    return types;
-  }
-
-  @Override
   public boolean matches(DexType type) {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/errors/dontwarn/NonEmptyDontWarnConfiguration.java b/src/main/java/com/android/tools/r8/errors/dontwarn/NonEmptyDontWarnConfiguration.java
index 89bcb10..ae31723 100644
--- a/src/main/java/com/android/tools/r8/errors/dontwarn/NonEmptyDontWarnConfiguration.java
+++ b/src/main/java/com/android/tools/r8/errors/dontwarn/NonEmptyDontWarnConfiguration.java
@@ -29,17 +29,6 @@
   }
 
   @Override
-  public Set<DexType> getNonMatches(Set<DexType> types) {
-    Set<DexType> nonMatches = Sets.newIdentityHashSet();
-    for (DexType type : types) {
-      if (!matches(type)) {
-        nonMatches.add(type);
-      }
-    }
-    return nonMatches;
-  }
-
-  @Override
   public boolean matches(DexType type) {
     for (ProguardClassNameList dontWarnPattern : dontWarnPatterns) {
       if (dontWarnPattern.matches(type)) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 8b09674..7622121 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -39,6 +39,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableSet;
@@ -454,6 +455,10 @@
     return appInfo.options();
   }
 
+  public Reporter reporter() {
+    return options().reporter;
+  }
+
   public TestingOptions testing() {
     return options().testing;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/Definition.java b/src/main/java/com/android/tools/r8/graph/Definition.java
index a7e4ed9..abf7c07 100644
--- a/src/main/java/com/android/tools/r8/graph/Definition.java
+++ b/src/main/java/com/android/tools/r8/graph/Definition.java
@@ -18,4 +18,12 @@
   DexType getContextType();
 
   DexReference getReference();
+
+  default boolean isProgramDefinition() {
+    return false;
+  }
+
+  default ProgramDefinition asProgramDefinition() {
+    return null;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
index 862efbc..7310270 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
@@ -38,6 +38,16 @@
     return null;
   }
 
+  @Override
+  default boolean isProgramDefinition() {
+    return true;
+  }
+
+  @Override
+  default ProgramDefinition asProgramDefinition() {
+    return this;
+  }
+
   default boolean isProgramField() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDerivedContext.java b/src/main/java/com/android/tools/r8/graph/ProgramDerivedContext.java
index f9ec920..d46cf33 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDerivedContext.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDerivedContext.java
@@ -7,4 +7,8 @@
 public interface ProgramDerivedContext {
 
   Definition getContext();
+
+  default boolean isProgramContext() {
+    return getContext().isProgramDefinition();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 4041efd..cb446d6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -24,6 +24,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /**
@@ -41,8 +42,9 @@
   }
 
   public static R8CfInstructionDesugaringEventConsumer createForR8(
-      AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return new R8CfInstructionDesugaringEventConsumer(appView);
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer) {
+    return new R8CfInstructionDesugaringEventConsumer(appView, lambdaClassConsumer);
   }
 
   public static CfInstructionDesugaringEventConsumer createForDesugaredCode() {
@@ -162,14 +164,17 @@
       extends CfInstructionDesugaringEventConsumer {
 
     private final AppView<? extends AppInfoWithClassHierarchy> appView;
+    private final BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer;
 
     private final Map<LambdaClass, ProgramMethod> synthesizedLambdaClasses =
         new IdentityHashMap<>();
     private final List<InvokeSpecialBridgeInfo> pendingInvokeSpecialBridges = new ArrayList<>();
 
     public R8CfInstructionDesugaringEventConsumer(
-        AppView<? extends AppInfoWithClassHierarchy> appView) {
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer) {
       this.appView = appView;
+      this.lambdaClassConsumer = lambdaClassConsumer;
     }
 
     @Override
@@ -184,6 +189,7 @@
       synchronized (synthesizedLambdaClasses) {
         synthesizedLambdaClasses.put(lambdaClass, context);
       }
+      lambdaClassConsumer.accept(lambdaClass, context);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 2f7edb2..caa97fd 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -90,6 +90,7 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.R8CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.LambdaClass;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.kotlin.KotlinMetadataEnqueuerExtension;
@@ -108,6 +109,7 @@
 import com.android.tools.r8.shaking.RootSetUtils.RootSet;
 import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
 import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
+import com.android.tools.r8.synthesis.SyntheticItems.SynthesizingContextOracle;
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
@@ -249,6 +251,11 @@
   private final Map<DexProgramClass, ProgramFieldSet> reachableInstanceFields =
       Maps.newIdentityHashMap();
 
+  // TODO(b/180091213): Remove when supported by synthetic items.
+  /** The synthesizing contexts for the synthesized lambda classes. */
+  private final Map<DexProgramClass, ProgramMethod> lambdaSynthesizingContexts =
+      new IdentityHashMap<>();
+
   /**
    * Set of types that are mentioned in the program. We at least need an empty abstract class item
    * for these.
@@ -3202,7 +3209,8 @@
       return;
     }
     R8CfInstructionDesugaringEventConsumer desugaringEventConsumer =
-        CfInstructionDesugaringEventConsumer.createForR8(appView);
+        CfInstructionDesugaringEventConsumer.createForR8(
+            appView, this::recordLambdaSynthesizingContext);
     ThreadUtils.processItems(
         pendingDesugaring,
         method ->
@@ -3213,6 +3221,12 @@
     pendingDesugaring.clear();
   }
 
+  private void recordLambdaSynthesizingContext(LambdaClass lambdaClass, ProgramMethod context) {
+    synchronized (lambdaSynthesizingContexts) {
+      lambdaSynthesizingContexts.put(lambdaClass.getLambdaProgramClass(), context);
+    }
+  }
+
   private void synthesizeInterfaceMethodBridges(SyntheticAdditions additions) {
     for (ProgramMethod bridge : syntheticInterfaceMethodBridges.values()) {
       DexProgramClass holder = bridge.getHolder();
@@ -3305,6 +3319,13 @@
     // Verify the references on the pruned application after type synthesis.
     assert verifyReferences(app);
 
+    SynthesizingContextOracle lambdaSynthesizingContextOracle =
+        syntheticClass -> {
+          ProgramMethod lambdaSynthesisContext = lambdaSynthesizingContexts.get(syntheticClass);
+          return lambdaSynthesisContext != null
+              ? ImmutableSet.of(lambdaSynthesisContext.getReference())
+              : ImmutableSet.of(syntheticClass.getType());
+        };
     AppInfoWithLiveness appInfoWithLiveness =
         new AppInfoWithLiveness(
             appInfo.getSyntheticItems().commit(app),
@@ -3312,7 +3333,10 @@
             appInfo.getMainDexInfo(),
             deadProtoTypes,
             appView.testing().enableExperimentalMissingClassesReporting
-                ? missingClassesBuilder.reportMissingClasses(appView)
+                ? (mode.isInitialTreeShaking()
+                    ? missingClassesBuilder.reportMissingClasses(
+                        appView, lambdaSynthesizingContextOracle)
+                    : missingClassesBuilder.assertNoMissingClasses(appView))
                 : missingClassesBuilder.ignoreMissingClasses(),
             SetUtils.mapIdentityHashSet(liveTypes.getItems(), DexProgramClass::getType),
             Enqueuer.toDescriptorSet(targetedMethods.getItems()),
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
index aa49727..9f306c0 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -7,24 +7,30 @@
 import static com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.DESCRIPTOR_VIVIFIED_PREFIX;
 import static com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter.getRetargetPackageAndClassPrefixDescriptor;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.EMULATE_LIBRARY_CLASS_NAME_SUFFIX;
+import static com.android.tools.r8.utils.collections.IdentityHashSetFromMap.newProgramDerivedContextSet;
 
 import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
 import com.android.tools.r8.diagnostic.internal.MissingDefinitionsDiagnosticImpl;
 import com.android.tools.r8.errors.dontwarn.DontWarnConfiguration;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramDerivedContext;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.synthesis.CommittedItems;
+import com.android.tools.r8.synthesis.SyntheticItems.SynthesizingContextOracle;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.Collection;
 import java.util.IdentityHashMap;
+import java.util.Iterator;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.function.Predicate;
 
@@ -62,7 +68,8 @@
   public static class Builder {
 
     private final Set<DexType> alreadyMissingClasses;
-    private final Map<DexType, Set<DexReference>> newMissingClasses = new IdentityHashMap<>();
+    private final Map<DexType, Set<ProgramDerivedContext>> newMissingClasses =
+        new IdentityHashMap<>();
 
     // Set of missing types that are not to be reported as missing. This does not hide reports
     // if the same type is in newMissingClasses in which case it is reported regardless.
@@ -81,16 +88,15 @@
       assert context.getContext().getContextType() != type;
       if (!alreadyMissingClasses.contains(type)) {
         newMissingClasses
-            .computeIfAbsent(type, ignore -> Sets.newIdentityHashSet())
-            .add(context.getContext().getReference());
+            .computeIfAbsent(type, ignore -> newProgramDerivedContextSet())
+            .add(context);
       }
     }
 
     public void legacyAddNewMissingClass(DexType type) {
       if (!alreadyMissingClasses.contains(type)) {
-        // The legacy reporting is context insensitive, so therefore we use the missing classes
-        // themselves as contexts.
-        newMissingClasses.computeIfAbsent(type, ignore -> Sets.newIdentityHashSet()).add(type);
+        // The legacy reporting is context insensitive, so we just use an empty set of contexts.
+        newMissingClasses.computeIfAbsent(type, ignore -> newProgramDerivedContextSet());
       }
     }
 
@@ -115,34 +121,109 @@
       return this;
     }
 
+    public MissingClasses assertNoMissingClasses(AppView<?> appView) {
+      assert getMissingClassesToBeReported(appView, clazz -> ImmutableSet.of(clazz.getType()))
+          .isEmpty();
+      return build();
+    }
+
     @Deprecated
     public MissingClasses ignoreMissingClasses() {
       return build();
     }
 
     public MissingClasses reportMissingClasses(AppView<?> appView) {
-      InternalOptions options = appView.options();
-      Map<DexType, Set<DexReference>> missingClassesToBeReported =
-          getMissingClassesToBeReported(appView);
+      return reportMissingClasses(appView, clazz -> ImmutableSet.of(clazz.getType()));
+    }
+
+    public MissingClasses reportMissingClasses(
+        AppView<?> appView, SynthesizingContextOracle synthesizingContextOracle) {
+      Map<DexType, Set<ProgramDerivedContext>> missingClassesToBeReported =
+          getMissingClassesToBeReported(appView, synthesizingContextOracle);
       if (!missingClassesToBeReported.isEmpty()) {
         MissingDefinitionsDiagnostic diagnostic =
             MissingDefinitionsDiagnosticImpl.builder()
                 .addMissingClasses(missingClassesToBeReported)
-                .setFatal(!options.ignoreMissingClasses)
                 .build();
-        if (options.ignoreMissingClasses) {
-          options.reporter.warning(diagnostic);
+        if (appView.options().ignoreMissingClasses) {
+          appView.reporter().warning(diagnostic);
         } else {
-          throw options.reporter.fatalError(diagnostic);
+          throw appView.reporter().fatalError(diagnostic);
         }
       }
       return build();
     }
 
-    private Map<DexType, Set<DexReference>> getMissingClassesToBeReported(AppView<?> appView) {
+    private void rewriteMissingClassContexts(
+        AppView<?> appView, SynthesizingContextOracle synthesizingContextOracle) {
+      Iterator<Entry<DexType, Set<ProgramDerivedContext>>> newMissingClassesIterator =
+          newMissingClasses.entrySet().iterator();
+      while (newMissingClassesIterator.hasNext()) {
+        Entry<DexType, Set<ProgramDerivedContext>> entry = newMissingClassesIterator.next();
+        entry.setValue(
+            rewriteMissingClassContextsForSingleMissingClass(
+                appView, entry.getValue(), synthesizingContextOracle));
+      }
+    }
+
+    private static Set<ProgramDerivedContext> rewriteMissingClassContextsForSingleMissingClass(
+        AppView<?> appView,
+        Set<ProgramDerivedContext> contexts,
+        SynthesizingContextOracle synthesizingContextOracle) {
+      if (contexts.isEmpty()) {
+        // Legacy reporting does not have any contexts.
+        return contexts;
+      }
+
+      Set<ProgramDerivedContext> rewrittenContexts = newProgramDerivedContextSet();
+      for (ProgramDerivedContext context : contexts) {
+        if (!context.isProgramContext()) {
+          rewrittenContexts.add(context);
+          continue;
+        }
+
+        DexProgramClass clazz = context.getContext().asProgramDefinition().getContextClass();
+        if (!appView.getSyntheticItems().isSyntheticClass(clazz)) {
+          rewrittenContexts.add(context);
+          continue;
+        }
+
+        // Rewrite the synthetic context to its synthesizing contexts.
+        Set<DexReference> synthesizingContextReferences =
+            appView.getSyntheticItems().getSynthesizingContexts(clazz, synthesizingContextOracle);
+        assert synthesizingContextReferences != null;
+        assert !synthesizingContextReferences.isEmpty();
+        for (DexReference synthesizingContextReference : synthesizingContextReferences) {
+          if (synthesizingContextReference.isDexMethod()) {
+            DexProgramClass holder =
+                appView
+                    .definitionFor(synthesizingContextReference.getContextType())
+                    .asProgramClass();
+            ProgramMethod synthesizingContext =
+                holder.lookupProgramMethod(synthesizingContextReference.asDexMethod());
+            assert synthesizingContext != null;
+            rewrittenContexts.add(synthesizingContext);
+          } else {
+            assert synthesizingContextReference.isDexType()
+                    && synthesizingContextReference
+                        .toSourceString()
+                        .contains("-$$InternalSyntheticTwrCloseResource$")
+                : "Unexpected synthesizing context: "
+                    + synthesizingContextReference.toSourceString();
+          }
+        }
+      }
+      return rewrittenContexts;
+    }
+
+    private Map<DexType, Set<ProgramDerivedContext>> getMissingClassesToBeReported(
+        AppView<?> appView, SynthesizingContextOracle synthesizingContextOracle) {
+      // Rewrite synthetic program contexts into their synthesizing contexts, to avoid reporting
+      // any synthetic contexts.
+      rewriteMissingClassContexts(appView, synthesizingContextOracle);
       Predicate<DexType> allowedMissingClassesPredicate =
           getIsAllowedMissingClassesPredicate(appView);
-      Map<DexType, Set<DexReference>> missingClassesToBeReported =
+      Map<DexType, Set<ProgramDerivedContext>> missingClassesToBeReported =
           new IdentityHashMap<>(newMissingClasses.size());
       newMissingClasses.forEach(
           (missingClass, contexts) -> {
@@ -151,10 +232,20 @@
               return;
             }
 
+            // TODO(b/175543745): This is a legacy reported missing class; remove once no longer
+            //  supported.
+            if (contexts.isEmpty()) {
+              missingClassesToBeReported.put(missingClass, contexts);
+              return;
+            }
+
             // Remove all contexts that are matched by a -dontwarn rule (a missing class should not
             // be reported if it os only referenced from contexts that are matched by a -dontwarn).
             contexts.removeIf(
-                context -> appView.getDontWarnConfiguration().matches(context.getContextType()));
+                context ->
+                    appView
+                        .getDontWarnConfiguration()
+                        .matches(context.getContext().getContextType()));
 
             // If there are any contexts not matched by a -dontwarn rule, then report.
             if (!contexts.isEmpty()) {
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 73b61b3..8263f40 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -217,9 +217,14 @@
 
   // TODO(b/180091213): Implement this and remove client provided the oracle.
   public Set<DexReference> getSynthesizingContexts(
-      DexProgramClass clazz, Function<DexProgramClass, Set<DexReference>> oracle) {
+      DexProgramClass clazz, SynthesizingContextOracle oracle) {
     assert isSyntheticClass(clazz);
-    return oracle.apply(clazz);
+    return oracle.getSynthesizingContexts(clazz);
+  }
+
+  public interface SynthesizingContextOracle {
+
+    Set<DexReference> getSynthesizingContexts(DexProgramClass clazz);
   }
 
   // The compiler should not inspect the kind of a synthetic, so this provided only as a assertion
diff --git a/src/main/java/com/android/tools/r8/utils/collections/IdentityHashSetFromMap.java b/src/main/java/com/android/tools/r8/utils/collections/IdentityHashSetFromMap.java
new file mode 100644
index 0000000..0322582
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/IdentityHashSetFromMap.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2021, 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.utils.collections;
+
+import com.android.tools.r8.graph.ProgramDerivedContext;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+public class IdentityHashSetFromMap<K, V> implements Set<V> {
+
+  private final Map<K, V> backing = new IdentityHashMap<>();
+  private final Function<V, K> valueToKeyMapping;
+
+  public IdentityHashSetFromMap(Function<V, K> valueToKeyMapping) {
+    this.valueToKeyMapping = valueToKeyMapping;
+  }
+
+  public static Set<ProgramDerivedContext> newProgramDerivedContextSet() {
+    return new IdentityHashSetFromMap<>(context -> context.getContext().getReference());
+  }
+
+  @Override
+  public int size() {
+    return backing.size();
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return backing.isEmpty();
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public boolean contains(Object o) {
+    return backing.containsKey(valueToKeyMapping.apply((V) o));
+  }
+
+  @Override
+  public Iterator<V> iterator() {
+    return backing.values().iterator();
+  }
+
+  @Override
+  public Object[] toArray() {
+    return backing.values().toArray();
+  }
+
+  @Override
+  public <T> T[] toArray(T[] ts) {
+    return backing.values().toArray(ts);
+  }
+
+  @Override
+  public boolean add(V v) {
+    return backing.put(valueToKeyMapping.apply(v), v) == null;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public boolean remove(Object o) {
+    return backing.remove(valueToKeyMapping.apply((V) o)) != null;
+  }
+
+  @Override
+  public boolean containsAll(Collection<?> collection) {
+    return backing.values().containsAll(collection);
+  }
+
+  @Override
+  public boolean addAll(Collection<? extends V> collection) {
+    boolean changed = false;
+    for (V element : collection) {
+      changed |= add(element);
+    }
+    return changed;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public boolean retainAll(Collection<?> collection) {
+    Collection<Object> found = new ArrayList<>(collection.size());
+    for (Object element : collection) {
+      if (contains(element)) {
+        found.add(element);
+      }
+    }
+    if (found.size() < size()) {
+      clear();
+      addAll((Collection<V>) found);
+      return true;
+    }
+    return false;
+  }
+
+  @Override
+  public boolean removeAll(Collection<?> collection) {
+    boolean changed = false;
+    for (Object element : collection) {
+      changed |= remove(element);
+    }
+    return changed;
+  }
+
+  @Override
+  public void clear() {
+    backing.clear();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index c9834f8..a1a5f88 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -179,6 +179,12 @@
     return proguardMap;
   }
 
+  public R8TestCompileResult inspectProguardMap(ThrowableConsumer<String> consumer)
+      throws Throwable {
+    consumer.accept(getProguardMap());
+    return this;
+  }
+
   public Path writeProguardMap() throws IOException {
     Path file = state.getNewTempFolder().resolve("out.zip");
     writeProguardMap(file);
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 1b4ca35..8648de9 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -131,6 +131,10 @@
     CF,
     DEX;
 
+    public boolean isCf() {
+      return this == CF;
+    }
+
     public boolean isDex() {
       return this == DEX;
     }
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 5730bea..78ac691 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -112,10 +112,6 @@
     return self();
   }
 
-  public T addDontWarn(String className) {
-    return addKeepRules("-dontwarn " + className);
-  }
-
   public T addDontWarn(Collection<String> classes) {
     for (String clazz : classes) {
       addKeepRules("-dontwarn " + clazz);
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaParameterTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaParameterTest.java
new file mode 100644
index 0000000..a09c4d0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaParameterTest.java
@@ -0,0 +1,113 @@
+// 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.missingclasses;
+
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilationIf;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class MissingClassReferencedFromUnusedLambdaParameterTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      Reference.method(
+          Reference.classFromClass(Main.class),
+          "lambda$main$0",
+          ImmutableList.of(Reference.classFromClass(MissingClass.class)),
+          null);
+
+  public MissingClassReferencedFromUnusedLambdaParameterTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test
+  public void testNoRules() throws Exception {
+    assertFailsCompilationIf(
+        parameters.isCfRuntime(),
+        () ->
+            compileWithExpectedDiagnostics(
+                Main.class,
+                parameters.isCfRuntime()
+                    ? diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom)
+                    : TestDiagnosticMessages::assertNoMessages,
+                addInterface()));
+  }
+
+  // The lambda is never called, therefore the lambda class' virtual method is dead, and therefore
+  // we never trace into lambda$main$0(). Therefore, we need allowUnusedDontWarnPatterns() here.
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addInterface()
+            .andThen(
+                builder ->
+                    builder
+                        .addDontWarn(Main.class)
+                        .applyIf(
+                            parameters.isDexRuntime(),
+                            R8TestBuilder::allowUnusedDontWarnPatterns)));
+  }
+
+  // The lambda is never called, therefore the lambda class' virtual method is dead, and therefore
+  // we never trace into lambda$main$0(). Therefore, we need allowUnusedDontWarnPatterns() here.
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addInterface()
+            .andThen(
+                builder ->
+                    builder
+                        .addDontWarn(Main.class)
+                        .applyIf(
+                            parameters.isDexRuntime(),
+                            R8TestBuilder::allowUnusedDontWarnPatterns)));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        parameters.isCfRuntime()
+            ? diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom)
+            : TestDiagnosticMessages::assertNoMessages,
+        addInterface()
+            .andThen(
+                builder ->
+                    builder
+                        .addIgnoreWarnings()
+                        .applyIf(
+                            parameters.isCfRuntime(),
+                            R8TestBuilder::allowDiagnosticWarningMessages)));
+  }
+
+  ThrowableConsumer<R8FullTestBuilder> addInterface() {
+    return builder -> builder.addProgramClasses(I.class);
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      I ignore = mc -> {};
+    }
+
+    /* private static synthetic void lambda$main$0(MissingClass mc) {} */
+  }
+
+  interface I {
+
+    void m(MissingClass mc);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaReturnTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaReturnTest.java
new file mode 100644
index 0000000..c196013
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUnusedLambdaReturnTest.java
@@ -0,0 +1,113 @@
+// 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.missingclasses;
+
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilationIf;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import java.util.Collections;
+import org.junit.Test;
+
+public class MissingClassReferencedFromUnusedLambdaReturnTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      Reference.method(
+          Reference.classFromClass(Main.class),
+          "lambda$main$0",
+          Collections.emptyList(),
+          Reference.classFromClass(MissingClass.class));
+
+  public MissingClassReferencedFromUnusedLambdaReturnTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test
+  public void testNoRules() throws Exception {
+    assertFailsCompilationIf(
+        parameters.isCfRuntime(),
+        () ->
+            compileWithExpectedDiagnostics(
+                Main.class,
+                parameters.isCfRuntime()
+                    ? diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom)
+                    : TestDiagnosticMessages::assertNoMessages,
+                addInterface()));
+  }
+
+  // The lambda is never called, therefore the lambda class' virtual method is dead, and therefore
+  // we never trace into lambda$main$0(). Therefore, we need allowUnusedDontWarnPatterns() here.
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addInterface()
+            .andThen(
+                builder ->
+                    builder
+                        .addDontWarn(Main.class)
+                        .applyIf(
+                            parameters.isDexRuntime(),
+                            R8TestBuilder::allowUnusedDontWarnPatterns)));
+  }
+
+  // The lambda is never called, therefore the lambda class' virtual method is dead, and therefore
+  // we never trace into lambda$main$0(). Therefore, we need allowUnusedDontWarnPatterns() here.
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addInterface()
+            .andThen(
+                builder ->
+                    builder
+                        .addDontWarn(MissingClass.class)
+                        .applyIf(
+                            parameters.isDexRuntime(),
+                            R8TestBuilder::allowUnusedDontWarnPatterns)));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        parameters.isCfRuntime()
+            ? diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom)
+            : TestDiagnosticMessages::assertNoMessages,
+        addInterface()
+            .andThen(
+                builder ->
+                    builder
+                        .addIgnoreWarnings()
+                        .applyIf(
+                            parameters.isCfRuntime(),
+                            R8TestBuilder::allowDiagnosticWarningMessages)));
+  }
+
+  ThrowableConsumer<R8FullTestBuilder> addInterface() {
+    return builder -> builder.addProgramClasses(I.class);
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      I ignore = () -> null;
+    }
+
+    /* private static synthetic MissingClass lambda$main$0() { return null; } */
+  }
+
+  interface I {
+
+    MissingClass m();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaParameterTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaParameterTest.java
new file mode 100644
index 0000000..2da56a8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaParameterTest.java
@@ -0,0 +1,82 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.missingclasses.MissingClassReferencedFromUsedLambdaReturnTest.I;
+import com.android.tools.r8.missingclasses.MissingClassReferencedFromUsedLambdaReturnTest.Main;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class MissingClassReferencedFromUsedLambdaParameterTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      Reference.method(
+          Reference.classFromClass(I.class),
+          "m",
+          ImmutableList.of(Reference.classFromClass(MissingClass.class)),
+          null);
+
+  public MissingClassReferencedFromUsedLambdaParameterTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom),
+        addInterface());
+  }
+
+  @Test
+  public void testDontWarnMainClassAndInterface() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addInterface().andThen(addDontWarn(Main.class, I.class)));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addInterface().andThen(addDontWarn(MissingClass.class)));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addInterface().andThen(addIgnoreWarnings()));
+  }
+
+  ThrowableConsumer<R8FullTestBuilder> addInterface() {
+    return builder -> builder.addProgramClasses(I.class);
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      I i = mc -> {};
+      i.m(null);
+    }
+
+    /* private static synthetic void lambda$main$0(MissingClass mc) {} */
+  }
+
+  interface I {
+
+    void m(MissingClass mc);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaReturnTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaReturnTest.java
new file mode 100644
index 0000000..1b6781d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromUsedLambdaReturnTest.java
@@ -0,0 +1,80 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import java.util.Collections;
+import org.junit.Test;
+
+public class MissingClassReferencedFromUsedLambdaReturnTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      Reference.method(
+          Reference.classFromClass(I.class),
+          "m",
+          Collections.emptyList(),
+          Reference.classFromClass(MissingClass.class));
+
+  public MissingClassReferencedFromUsedLambdaReturnTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom),
+        addInterface());
+  }
+
+  @Test
+  public void testDontWarnMainClassAndInterface() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addInterface().andThen(addDontWarn(Main.class, I.class)));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addInterface().andThen(addDontWarn(MissingClass.class)));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addInterface().andThen(addIgnoreWarnings()));
+  }
+
+  ThrowableConsumer<R8FullTestBuilder> addInterface() {
+    return builder -> builder.addProgramClasses(I.class);
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      I i = () -> null;
+      i.m();
+    }
+
+    /* private static synthetic MissingClass lambda$main$0() { return null; } */
+  }
+
+  interface I {
+
+    MissingClass m();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
index 3968b75..629fcb6 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
@@ -46,7 +46,7 @@
 
   interface MissingInterface {}
 
-  private final TestParameters parameters;
+  protected final TestParameters parameters;
 
   @Parameters(name = "{0}")
   public static List<Object[]> data() {
@@ -57,8 +57,8 @@
     this.parameters = parameters;
   }
 
-  public ThrowableConsumer<R8FullTestBuilder> addDontWarn(Class<?> clazz) {
-    return builder -> builder.addDontWarn(clazz);
+  public ThrowableConsumer<R8FullTestBuilder> addDontWarn(Class<?>... classes) {
+    return builder -> builder.addDontWarn(classes);
   }
 
   public ThrowableConsumer<R8FullTestBuilder> addIgnoreWarnings() {
@@ -111,25 +111,21 @@
   void inspectDiagnosticsWithIgnoreWarnings(
       TestDiagnosticMessages diagnostics, ClassReference referencedFrom) {
     inspectDiagnosticsWithIgnoreWarnings(
-        diagnostics,
-        getExpectedDiagnosticMessageWithIgnoreWarnings(
-            referencedFrom, ClassReference::getTypeName));
+        diagnostics, getExpectedDiagnosticMessage(referencedFrom, ClassReference::getTypeName));
   }
 
   void inspectDiagnosticsWithIgnoreWarnings(
       TestDiagnosticMessages diagnostics, FieldReference referencedFrom) {
     inspectDiagnosticsWithIgnoreWarnings(
         diagnostics,
-        getExpectedDiagnosticMessageWithIgnoreWarnings(
-            referencedFrom, FieldReferenceUtils::toSourceString));
+        getExpectedDiagnosticMessage(referencedFrom, FieldReferenceUtils::toSourceString));
   }
 
   void inspectDiagnosticsWithIgnoreWarnings(
       TestDiagnosticMessages diagnostics, MethodReference referencedFrom) {
     inspectDiagnosticsWithIgnoreWarnings(
         diagnostics,
-        getExpectedDiagnosticMessageWithIgnoreWarnings(
-            referencedFrom, MethodReferenceUtils::toSourceString));
+        getExpectedDiagnosticMessage(referencedFrom, MethodReferenceUtils::toSourceString));
   }
 
   void inspectDiagnosticsWithIgnoreWarnings(
@@ -144,36 +140,24 @@
     assertEquals(expectedDiagnosticMessage, diagnostic.getDiagnosticMessage());
   }
 
-  private <T> String getExpectedDiagnosticMessageWithIgnoreWarnings(
-      T referencedFrom, Function<T, String> toSourceStringFunction) {
-    return "Missing class "
-        + getMissingClassReference().getTypeName()
-        + " (referenced from: "
-        + toSourceStringFunction.apply(referencedFrom)
-        + ")";
-  }
-
   void inspectDiagnosticsWithNoRules(
       TestDiagnosticMessages diagnostics, ClassReference referencedFrom) {
     inspectDiagnosticsWithNoRules(
-        diagnostics,
-        getExpectedDiagnosticMessageWithNoRules(referencedFrom, ClassReference::getTypeName));
+        diagnostics, getExpectedDiagnosticMessage(referencedFrom, ClassReference::getTypeName));
   }
 
   void inspectDiagnosticsWithNoRules(
       TestDiagnosticMessages diagnostics, FieldReference referencedFrom) {
     inspectDiagnosticsWithNoRules(
         diagnostics,
-        getExpectedDiagnosticMessageWithNoRules(
-            referencedFrom, FieldReferenceUtils::toSourceString));
+        getExpectedDiagnosticMessage(referencedFrom, FieldReferenceUtils::toSourceString));
   }
 
   void inspectDiagnosticsWithNoRules(
       TestDiagnosticMessages diagnostics, MethodReference referencedFrom) {
     inspectDiagnosticsWithNoRules(
         diagnostics,
-        getExpectedDiagnosticMessageWithNoRules(
-            referencedFrom, MethodReferenceUtils::toSourceString));
+        getExpectedDiagnosticMessage(referencedFrom, MethodReferenceUtils::toSourceString));
   }
 
   void inspectDiagnosticsWithNoRules(
@@ -188,13 +172,12 @@
     assertEquals(expectedDiagnosticMessage, diagnostic.getDiagnosticMessage());
   }
 
-  private <T> String getExpectedDiagnosticMessageWithNoRules(
+  private <T> String getExpectedDiagnosticMessage(
       T referencedFrom, Function<T, String> toSourceStringFunction) {
-    return "Compilation can't be completed because the following class is missing: "
+    return "Missing class "
         + getMissingClassReference().getTypeName()
         + " (referenced from: "
         + toSourceStringFunction.apply(referencedFrom)
-        + ")"
-        + ".";
+        + ")";
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index 63c9f40..2fac539 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -3,23 +3,24 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static com.android.tools.r8.ToolHelper.EXAMPLES_BUILD_DIR;
 import static com.android.tools.r8.ToolHelper.EXAMPLES_DIR;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.Diagnostic;
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersBuilder;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
 import java.io.BufferedReader;
 import java.io.IOException;
-import java.io.PrintStream;
 import java.io.StringReader;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
@@ -30,7 +31,6 @@
 import java.util.stream.Stream;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -52,13 +52,16 @@
     parameters.assertNoneRuntime();
   }
 
-  private static final String VALID_PROGUARD_DIR = "src/test/proguard/valid/";
-
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
+  private Path getProgramFiles(String test) {
+    return Paths.get(EXAMPLES_BUILD_DIR, test + ".jar");
+  }
+
+  private byte[] getProgramDexFileData(String test) throws IOException {
+    return Files.readAllBytes(Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex"));
+  }
 
   private void finishBuild(R8Command.Builder builder, Path out, String test) throws IOException {
     Path input;
@@ -75,79 +78,77 @@
   @Test
   public void testIgnoreWarnings() throws Exception {
     // Generate R8 processed version without library option.
-    Path out = temp.getRoot().toPath();
     String test = "shaking2";
-    Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
-    Path ignoreWarnings = Paths.get(VALID_PROGUARD_DIR, "ignorewarnings.flags");
-    R8Command.Builder builder = R8Command.builder()
-        .addProguardConfigurationFiles(keepRules, ignoreWarnings);
-    finishBuild(builder, out, test);
-    R8.run(builder.build());
+    testForR8(backend)
+        .applyIf(
+            backend.isCf(),
+            builder -> builder.addProgramFiles(getProgramFiles(test)),
+            builder -> builder.addProgramDexFileData(getProgramDexFileData(test)))
+        .addKeepRuleFiles(Paths.get(EXAMPLES_DIR, test, "keep-rules.txt"))
+        .addIgnoreWarnings()
+        .compile();
   }
 
   @Test(expected = CompilationFailedException.class)
   public void testMissingLibrary() throws Exception {
     // Generate R8 processed version without library option.
-    Path out = temp.getRoot().toPath();
     String test = "shaking2";
-    Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
-    DiagnosticsHandler handler =
-        new DiagnosticsHandler() {
-          @Override
-          public void error(Diagnostic error) {
-            assertEquals(
-                "Compilation can't be completed because the following class is missing: "
-                    + "java.lang.Object.",
-                error.getDiagnosticMessage());
-          }
-        };
-    R8Command.Builder builder = R8Command.builder(handler)
-        .addProguardConfigurationFiles(keepRules);
-    finishBuild(builder, out, test);
-    R8.run(builder.build());
+    testForR8(backend)
+        .applyIf(
+            backend.isCf(),
+            builder -> builder.addProgramFiles(getProgramFiles(test)),
+            builder -> builder.addProgramDexFileData(getProgramDexFileData(test)))
+        .addLibraryFiles()
+        .addKeepRuleFiles(Paths.get(EXAMPLES_DIR, test, "keep-rules.txt"))
+        .allowDiagnosticErrorMessages()
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics
+                  .assertOnlyErrors()
+                  .assertErrorsMatch(diagnosticType(MissingDefinitionsDiagnostic.class));
+
+              MissingDefinitionsDiagnostic diagnostic =
+                  (MissingDefinitionsDiagnostic) diagnostics.getErrors().get(0);
+              assertThat(
+                  diagnostic.getDiagnosticMessage(),
+                  allOf(
+                      containsString("Missing class java.io.PrintStream"),
+                      containsString("Missing class java.lang.Object"),
+                      containsString("Missing class java.lang.String"),
+                      containsString("Missing class java.lang.StringBuilder"),
+                      containsString("Missing class java.lang.System")));
+            });
   }
 
   @Test
-  public void testPrintMapping() throws Exception {
+  public void testPrintMapping() throws Throwable {
     // Generate R8 processed version without library option.
     String test = "shaking1";
-    Path out = temp.getRoot().toPath();
-    Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
-
-    // Create a flags file in temp dir requesting dump of the mapping.
-    // The mapping file will be created alongside the flags file in temp dir.
-    Path printMapping = out.resolve("printmapping.flags");
-    try (PrintStream mapping = new PrintStream(printMapping.toFile())) {
-      mapping.println("-printmapping mapping.txt");
-    }
-
-    R8Command.Builder builder = R8Command.builder()
-        .addProguardConfigurationFiles(keepRules, printMapping);
-    // Turn off inlining, as we want the mapping that is printed to be stable.
-    finishBuild(builder, out, test);
-    if (backend == Backend.DEX) {
-      builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
-    } else {
-      assert backend == Backend.CF;
-      builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
-    }
-    ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
-
-    Path outputmapping = out.resolve("mapping.txt");
-    // Remove comments.
-    String actualMapping =
-        Stream.of(new String(Files.readAllBytes(outputmapping), StandardCharsets.UTF_8).split("\n"))
-            .filter(line -> !line.startsWith("#"))
-            .collect(Collectors.joining("\n"));
-    String refMapping =
-        new String(
-            Files.readAllBytes(
-                Paths.get(
-                    EXAMPLES_DIR,
-                    "shaking1",
-                    "print-mapping-" + backend.name().toLowerCase() + ".ref")),
-            StandardCharsets.UTF_8);
-    assertEquals(sorted(refMapping), sorted(actualMapping));
+    testForR8(backend)
+        .applyIf(
+            backend.isCf(),
+            builder -> builder.addProgramFiles(getProgramFiles(test)),
+            builder -> builder.addProgramDexFileData(getProgramDexFileData(test)))
+        .addKeepRuleFiles(Paths.get(EXAMPLES_DIR, test, "keep-rules.txt"))
+        .addOptionsModification(options -> options.enableInlining = false)
+        .compile()
+        .inspectProguardMap(
+            proguardMap -> {
+              // Remove comments.
+              String actualMapping =
+                  Stream.of(proguardMap.split("\n"))
+                      .filter(line -> !line.startsWith("#"))
+                      .collect(Collectors.joining("\n"));
+              String refMapping =
+                  new String(
+                      Files.readAllBytes(
+                          Paths.get(
+                              EXAMPLES_DIR,
+                              "shaking1",
+                              "print-mapping-" + backend.name().toLowerCase() + ".ref")),
+                      StandardCharsets.UTF_8);
+              assertEquals(sorted(refMapping), sorted(actualMapping));
+            });
   }
 
   private static String sorted(String str) {