Introduce Machine specification wrapper flags

Bug:184026720
Change-Id: I452753be77f2501e35e6d39f7dcf6473d55de2c8
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
index 0795a4e..150e6c4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
@@ -201,8 +201,7 @@
                 clazz.isInterface(),
                 eventConsumer)
             .generateCfCode();
-    DexEncodedMethod newMethod =
-        wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
+    DexEncodedMethod newMethod = wrapperSynthesizor.newSynthesizedMethod(methodToInstall, cfCode);
     newMethod.setCode(cfCode, appView);
     if (originalMethod.isLibraryMethodOverride().isTrue()) {
       newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
index 55d61b9..4a8cabe 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -29,7 +29,6 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterConstructorCfCodeProvider;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterThrowRuntimeExceptionCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterVivifiedWrapperCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperConversionCfCodeProvider;
@@ -41,14 +40,11 @@
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 
@@ -387,22 +383,8 @@
                         wrapperField, reverseWrapperField, context)));
   }
 
-  private boolean isInvalidWrapper(DexClass clazz) {
-    return Iterables.any(allImplementedMethods(clazz), DexEncodedMethod::isFinal);
-  }
-
   private CfCode computeProgramConversionMethodCode(
       DexField wrapperField, DexField reverseWrapperField, DexClass context) {
-    if (isInvalidWrapper(context)) {
-      return new APIConverterThrowRuntimeExceptionCfCodeProvider(
-              appView,
-              factory.createString(
-                  "Unsupported conversion for "
-                      + context.type
-                      + ". See compilation time warnings for more details."),
-              wrapperField.holder)
-          .generateCfCode();
-    }
     assert context.isProgramClass();
     return new APIConverterWrapperConversionCfCodeProvider(
             appView, reverseWrapperField, wrapperField)
@@ -459,7 +441,7 @@
 
   private Collection<DexEncodedMethod> synthesizeVirtualMethodsForVivifiedTypeWrapper(
       DexClass dexClass, DexEncodedField wrapperField) {
-    List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
+    Iterable<DexMethod> allImplementedMethods = allImplementedMethods(dexClass);
     List<DexEncodedMethod> generatedMethods = new ArrayList<>();
     // Each method should use only types in their signature, but each method the wrapper forwards
     // to should used only vivified types.
@@ -470,30 +452,23 @@
     //   v2 <- convertTypeToVivifiedType(v0);
     //   v3 <- wrappedValue.foo(v2,v1);
     //   return v3;
-    Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
-    for (DexEncodedMethod dexEncodedMethod : dexMethods) {
-      DexClass holderClass = appView.definitionFor(dexEncodedMethod.getHolderType());
+    for (DexMethod method : allImplementedMethods) {
+      DexClass holderClass = appView.definitionFor(method.getHolderType());
       boolean isInterface;
       if (holderClass == null) {
         assert appView
             .options()
             .desugaredLibrarySpecification
             .getEmulateLibraryInterface()
-            .containsValue(dexEncodedMethod.getHolderType());
+            .containsValue(method.getHolderType());
         isInterface = true;
       } else {
         isInterface = holderClass.isInterface();
       }
       DexMethod methodToInstall =
-          factory.createMethod(
-              wrapperField.getHolderType(),
-              dexEncodedMethod.getReference().proto,
-              dexEncodedMethod.getReference().name);
+          factory.createMethod(wrapperField.getHolderType(), method.proto, method.name);
       CfCode cfCode;
-      if (dexEncodedMethod.isFinal()) {
-        finalMethods.add(dexEncodedMethod.getReference());
-        continue;
-      } else if (dexClass.isProgramClass()) {
+      if (dexClass.isProgramClass()) {
         cfCode =
             new APIConverterVivifiedWrapperCfCodeProvider(
                     appView, methodToInstall, wrapperField.getReference(), this, isInterface)
@@ -501,16 +476,15 @@
       } else {
         cfCode = null;
       }
-      DexEncodedMethod newDexEncodedMethod =
-          newSynthesizedMethod(methodToInstall, dexEncodedMethod, cfCode);
+      DexEncodedMethod newDexEncodedMethod = newSynthesizedMethod(methodToInstall, cfCode);
       generatedMethods.add(newDexEncodedMethod);
     }
-    return finalizeWrapperMethods(generatedMethods, finalMethods);
+    return generatedMethods;
   }
 
   private Collection<DexEncodedMethod> synthesizeVirtualMethodsForTypeWrapper(
       DexClass dexClass, DexEncodedField wrapperField) {
-    List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
+    Iterable<DexMethod> dexMethods = allImplementedMethods(dexClass);
     List<DexEncodedMethod> generatedMethods = new ArrayList<>();
     // Each method should use only vivified types in their signature, but each method the wrapper
     // forwards
@@ -522,87 +496,64 @@
     //   v2 <- convertVivifiedTypeToType(v0);
     //   v3 <- wrappedValue.foo(v2,v1);
     //   return v3;
-    Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
-    for (DexEncodedMethod dexEncodedMethod : dexMethods) {
-      DexClass holderClass = appView.definitionFor(dexEncodedMethod.getHolderType());
+    for (DexMethod method : dexMethods) {
+      DexClass holderClass = appView.definitionFor(method.getHolderType());
       assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
       boolean isInterface = holderClass == null || holderClass.isInterface();
       DexMethod methodToInstall =
           DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
-              dexEncodedMethod.getReference(), wrapperField.getHolderType(), appView);
+              method, wrapperField.getHolderType(), appView);
       CfCode cfCode;
-      if (dexEncodedMethod.isFinal()) {
-        finalMethods.add(dexEncodedMethod.getReference());
-        continue;
-      } else if (dexClass.isProgramClass()) {
+      if (dexClass.isProgramClass()) {
         cfCode =
             new APIConverterWrapperCfCodeProvider(
-                    appView,
-                    dexEncodedMethod.getReference(),
-                    wrapperField.getReference(),
-                    this,
-                    isInterface)
+                    appView, method, wrapperField.getReference(), this, isInterface)
                 .generateCfCode();
       } else {
         cfCode = null;
       }
-      DexEncodedMethod newDexEncodedMethod =
-          newSynthesizedMethod(methodToInstall, dexEncodedMethod, cfCode);
+      DexEncodedMethod newDexEncodedMethod = newSynthesizedMethod(methodToInstall, cfCode);
       generatedMethods.add(newDexEncodedMethod);
     }
-    return finalizeWrapperMethods(generatedMethods, finalMethods);
+    return generatedMethods;
   }
 
-  private Collection<DexEncodedMethod> finalizeWrapperMethods(
-      List<DexEncodedMethod> generatedMethods, Set<DexMethod> finalMethods) {
-    if (finalMethods.isEmpty()) {
-      return generatedMethods;
-    }
-    // Wrapper is invalid, no need to add the virtual methods.
-    reportFinalMethodsInWrapper(finalMethods);
-    return Collections.emptyList();
-  }
-
-  private void reportFinalMethodsInWrapper(Set<DexMethod> methods) {
-    String[] methodArray =
-        methods.stream().map(method -> method.holder + "#" + method.name).toArray(String[]::new);
-    appView
-        .options()
-        .reporter
-        .warning(
-            new StringDiagnostic(
-                "Desugared library API conversion: cannot wrap final methods "
-                    + Arrays.toString(methodArray)
-                    + ". "
-                    + methods.iterator().next().holder
-                    + " is marked as invalid and will throw a runtime exception upon conversion."));
-  }
-
-  DexEncodedMethod newSynthesizedMethod(
-      DexMethod methodToInstall, DexEncodedMethod template, Code code) {
-    MethodAccessFlags newFlags = template.accessFlags.copy();
-    assert newFlags.isPublic();
-    // It can happen that we wrap an abstract method, in which case the wrapping method is no
-    // longer abstract.
-    if (code != null) {
-      newFlags.unsetAbstract();
-    }
-    // TODO(b/146114533): Fix inlining in synthetic methods and remove unsetBridge.
-    newFlags.unsetBridge();
-    newFlags.setSynthetic();
+  DexEncodedMethod newSynthesizedMethod(DexMethod methodToInstall, Code code) {
+    MethodAccessFlags newFlags =
+        MethodAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false);
+    ComputedApiLevel apiLevelForDefinition =
+        appView.enableWholeProgramOptimizations()
+            ? ComputedApiLevel.notSet()
+            : appView
+                .apiLevelCompute()
+                .computeApiLevelForDefinition(methodToInstall, factory, ComputedApiLevel.unknown());
+    // Since the method is a forwarding method, the api level for code is the same as the
+    // definition.
+    ComputedApiLevel apiLevelForCode = apiLevelForDefinition;
     return DexEncodedMethod.syntheticBuilder()
         .setMethod(methodToInstall)
         .setAccessFlags(newFlags)
         .setCode(code)
-        .setApiLevelForDefinition(template.getApiLevelForDefinition())
-        .setApiLevelForCode(
-            code == null ? ComputedApiLevel.notSet() : template.getApiLevelForCode())
+        .setApiLevelForDefinition(apiLevelForDefinition)
+        .setApiLevelForCode(code == null ? ComputedApiLevel.notSet() : apiLevelForCode)
         .build();
   }
 
-  private List<DexEncodedMethod> allImplementedMethods(DexClass clazz) {
-    return allImplementedMethodsCache.computeIfAbsent(
-        clazz.type, type -> internalAllImplementedMethods(clazz));
+  private Iterable<DexMethod> allImplementedMethods(DexClass clazz) {
+    if (appView.options().testing.machineDesugaredLibrarySpecification != null) {
+      return appView
+          .options()
+          .testing
+          .machineDesugaredLibrarySpecification
+          .getRewritingFlags()
+          .getWrappers()
+          .get(clazz.type);
+    }
+    List<DexEncodedMethod> dexEncodedMethods =
+        allImplementedMethodsCache.computeIfAbsent(
+            clazz.type, type -> internalAllImplementedMethods(clazz));
+    return Iterables.transform(dexEncodedMethods, m -> m.getReference());
   }
 
   private List<DexEncodedMethod> internalAllImplementedMethods(DexClass libraryClass) {
@@ -641,6 +592,7 @@
         workList.add(superClass);
       }
     }
+    assert !Iterables.any(implementedMethods, DexEncodedMethod::isFinal);
     return implementedMethods;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
index 4077e4b..eea4697 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -6,8 +6,10 @@
 
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 
 public class MachineRewritingFlags {
@@ -22,13 +24,15 @@
       Map<DexMethod, DexMethod> staticRetarget,
       Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget,
       Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget,
-      Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces) {
+      Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces,
+      Map<DexType, List<DexMethod>> wrappers) {
     this.rewriteType = rewriteType;
     this.rewriteDerivedTypeOnly = rewriteDerivedTypeOnly;
     this.staticRetarget = staticRetarget;
     this.nonEmulatedVirtualRetarget = nonEmulatedVirtualRetarget;
     this.emulatedVirtualRetarget = emulatedVirtualRetarget;
     this.emulatedInterfaces = emulatedInterfaces;
+    this.wrappers = wrappers;
   }
 
   // Rewrites all the references to the keys as well as synthetic types derived from any key.
@@ -53,6 +57,9 @@
   // Emulated interface descriptors.
   private final Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces;
 
+  // Wrappers and the list of methods they implement.
+  private final Map<DexType, List<DexMethod>> wrappers;
+
   public Map<DexType, DexType> getRewriteType() {
     return rewriteType;
   }
@@ -77,6 +84,10 @@
     return emulatedInterfaces;
   }
 
+  public Map<DexType, List<DexMethod>> getWrappers() {
+    return wrappers;
+  }
+
   public static class Builder {
 
     Builder() {}
@@ -91,6 +102,7 @@
         emulatedVirtualRetarget = ImmutableMap.builder();
     private final ImmutableMap.Builder<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces =
         ImmutableMap.builder();
+    private final ImmutableMap.Builder<DexType, List<DexMethod>> wrappers = ImmutableMap.builder();
 
     public void rewriteType(DexType src, DexType target) {
       rewriteType.put(src, target);
@@ -116,6 +128,10 @@
       emulatedVirtualRetarget.put(src, dest);
     }
 
+    public void addWrapper(DexType wrapperConversion, List<DexMethod> methods) {
+      wrappers.put(wrapperConversion, ImmutableList.copyOf(methods));
+    }
+
     public MachineRewritingFlags build() {
       return new MachineRewritingFlags(
           rewriteType,
@@ -123,7 +139,8 @@
           staticRetarget.build(),
           nonEmulatedVirtualRetarget.build(),
           emulatedVirtualRetarget.build(),
-          emulatedInterfaces.build());
+          emulatedInterfaces.build(),
+          wrappers.build());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
index af380d4..63b903c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -47,6 +47,7 @@
         .convertEmulatedInterfaces(rewritingFlags, appInfo, builder);
     new HumanToMachinePrefixConverter(appInfo)
         .convertPrefixFlags(rewritingFlags, builder, synthesizedPrefix);
+    new HumanToMachineWrapperConverter(appInfo).convertWrappers(rewritingFlags, builder);
     return builder.build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java
new file mode 100644
index 0000000..db87890
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2022, 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.desugar.desugaredlibrary.specificationconversion;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.DexClass;
+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.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+public class HumanToMachineWrapperConverter {
+
+  private final AppInfoWithClassHierarchy appInfo;
+
+  public HumanToMachineWrapperConverter(AppInfoWithClassHierarchy appInfo) {
+    this.appInfo = appInfo;
+  }
+
+  public void convertWrappers(
+      HumanRewritingFlags rewritingFlags, MachineRewritingFlags.Builder builder) {
+    for (DexType wrapperConversion : rewritingFlags.getWrapperConversions()) {
+      DexClass wrapperClass = appInfo.definitionFor(wrapperConversion);
+      assert wrapperClass != null;
+      List<DexMethod> methods = allImplementedMethods(wrapperClass);
+      methods.sort(DexMethod::compareTo);
+      builder.addWrapper(wrapperConversion, methods);
+    }
+  }
+
+  private List<DexMethod> allImplementedMethods(DexClass wrapperClass) {
+    LinkedList<DexClass> workList = new LinkedList<>();
+    List<DexMethod> implementedMethods = new ArrayList<>();
+    workList.add(wrapperClass);
+    while (!workList.isEmpty()) {
+      DexClass dexClass = workList.removeFirst();
+      for (DexEncodedMethod virtualMethod : dexClass.virtualMethods()) {
+        if (!virtualMethod.isPrivateMethod()) {
+          assert virtualMethod.isProtectedMethod() || virtualMethod.isPublicMethod();
+          boolean alreadyAdded = false;
+          // This looks quadratic but given the size of the collections met in practice for
+          // desugared libraries (Max ~15) it does not matter.
+          for (DexMethod alreadyImplementedMethod : implementedMethods) {
+            if (alreadyImplementedMethod.match(virtualMethod.getReference())) {
+              alreadyAdded = true;
+              break;
+            }
+          }
+          if (!alreadyAdded) {
+            assert !virtualMethod.isFinal() : "Cannot wrap final method " + virtualMethod;
+            implementedMethods.add(virtualMethod.getReference());
+          }
+        }
+      }
+      for (DexType itf : dexClass.interfaces.values) {
+        DexClass itfClass = appInfo.definitionFor(itf);
+        if (itfClass != null) {
+          workList.add(itfClass);
+        }
+      }
+      if (dexClass.superType != appInfo.dexItemFactory().objectType) {
+        DexClass superClass = appInfo.definitionFor(dexClass.superType);
+        assert superClass != null; // Cannot be null since we started from a LibraryClass.
+        workList.add(superClass);
+      }
+    }
+    return implementedMethods;
+  }
+}