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)));
+ }
+ }
+}