Refactor CONSTANT_Dynamic desugaring when throwing

Bug: 178172809
Change-Id: I902a341198ca4f0046b7806b55b8eadfd95aaec5
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
index 2933e4d..492d428 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
@@ -3,8 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.desugar.constantdynamic;
 
+import static com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass.Behaviour.CACHE_CONSTANT;
+import static com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass.Behaviour.THROW_ICCE;
+import static com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass.Behaviour.THROW_NSME;
 import static org.objectweb.asm.Opcodes.GETSTATIC;
-import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
 import static org.objectweb.asm.Opcodes.INVOKESTATIC;
 import static org.objectweb.asm.Opcodes.PUTSTATIC;
 
@@ -24,13 +26,13 @@
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfMonitor;
-import com.android.tools.r8.cf.code.CfNew;
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -40,7 +42,6 @@
 import com.android.tools.r8.graph.DexMethod;
 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.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodResolutionResult;
@@ -49,24 +50,38 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.Monitor;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.MethodSynthesizerConsumer;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
 import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.collections.ImmutableDeque;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
 import com.google.common.collect.ImmutableList;
+import java.util.Collection;
 import java.util.List;
+import org.objectweb.asm.Opcodes;
 
 public class ConstantDynamicClass {
+  enum Behaviour {
+    CACHE_CONSTANT,
+    THROW_NSME,
+    THROW_ICCE
+  }
+
   public static final String INITIALIZED_FIELD_NAME = "INITIALIZED";
   public static final String CONST_FIELD_NAME = "CONST";
 
-  final AppView<?> appView;
-  final ConstantDynamicInstructionDesugaring desugaring;
-  private final DexType accessedFrom;
-  public ConstantDynamicReference reference;
+  private final AppView<?> appView;
+  private final ConstantDynamicInstructionDesugaring desugaring;
+  private final ProgramMethod context;
+  public final ConstantDynamicReference reference;
   public final DexField initializedValueField;
   public final DexField constantValueField;
-  final DexMethod getConstMethod;
+  private final DexMethod getConstMethod;
+  private final Behaviour behaviour;
 
   // Considered final but is set after due to circularity in allocation.
   private DexProgramClass clazz = null;
@@ -75,12 +90,12 @@
       SyntheticProgramClassBuilder builder,
       AppView<?> appView,
       ConstantDynamicInstructionDesugaring desugaring,
-      ProgramMethod accessedFrom,
+      ProgramMethod context,
       CfConstDynamic constantDynamic) {
     DexItemFactory factory = appView.dexItemFactory();
     this.appView = appView;
     this.desugaring = desugaring;
-    this.accessedFrom = accessedFrom.getHolderType();
+    this.context = context;
     this.reference = constantDynamic.getReference();
     this.constantValueField =
         factory.createField(
@@ -94,7 +109,57 @@
             factory.createProto(constantDynamic.getType()),
             factory.createString("get"));
 
-    synthesizeConstantDynamicClass(builder);
+    DexMethodHandle bootstrapMethodHandle = reference.getBootstrapMethod();
+    DexMethod bootstrapMethodReference = bootstrapMethodHandle.asMethod();
+    MethodResolutionResult resolution =
+        appView
+            .appInfoForDesugaring()
+            .resolveMethod(bootstrapMethodReference, bootstrapMethodHandle.isInterface);
+    if (resolution.isSingleResolution()
+        && resolution.asSingleResolution().getResolvedMethod().isStatic()) {
+      // Ensure that the bootstrap method is accessible from the generated class.
+      SingleResolutionResult result = resolution.asSingleResolution();
+      MethodAccessFlags flags = result.getResolvedMethod().getAccessFlags();
+      flags.unsetPrivate();
+      flags.setPublic();
+      behaviour = CACHE_CONSTANT;
+      synthesizeConstantDynamicClass(builder);
+    } else {
+      // Unconditionally throw as the RI.
+      behaviour = resolution.isFailedResolution() ? THROW_NSME : THROW_ICCE;
+    }
+  }
+
+  public Collection<CfInstruction> desugarConstDynamicInstruction(
+      CfConstDynamic invoke,
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator,
+      ConstantDynamicDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext) {
+    assert invoke.getReference().equals(reference);
+    if (behaviour == CACHE_CONSTANT) {
+      return ImmutableList.of(new CfInvoke(Opcodes.INVOKESTATIC, getConstMethod, false));
+    }
+    return desugarToThrow(
+        behaviour == THROW_NSME
+            ? UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod
+            : UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod,
+        eventConsumer,
+        context,
+        methodProcessingContext);
+  }
+
+  private Collection<CfInstruction> desugarToThrow(
+      MethodSynthesizerConsumer methodSynthesizerConsumer,
+      ConstantDynamicDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext) {
+    UtilityMethodForCodeOptimizations throwMethod =
+        methodSynthesizerConsumer.synthesizeMethod(appView, methodProcessingContext);
+    ProgramMethod throwProgramMethod = throwMethod.uncheckedGetMethod();
+    eventConsumer.acceptThrowMethod(throwProgramMethod, context);
+    return ImmutableList.of(new CfInvoke(INVOKESTATIC, throwProgramMethod.getReference(), false));
   }
 
   /*
@@ -167,30 +232,6 @@
 
   private CfCode generateGetterCode(SyntheticProgramClassBuilder builder) {
     // TODO(b/178172809): Use MethodHandle.invokeWithArguments if supported.
-    DexMethodHandle bootstrapMethodHandle = reference.getBootstrapMethod();
-    DexMethod bootstrapMethodReference = bootstrapMethodHandle.asMethod();
-    MethodResolutionResult resolution =
-        appView
-            .appInfoForDesugaring()
-            .resolveMethod(bootstrapMethodReference, bootstrapMethodHandle.isInterface);
-    if ((resolution.isSingleResolution()
-            && resolution.asSingleResolution().getResolvedMethod().isStatic())
-        || resolution.isFailedResolution()) {
-      if (resolution.isSingleResolution()) {
-        // Ensure that the bootstrap method is accessible from the generated class.
-        SingleResolutionResult result = resolution.asSingleResolution();
-        MethodAccessFlags flags = result.getResolvedMethod().getAccessFlags();
-        flags.unsetPrivate();
-        flags.setPublic();
-      }
-      return generateGetterCodeInvokingBootstrapMethod(builder);
-    } else {
-      // Unconditionally throw ICCE as the RI.
-      return generateGetterCodeThrowingICCE(builder);
-    }
-  }
-
-  private CfCode generateGetterCodeInvokingBootstrapMethod(SyntheticProgramClassBuilder builder) {
     int maxStack = 3;
     int maxLocals = 2;
     ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
@@ -272,33 +313,6 @@
         localVariables);
   }
 
-  private CfCode generateGetterCodeThrowingICCE(SyntheticProgramClassBuilder builder) {
-    int maxStack = 2;
-    int maxLocals = 0;
-    ImmutableList<CfTryCatch> tryCatchRanges = ImmutableList.of();
-    ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
-    ImmutableList.Builder<CfInstruction> instructions = ImmutableList.builder();
-    DexItemFactory factory = builder.getFactory();
-    instructions.add(new CfNew(factory.icceType));
-    instructions.add(new CfStackInstruction(Opcode.Dup));
-    instructions.add(
-        new CfInvoke(
-            INVOKESPECIAL,
-            factory.createMethod(
-                factory.icceType,
-                factory.createProto(factory.voidType),
-                factory.constructorMethodName),
-            false));
-    instructions.add(new CfThrow());
-    return new CfCode(
-        builder.getType(),
-        maxStack,
-        maxLocals,
-        instructions.build(),
-        tryCatchRanges,
-        localVariables);
-  }
-
   public final DexProgramClass getConstantDynamicProgramClass() {
     assert clazz != null;
     return clazz;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java
index 3c0efd7..1d2dcf6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java
@@ -8,4 +8,6 @@
 public interface ConstantDynamicDesugaringEventConsumer {
 
   void acceptConstantDynamicClass(ConstantDynamicClass lambdaClass, ProgramMethod context);
+
+  void acceptThrowMethod(ProgramMethod method, ProgramMethod context);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
index a01fdd2..8ef2e18 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.cf.code.CfConstDynamic;
 import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -19,12 +18,10 @@
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.Box;
-import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
-import org.objectweb.asm.Opcodes;
 
 public class ConstantDynamicInstructionDesugaring implements CfInstructionDesugaring {
 
@@ -71,8 +68,13 @@
       MethodProcessingContext methodProcessingContext) {
     ConstantDynamicClass constantDynamicClass =
         ensureConstantDynamicClass(invoke, context, methodProcessingContext, eventConsumer);
-    return ImmutableList.of(
-        new CfInvoke(Opcodes.INVOKESTATIC, constantDynamicClass.getConstMethod, false));
+    return constantDynamicClass.desugarConstDynamicInstruction(
+        invoke,
+        freshLocalProvider,
+        localStackAllocator,
+        eventConsumer,
+        context,
+        methodProcessingContext);
   }
 
   // Creates a class corresponding to the constant dynamic symbolic reference and context.
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/BootstrapMethodNotFoundConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/BootstrapMethodNotFoundConstantDynamicTest.java
index 12ee318..cdf9723 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/BootstrapMethodNotFoundConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/BootstrapMethodNotFoundConstantDynamicTest.java
@@ -88,11 +88,6 @@
         .addProgramClassFileData(getTransformedClasses())
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(A.class)
-        // TODO(b/198142613): There should not be a warnings on class references which are
-        //  desugared away.
-        .applyIf(
-            parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
-            b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup"))
         // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
         .applyIf(
             parameters.isCfRuntime(),