Desugared library interface dispatch

Bug: 134732760
Change-Id: Ib7b3fd84dbe54f1208afe8aabc3762f98798bae5
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 932a874..5ad01dc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -50,6 +50,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.NestUtils;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
+import com.android.tools.r8.ir.synthetic.CfEmulateInterfaceSyntheticSourceCodeProvider;
 import com.android.tools.r8.ir.synthetic.FieldAccessorSourceCode;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
@@ -266,6 +267,11 @@
     return accessFlags.isConstructor() && accessFlags.isStatic();
   }
 
+  public boolean isDefaultMethod() {
+    // Assumes holder is an interface
+    return !isAbstract() && !isPrivateMethod() && !isInstanceInitializer();
+  }
+
   /**
    * Returns true if this method can be invoked via invoke-virtual, invoke-super or
    * invoke-interface.
@@ -836,6 +842,33 @@
         newMethod, accessFlags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), code);
   }
 
+  public DexEncodedMethod toEmulateInterfaceLibraryMethod(
+      DexMethod newMethod, DexMethod companionMethod, DexMethod libraryMethod, AppView<?> appView) {
+    // TODO(134732760): Deal with overrides for correct dispatch to implementations of Interfaces
+    assert isDefaultMethod();
+    DexEncodedMethod.Builder builder = DexEncodedMethod.builder(this);
+    builder.setMethod(newMethod);
+    builder.accessFlags.setSynthetic();
+    builder.accessFlags.setStatic();
+    builder.accessFlags.unsetPrivate();
+    builder.accessFlags.setPublic();
+    DexEncodedMethod newEncodedMethod = builder.build();
+    newEncodedMethod.setCode(
+        new SynthesizedCode(
+            new CfEmulateInterfaceSyntheticSourceCodeProvider(
+                this.method.holder,
+                companionMethod,
+                newEncodedMethod,
+                libraryMethod,
+                this.method,
+                appView),
+            registry -> {
+              registry.registerInvokeInterface(libraryMethod);
+              registry.registerInvokeStatic(companionMethod);
+            }));
+    return newEncodedMethod;
+  }
+
   public DexEncodedMethod toStaticForwardingBridge(DexClass holder, DexMethod newMethod) {
     assert accessFlags.isPrivate()
         : "Expected to create bridge for private method as part of nest-based access desugaring";
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 6a2d270..6f2712e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.DISPATCH_CLASS_NAME_SUFFIX;
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.EMULATE_LIBRARY_CLASS_NAME_SUFFIX;
 import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX;
 import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
 
@@ -244,6 +245,7 @@
   public boolean isD8R8SynthesizedClassType() {
     String name = toSourceString();
     return name.contains(COMPANION_CLASS_NAME_SUFFIX)
+        || name.contains(EMULATE_LIBRARY_CLASS_NAME_SUFFIX)
         || name.contains(DISPATCH_CLASS_NAME_SUFFIX)
         || name.contains(LAMBDA_CLASS_NAME_PREFIX)
         || name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX)
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index ed1cb27..63a01b9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -8,9 +8,12 @@
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexLibraryClass;
@@ -18,6 +21,7 @@
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
@@ -31,9 +35,16 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.Collection;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -72,6 +83,7 @@
 public final class InterfaceMethodRewriter {
 
   // Public for testing.
+  public static final String EMULATE_LIBRARY_CLASS_NAME_SUFFIX = "$-EL";
   public static final String DISPATCH_CLASS_NAME_SUFFIX = "$-DC";
   public static final String COMPANION_CLASS_NAME_SUFFIX = "$-CC";
   public static final String DEFAULT_METHOD_PREFIX = "$default$";
@@ -81,6 +93,7 @@
   private final IRConverter converter;
   private final InternalOptions options;
   final DexItemFactory factory;
+  private final Map<DexType, DexType> emulatedInterfaces = new IdentityHashMap<>();
 
   // All forwarding methods generated during desugaring. We don't synchronize access
   // to this collection since it is only filled in ClassProcessor running synchronously.
@@ -113,6 +126,13 @@
     this.converter = converter;
     this.options = appView.options();
     this.factory = appView.dexItemFactory();
+    for (String interfaceName : options.emulateLibraryInterface.keySet()) {
+      emulatedInterfaces.put(
+          factory.createType(DescriptorUtils.javaTypeToDescriptor(interfaceName)),
+          factory.createType(
+              DescriptorUtils.javaTypeToDescriptor(
+                  options.emulateLibraryInterface.get(interfaceName))));
+    }
   }
 
   // Rewrites the references to static and default interface methods.
@@ -268,6 +288,101 @@
     }
   }
 
+  private void generateEmulateInterfaceLibrary(Builder<?> builder) {
+    for (DexType interfaceType : emulatedInterfaces.keySet()) {
+      DexClass theInterface = appView.definitionFor(interfaceType);
+      if (theInterface == null) {
+        StringDiagnostic warning =
+            new StringDiagnostic(
+                "Cannot emulate interface "
+                    + interfaceType.getName()
+                    + " because the interface is missing.");
+        options.reporter.warning(warning);
+      } else if (theInterface.isProgramClass()) {
+        DexProgramClass synthesizedClass =
+            synthetizeEmulateInterfaceLibraryClass(theInterface.asProgramClass());
+        if (synthesizedClass != null) {
+          builder.addSynthesizedClass(synthesizedClass, isInMainDexList(interfaceType));
+          appView.appInfo().addSynthesizedClass(synthesizedClass);
+        }
+      }
+    }
+  }
+
+  private static DexMethod emulateInterfaceLibraryMethod(DexMethod method, DexItemFactory factory) {
+    return factory.createMethod(
+        getEmulateLibraryInterfaceClassType(method.holder, factory),
+        factory.prependTypeToProto(method.holder, method.proto),
+        factory.createString(method.name.toString()));
+  }
+
+  private DexProgramClass synthetizeEmulateInterfaceLibraryClass(DexProgramClass theInterface) {
+    List<DexEncodedMethod> emulationMethods = new ArrayList<>();
+    for (DexEncodedMethod method : theInterface.methods()) {
+      if (method.isDefaultMethod()) {
+        DexMethod libraryMethod =
+            factory.createMethod(
+                emulatedInterfaces.get(theInterface.type), method.method.proto, method.method.name);
+        DexMethod companionMethod =
+            method.isStatic()
+                ? staticAsMethodOfCompanionClass(method.method)
+                : instanceAsMethodOfCompanionClass(method.method, DEFAULT_METHOD_PREFIX, factory);
+        emulationMethods.add(
+            method.toEmulateInterfaceLibraryMethod(
+                emulateInterfaceLibraryMethod(method.method, factory),
+                companionMethod,
+                libraryMethod,
+                appView));
+      }
+    }
+    if (emulationMethods.isEmpty()) {
+      return null;
+    }
+    DexType emulateLibraryClassType =
+        getEmulateLibraryInterfaceClassType(theInterface.type, factory);
+    ClassAccessFlags emulateLibraryClassFlags = theInterface.accessFlags.copy();
+    emulateLibraryClassFlags.unsetAbstract();
+    emulateLibraryClassFlags.unsetInterface();
+    emulateLibraryClassFlags.unsetAnnotation();
+    emulateLibraryClassFlags.setFinal();
+    emulateLibraryClassFlags.setSynthetic();
+    emulateLibraryClassFlags.setPublic();
+    synthesizedMethods.addAll(emulationMethods);
+    return new DexProgramClass(
+        emulateLibraryClassType,
+        null,
+        new SynthesizedOrigin("interface desugaring (libs)", getClass()),
+        emulateLibraryClassFlags,
+        factory.objectType,
+        DexTypeList.empty(),
+        theInterface.sourceFile,
+        null,
+        Collections.emptyList(),
+        null,
+        Collections.emptyList(),
+        DexAnnotationSet.empty(),
+        DexEncodedField.EMPTY_ARRAY,
+        DexEncodedField.EMPTY_ARRAY,
+        // All synthetized methods are static in this case.
+        emulationMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
+        DexEncodedMethod.EMPTY_ARRAY,
+        factory.getSkipNameValidationForTesting(),
+        Collections.singletonList(theInterface));
+  }
+
+  private static String getEmulateLibraryInterfaceClassDescriptor(String descriptor) {
+    return descriptor.substring(0, descriptor.length() - 1)
+        + EMULATE_LIBRARY_CLASS_NAME_SUFFIX
+        + ";";
+  }
+
+  static DexType getEmulateLibraryInterfaceClassType(DexType type, DexItemFactory factory) {
+    assert type.isClassType();
+    String descriptor = type.descriptor.toString();
+    String elTypeDescriptor = getEmulateLibraryInterfaceClassDescriptor(descriptor);
+    return factory.createType(elTypeDescriptor);
+  }
+
   private void reportStaticInterfaceMethodHandle(DexMethod referencedFrom, DexMethodHandle handle) {
     if (handle.type.isInvokeStatic()) {
       DexClass holderClass = appView.definitionFor(handle.asMethod().holder);
@@ -400,6 +515,9 @@
       Flavor flavour,
       ExecutorService executorService)
       throws ExecutionException {
+    // Emulated library interfaces should generate the Emulated Library EL dispatch class
+    generateEmulateInterfaceLibrary(builder);
+
     // Process all classes first. Add missing forwarding methods to
     // replace desugared default interface methods.
     synthesizedMethods.addAll(processClasses(builder, flavour));
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/CfEmulateInterfaceSyntheticSourceCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/CfEmulateInterfaceSyntheticSourceCodeProvider.java
new file mode 100644
index 0000000..f4da935
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/CfEmulateInterfaceSyntheticSourceCodeProvider.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2019, 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.ir.synthetic;
+
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfInstanceOf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public class CfEmulateInterfaceSyntheticSourceCodeProvider extends CfSyntheticSourceCodeProvider {
+
+  private final DexType interfaceType;
+  private final DexMethod companionMethod;
+  private final DexMethod libraryMethod;
+
+  public CfEmulateInterfaceSyntheticSourceCodeProvider(
+      DexType interfaceType,
+      DexMethod companionMethod,
+      DexEncodedMethod method,
+      DexMethod libraryMethod,
+      DexMethod originalMethod,
+      AppView<?> appView) {
+    super(method, originalMethod, appView);
+    this.interfaceType = interfaceType;
+    this.companionMethod = companionMethod;
+    this.libraryMethod = libraryMethod;
+  }
+
+  @Override
+  protected CfCode generateCfCode(Position callerPosition) {
+    List<CfInstruction> instructions = new ArrayList<>();
+    CfLabel companionLabel = new CfLabel();
+    instructions.add(new CfLoad(ValueType.fromDexType(interfaceType), 0));
+    instructions.add(new CfInstanceOf(libraryMethod.holder));
+    instructions.add(new CfIf(If.Type.NE, ValueType.INT, companionLabel));
+
+    // Branch with library call.
+    instructions.add(new CfLoad(ValueType.fromDexType(interfaceType), 0));
+    instructions.add(new CfCheckCast(libraryMethod.holder));
+    loadExtraParameters(instructions);
+    instructions.add(new CfInvoke(Opcodes.INVOKEINTERFACE, libraryMethod, true));
+    addReturn(instructions);
+
+    // Branch with companion call.
+    instructions.add(companionLabel);
+    instructions.add(new CfLoad(ValueType.fromDexType(interfaceType), 0));
+    loadExtraParameters(instructions);
+    instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, companionMethod, false));
+    addReturn(instructions);
+    return standardCfCodeFromInstructions(instructions);
+  }
+
+  private void loadExtraParameters(List<CfInstruction> instructions) {
+    int index = 1;
+    for (DexType type : libraryMethod.proto.parameters.values) {
+      instructions.add(new CfLoad(ValueType.fromDexType(type), index++));
+    }
+  }
+
+  private void addReturn(List<CfInstruction> instructions) {
+    if (libraryMethod.proto.returnType == appView.dexItemFactory().voidType) {
+      instructions.add(new CfReturnVoid());
+    } else {
+      instructions.add(new CfReturn(ValueType.fromDexType(libraryMethod.proto.returnType)));
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/CfSyntheticSourceCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/CfSyntheticSourceCodeProvider.java
new file mode 100644
index 0000000..14e4b3b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/CfSyntheticSourceCodeProvider.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2019, 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.ir.synthetic;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.SourceCode;
+import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode.SourceCodeProvider;
+import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+
+public abstract class CfSyntheticSourceCodeProvider implements SourceCodeProvider {
+
+  private final DexEncodedMethod method;
+  private final DexMethod originalMethod;
+  protected final AppView<?> appView;
+
+  public CfSyntheticSourceCodeProvider(
+      DexEncodedMethod method, DexMethod originalMethod, AppView<?> appView) {
+    this.method = method;
+    this.originalMethod = originalMethod;
+    this.appView = appView;
+  }
+
+  @Override
+  public SourceCode get(Position callerPosition) {
+    return new CfSourceCode(
+        generateCfCode(callerPosition),
+        method,
+        originalMethod,
+        callerPosition,
+        Origin.unknown(),
+        appView);
+  }
+
+  protected abstract CfCode generateCfCode(Position callerPosition);
+
+  protected CfCode standardCfCodeFromInstructions(List<CfInstruction> instructions) {
+    return new CfCode(
+        defaultMaxStack(),
+        defaultMaxLocals(),
+        instructions,
+        defaultTryCatchs(),
+        ImmutableList.of());
+  }
+
+  protected int defaultMaxStack() {
+    return 16;
+  }
+
+  protected int defaultMaxLocals() {
+    return 16;
+  }
+
+  protected List<CfTryCatch> defaultTryCatchs() {
+    return ImmutableList.of();
+  }
+}
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 170e9aa..d54fc1f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -381,6 +381,7 @@
   public boolean enableInheritanceClassInDexDistributor = true;
 
   public Map<String, String> rewritePrefix = ImmutableMap.of();
+  public Map<String, String> emulateLibraryInterface = ImmutableMap.of();
 
   public LineNumberOptimization lineNumberOptimization = LineNumberOptimization.ON;
 
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/CoreLibDesugarTestBase.java b/src/test/java/com/android/tools/r8/desugar/corelib/CoreLibDesugarTestBase.java
index 39db8b6..aca4d4b 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/CoreLibDesugarTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/CoreLibDesugarTestBase.java
@@ -42,18 +42,23 @@
         .put("java.util.concurrent.DesugarUnsafe", "j$.util.concurrent.DesugarUnsafe")
         .put("java.util.concurrent.ThreadLocalRandom", "j$.util.concurrent.ThreadLocalRandom")
         .put("java.util.concurrent.atomic.DesugarAtomic", "j$.util.concurrent.atomic.DesugarAtomic")
-        // TODO(134732760): These should also be handled.
+        .build();
+  }
+
+  private Map<String, String> buildEmulateLibraryInterface() {
+    return ImmutableMap.<String, String>builder()
         // --emulate_core_library_interface.
-        // .put("java.util.Collection", "j$.util.Collection")
-        // .put("java.util.Map", "j$.util.Map")
-        // .put("java.util.Map$Entry", "j$.util.Map$Entry")
-        // .put("java.util.Iterator", "j$.util.Iterator")
-        // .put("java.util.Comparator", "j$.util.Comparator")
+        .put("java.util.Collection", "j$.util.Collection")
+        .put("java.util.Map", "j$.util.Map")
+        .put("java.util.Map$Entry", "j$.util.Map$Entry")
+        .put("java.util.Iterator", "j$.util.Iterator")
+        .put("java.util.Comparator", "j$.util.Comparator")
         .build();
   }
 
   protected void configureCoreLibDesugar(InternalOptions options) {
     options.rewritePrefix = buildPrefixRewriting();
+    options.emulateLibraryInterface = buildEmulateLibraryInterface();
   }
 
   protected Path buildDesugaredLibrary(AndroidApiLevel apiLevel) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/EmulateLibraryInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/EmulateLibraryInterfaceTest.java
new file mode 100644
index 0000000..6367678
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/EmulateLibraryInterfaceTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2019, 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.corelib;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EmulateLibraryInterfaceTest extends CoreLibDesugarTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().build();
+  }
+
+  public EmulateLibraryInterfaceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testDispatchClasses() throws Exception {
+    CodeInspector inspector = new CodeInspector(buildDesugaredLibrary(parameters.getRuntime()));
+    List<FoundClassSubject> dispatchClasses =
+        inspector.allClasses().stream()
+            .filter(
+                clazz ->
+                    clazz
+                        .getOriginalName()
+                        .contains(InterfaceMethodRewriter.EMULATE_LIBRARY_CLASS_NAME_SUFFIX))
+            .collect(Collectors.toList());
+    int numDispatchClasses =
+        this.parameters.getRuntime().asDex().getMinApiLevel().getLevel()
+                < AndroidApiLevel.N.getLevel()
+            ? 5
+            : 0;
+    assertEquals(numDispatchClasses, dispatchClasses.size());
+    for (FoundClassSubject clazz : dispatchClasses) {
+      assertTrue(
+          clazz.allMethods().stream()
+              .allMatch(
+                  method ->
+                      method.isStatic()
+                          && method
+                              .streamInstructions()
+                              .anyMatch(InstructionSubject::isInstanceOf)));
+    }
+  }
+}