Desugaring of CONSTANT_Dynamic to support JaCoCo

This desugaring supports the use of CONSTANT_Dynamic found
in JaCoCo version 0.8.4+ when instrumenting class files
with version 55 or higher.

Initial implementation use <clinit> for synchronizing constant
creation.

Bug: 178172809
Change-Id: I2ec90f9848d1323c5af83a6f3c19066f08b5836d
diff --git a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
index fdec52c..ab9165b 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfCmp;
 import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstDynamic;
 import com.android.tools.r8.cf.code.CfConstMethodHandle;
 import com.android.tools.r8.cf.code.CfConstMethodType;
 import com.android.tools.r8.cf.code.CfConstNull;
@@ -388,6 +389,12 @@
   }
 
   @Override
+  public void print(CfConstDynamic constDynamic) {
+    // TODO(b/198143561): Support CfConstDynamic.
+    throw new Unimplemented(constDynamic.getClass().getSimpleName());
+  }
+
+  @Override
   public void print(CfReturnVoid ret) {
     printNewInstruction("CfReturnVoid");
   }
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index c6ba520..480c9cb 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfCmp;
 import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstDynamic;
 import com.android.tools.r8.cf.code.CfConstMethodHandle;
 import com.android.tools.r8.cf.code.CfConstMethodType;
 import com.android.tools.r8.cf.code.CfConstNull;
@@ -326,6 +327,12 @@
     appendType(constClass.getType());
   }
 
+  public void print(CfConstDynamic constDynamic) {
+    indent();
+    builder.append("ldc <dynamic> ");
+    appendType(constDynamic.getType());
+  }
+
   public void print(CfInitClass initClass) {
     indent();
     builder.append("initclass ");
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
new file mode 100644
index 0000000..e6e1f0d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
@@ -0,0 +1,232 @@
+// 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.cf.code;
+
+import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.CfCompareHelper;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.JarApplicationReader;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicReference;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.structural.CompareToVisitor;
+import java.util.ListIterator;
+import org.objectweb.asm.ConstantDynamic;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CfConstDynamic extends CfInstruction implements CfTypeInstruction {
+
+  private final ConstantDynamicReference reference;
+
+  public CfConstDynamic(
+      DexString name,
+      DexType type,
+      DexMethodHandle bootstrapMethod,
+      Object[] bootstrapMethodArguments) {
+    assert name != null;
+    assert type != null;
+    assert bootstrapMethod != null;
+    assert bootstrapMethodArguments != null;
+    assert bootstrapMethodArguments.length == 0;
+
+    reference = new ConstantDynamicReference(name, type, bootstrapMethod, bootstrapMethodArguments);
+  }
+
+  @Override
+  public CfConstDynamic asConstDynamic() {
+    return this;
+  }
+
+  @Override
+  public boolean isConstDynamic() {
+    return true;
+  }
+
+  public ConstantDynamicReference getReference() {
+    return reference;
+  }
+
+  public DexString getName() {
+    return reference.getName();
+  }
+
+  public DexMethodHandle getBootstrapMethod() {
+    return reference.getBootstrapMethod();
+  }
+
+  public Object[] getBootstrapMethodArguments() {
+    return reference.getBootstrapMethodArguments();
+  }
+
+  public static CfConstDynamic fromAsmConstantDynamic(
+      ConstantDynamic insn, JarApplicationReader application, DexType clazz) {
+    String constantName = insn.getName();
+    String constantDescriptor = insn.getDescriptor();
+    // TODO(b/178172809): Handle bootstrap arguments.
+    if (insn.getBootstrapMethodArgumentCount() > 0) {
+      throw new CompilationError(
+          "Unsupported dynamic constant (has arguments to bootstrap method)");
+    }
+    if (insn.getBootstrapMethod().getTag() != Opcodes.H_INVOKESTATIC) {
+      throw new CompilationError("Unsupported dynamic constant (not invoke static)");
+    }
+    if (insn.getBootstrapMethod().getOwner().equals("java/lang/invoke/ConstantBootstraps")) {
+      throw new CompilationError(
+          "Unsupported dynamic constant (runtime provided bootstrap method)");
+    }
+    if (application.getTypeFromName(insn.getBootstrapMethod().getOwner()) != clazz) {
+      throw new CompilationError("Unsupported dynamic constant (different owner)");
+    }
+    // Resolve the bootstrap method.
+    DexMethodHandle bootstrapMethodHandle =
+        DexMethodHandle.fromAsmHandle(insn.getBootstrapMethod(), application, clazz);
+    if (!bootstrapMethodHandle.member.isDexMethod()) {
+      throw new CompilationError("Unsupported dynamic constant (invalid method handle)");
+    }
+    DexMethod bootstrapMethod = bootstrapMethodHandle.asMethod();
+    if (bootstrapMethod.getProto().returnType != application.getTypeFromDescriptor("[Z")
+        && bootstrapMethod.getProto().returnType
+            != application.getTypeFromDescriptor("Ljava/lang/Object;")) {
+      throw new CompilationError("Unsupported dynamic constant (unsupported constant type)");
+    }
+    if (bootstrapMethod.getProto().getParameters().size() != 3) {
+      throw new CompilationError("Unsupported dynamic constant (unsupported signature)");
+    }
+    if (bootstrapMethod.getProto().getParameters().get(0) != application.getFactory().lookupType) {
+      throw new CompilationError(
+          "Unsupported dynamic constant (unexpected type of first argument to bootstrap method");
+    }
+    if (bootstrapMethod.getProto().getParameters().get(1) != application.getFactory().stringType) {
+      throw new CompilationError(
+          "Unsupported dynamic constant (unexpected type of second argument to bootstrap method");
+    }
+    if (bootstrapMethod.getProto().getParameters().get(2) != application.getFactory().classType) {
+      throw new CompilationError(
+          "Unsupported dynamic constant (unexpected type of third argument to bootstrap method");
+    }
+    return new CfConstDynamic(
+        application.getString(constantName),
+        application.getTypeFromDescriptor(constantDescriptor),
+        bootstrapMethodHandle,
+        new Object[] {});
+  }
+
+  @Override
+  public int getCompareToId() {
+    return CfCompareHelper.CONST_DYNAMIC_COMPARE_ID;
+  }
+
+  @Override
+  public int internalAcceptCompareTo(
+      CfInstruction other, CompareToVisitor visitor, CfCompareHelper helper) {
+    int diff = getName().acceptCompareTo(((CfConstDynamic) other).getName(), visitor);
+    if (diff != 0) {
+      return diff;
+    }
+    diff = getType().acceptCompareTo(((CfConstDynamic) other).getType(), visitor);
+    if (diff != 0) {
+      return diff;
+    }
+    return getBootstrapMethod()
+        .acceptCompareTo(((CfConstDynamic) other).getBootstrapMethod(), visitor);
+  }
+
+  @Override
+  public CfTypeInstruction asTypeInstruction() {
+    return this;
+  }
+
+  @Override
+  public boolean isTypeInstruction() {
+    return true;
+  }
+
+  @Override
+  public DexType getType() {
+    return reference.getType();
+  }
+
+  @Override
+  public CfInstruction withType(DexType newType) {
+    throw new Unimplemented();
+  }
+
+  @Override
+  public void write(
+      AppView<?> appView,
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    // TODO(b/198142625): Support CONSTANT_Dynamic for R8 cf to cf.
+    throw new CompilationError("Unsupported dynamic constant (not desugaring)");
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+    registry.registerTypeReference(reference.getType());
+    registry.registerMethodHandle(
+        reference.getBootstrapMethod(), NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
+    assert reference.getBootstrapMethodArguments().length == 0;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    throw new CompilationError("Unsupported dynamic constant (not desugaring)");
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ... →
+    // ..., value
+    frameBuilder.push(factory.classType);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 6067cc3..188c575 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -118,6 +118,14 @@
     return false;
   }
 
+  public CfConstDynamic asConstDynamic() {
+    return null;
+  }
+
+  public boolean isConstDynamic() {
+    return false;
+  }
+
   public CfFieldInstruction asFieldInstruction() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java b/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java
index 1108c72..d3c3351 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java
@@ -25,6 +25,7 @@
   public static final int CONST_NUMBER_COMPARE_ID;
   public static final int CONST_METHOD_TYPE_COMPARE_ID;
   public static final int CONST_METHOD_HANDLE_COMPARE_ID;
+  public static final int CONST_DYNAMIC_COMPARE_ID;
   public static final int FRAME_COMPARE_ID;
   public static final int INIT_CLASS_COMPARE_ID;
   public static final int LABEL_COMPARE_ID;
@@ -38,6 +39,7 @@
     CONST_NUMBER_COMPARE_ID = ++lastId;
     CONST_METHOD_TYPE_COMPARE_ID = ++lastId;
     CONST_METHOD_HANDLE_COMPARE_ID = ++lastId;
+    CONST_DYNAMIC_COMPARE_ID = ++lastId;
     FRAME_COMPARE_ID = ++lastId;
     INIT_CLASS_COMPARE_ID = ++lastId;
     LABEL_COMPARE_ID = ++lastId;
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 174dca5..c554ca5 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfCmp;
 import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstDynamic;
 import com.android.tools.r8.cf.code.CfConstMethodHandle;
 import com.android.tools.r8.cf.code.CfConstMethodType;
 import com.android.tools.r8.cf.code.CfConstNull;
@@ -887,7 +888,9 @@
             new CfConstMethodHandle(
                 DexMethodHandle.fromAsmHandle((Handle) cst, application, method.holder)));
       } else if (cst instanceof ConstantDynamic) {
-        throw new CompilationError("Unsupported dynamic constant: " + cst.toString());
+        instructions.add(
+            CfConstDynamic.fromAsmConstantDynamic(
+                (ConstantDynamic) cst, application, method.holder));
       } else {
         throw new CompilationError("Unsupported constant: " + cst.toString());
       }
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 c6fd133..2493dd3 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
@@ -14,6 +14,8 @@
 import com.android.tools.r8.ir.conversion.ClassConverterResult;
 import com.android.tools.r8.ir.conversion.D8MethodProcessor;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethodDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass;
+import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterInstructionEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPIConverterEventConsumer;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo;
@@ -45,6 +47,7 @@
     implements BackportedMethodDesugaringEventConsumer,
         InvokeSpecialToSelfDesugaringEventConsumer,
         LambdaDesugaringEventConsumer,
+        ConstantDynamicDesugaringEventConsumer,
         NestBasedAccessDesugaringEventConsumer,
         RecordInstructionDesugaringEventConsumer,
         TwrCloseResourceDesugaringEventConsumer,
@@ -61,12 +64,14 @@
   public static R8CfInstructionDesugaringEventConsumer createForR8(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer,
+      BiConsumer<ConstantDynamicClass, ProgramMethod> constantDynamicClassConsumer,
       BiConsumer<ProgramMethod, ProgramMethod> twrCloseResourceMethodConsumer,
       SyntheticAdditions additions,
       BiConsumer<ProgramMethod, ProgramMethod> companionMethodConsumer) {
     return new R8CfInstructionDesugaringEventConsumer(
         appView,
         lambdaClassConsumer,
+        constantDynamicClassConsumer,
         twrCloseResourceMethodConsumer,
         additions,
         companionMethodConsumer);
@@ -141,6 +146,12 @@
       }
 
       @Override
+      public void acceptConstantDynamicClass(
+          ConstantDynamicClass constantDynamicClass, ProgramMethod context) {
+        assert false;
+      }
+
+      @Override
       public void acceptNestFieldGetBridge(ProgramField target, ProgramMethod bridge) {
         assert false;
       }
@@ -175,6 +186,7 @@
     private final Map<DexReference, InvokeSpecialBridgeInfo> pendingInvokeSpecialBridges =
         new LinkedHashMap<>();
     private final List<LambdaClass> synthesizedLambdaClasses = new ArrayList<>();
+    private final List<ConstantDynamicClass> synthesizedConstantDynamicClasses = new ArrayList<>();
 
     private D8CfInstructionDesugaringEventConsumer(D8MethodProcessor methodProcessor) {
       this.methodProcessor = methodProcessor;
@@ -226,6 +238,14 @@
     }
 
     @Override
+    public void acceptConstantDynamicClass(
+        ConstantDynamicClass constantDynamicClass, ProgramMethod context) {
+      synchronized (synthesizedConstantDynamicClasses) {
+        synthesizedConstantDynamicClasses.add(constantDynamicClass);
+      }
+    }
+
+    @Override
     public void acceptNestFieldGetBridge(ProgramField target, ProgramMethod bridge) {
       assert false;
     }
@@ -276,6 +296,7 @@
       List<ProgramMethod> needsProcessing = new ArrayList<>();
       finalizeInvokeSpecialDesugaring(appView, needsProcessing::add);
       finalizeLambdaDesugaring(classConverterResultBuilder, needsProcessing::add);
+      finalizeConstantDynamicDesugaring(needsProcessing::add);
       return needsProcessing;
     }
 
@@ -318,6 +339,13 @@
       synthesizedLambdaClasses.clear();
     }
 
+    private void finalizeConstantDynamicDesugaring(Consumer<ProgramMethod> needsProcessing) {
+      for (ConstantDynamicClass constantDynamicClass : synthesizedConstantDynamicClasses) {
+        constantDynamicClass.getConstantDynamicProgramClass().forEachProgramMethod(needsProcessing);
+      }
+      synthesizedLambdaClasses.clear();
+    }
+
     public boolean verifyNothingToFinalize() {
       assert pendingInvokeSpecialBridges.isEmpty();
       assert synthesizedLambdaClasses.isEmpty();
@@ -330,26 +358,31 @@
 
     private final AppView<? extends AppInfoWithClassHierarchy> appView;
 
-    // TODO(b/180091213): Remove these two consumers when synthesizing contexts are accessible from
+    // TODO(b/180091213): Remove these three consumers when synthesizing contexts are accessible
+    // from
     //  synthetic items.
     private final BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer;
+    private final BiConsumer<ConstantDynamicClass, ProgramMethod> constantDynamicClassConsumer;
     private final BiConsumer<ProgramMethod, ProgramMethod> twrCloseResourceMethodConsumer;
     private final SyntheticAdditions additions;
 
     private final Map<LambdaClass, ProgramMethod> synthesizedLambdaClasses =
         new IdentityHashMap<>();
     private final List<InvokeSpecialBridgeInfo> pendingInvokeSpecialBridges = new ArrayList<>();
+    private final List<ConstantDynamicClass> synthesizedConstantDynamicClasses = new ArrayList<>();
 
     private final BiConsumer<ProgramMethod, ProgramMethod> onCompanionMethodCallback;
 
     public R8CfInstructionDesugaringEventConsumer(
         AppView<? extends AppInfoWithClassHierarchy> appView,
         BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer,
+        BiConsumer<ConstantDynamicClass, ProgramMethod> constantDynamicClassConsumer,
         BiConsumer<ProgramMethod, ProgramMethod> twrCloseResourceMethodConsumer,
         SyntheticAdditions additions,
         BiConsumer<ProgramMethod, ProgramMethod> onCompanionMethodCallback) {
       this.appView = appView;
       this.lambdaClassConsumer = lambdaClassConsumer;
+      this.constantDynamicClassConsumer = constantDynamicClassConsumer;
       this.twrCloseResourceMethodConsumer = twrCloseResourceMethodConsumer;
       this.additions = additions;
       this.onCompanionMethodCallback = onCompanionMethodCallback;
@@ -430,6 +463,17 @@
     }
 
     @Override
+    public void acceptConstantDynamicClass(
+        ConstantDynamicClass constantDynamicClass, ProgramMethod context) {
+      synchronized (synthesizedConstantDynamicClasses) {
+        synthesizedConstantDynamicClasses.add(constantDynamicClass);
+      }
+      // TODO(b/180091213): Remove the recording of the synthesizing context when this is accessible
+      //  from synthetic items.
+      constantDynamicClassConsumer.accept(constantDynamicClass, context);
+    }
+
+    @Override
     public void acceptNestFieldGetBridge(ProgramField target, ProgramMethod bridge) {
       assert false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index dd15950..bd9805d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
@@ -95,6 +96,7 @@
       desugarings.add(desugaredLibraryAPIConverter);
     }
     desugarings.add(new LambdaInstructionDesugaring(appView));
+    desugarings.add(new ConstantDynamicInstructionDesugaring(appView));
     desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
     desugarings.add(new InvokeToPrivateRewriter());
     desugarings.add(new StringConcatInstructionDesugaring(appView));
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
new file mode 100644
index 0000000..0743b32
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
@@ -0,0 +1,221 @@
+// 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.ir.desugar.constantdynamic;
+
+import static org.objectweb.asm.Opcodes.GETSTATIC;
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+import static org.objectweb.asm.Opcodes.PUTSTATIC;
+
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstDynamic;
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+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.DexValue.DexValueNull;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ConstantDynamicClass {
+  public static final String CONDY_CONST_FIELD_NAME = "CONST";
+
+  final AppView<?> appView;
+  final ConstantDynamicInstructionDesugaring desugaring;
+  private final DexType accessedFrom;
+  public ConstantDynamicReference reference;
+  public final DexField constantValueField;
+  final DexMethod classConstructor;
+  final DexMethod getConstMethod;
+
+  // Considered final but is set after due to circularity in allocation.
+  private DexProgramClass clazz = null;
+
+  public ConstantDynamicClass(
+      SyntheticProgramClassBuilder builder,
+      AppView<?> appView,
+      ConstantDynamicInstructionDesugaring desugaring,
+      ProgramMethod accessedFrom,
+      CfConstDynamic constantDynamic) {
+    DexItemFactory factory = appView.dexItemFactory();
+    this.appView = appView;
+    this.desugaring = desugaring;
+    this.accessedFrom = accessedFrom.getHolderType();
+    this.reference = constantDynamic.getReference();
+    this.constantValueField =
+        factory.createField(
+            builder.getType(),
+            constantDynamic.getType(),
+            factory.createString(CONDY_CONST_FIELD_NAME));
+    this.classConstructor =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(factory.voidType),
+            factory.classConstructorMethodName);
+    this.getConstMethod =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(constantDynamic.getType()),
+            factory.createString("get"));
+
+    synthesizeConstantDynamicClass(builder);
+  }
+
+  /*
+    // TODO(b/178172809): Don't use <clinit> to synchronize constant creation.
+    // Simple one class per. constant using <clinit> to synchronize constant creation
+    // generated with a pattern like this:
+
+    class ExternalSyntheticXXX {
+       public static <constant type> CONST =
+           (<constant type>) bootstrapMethod(null, "constant name", <constant type>)
+
+       public static <constant type> get() {
+         return CONST;
+       }
+     }
+
+  */
+  private void synthesizeConstantDynamicClass(SyntheticProgramClassBuilder builder) {
+    synthesizeStaticFields(builder);
+    synthesizeDirectMethods(builder);
+  }
+
+  private void synthesizeStaticFields(SyntheticProgramClassBuilder builder) {
+    // Create static field for the constant.
+    boolean deprecated = false;
+    boolean d8R8Synthesized = true;
+    builder.setStaticFields(
+        ImmutableList.of(
+            new DexEncodedField(
+                this.constantValueField,
+                FieldAccessFlags.fromSharedAccessFlags(
+                    Constants.ACC_PRIVATE
+                        | Constants.ACC_FINAL
+                        | Constants.ACC_SYNTHETIC
+                        | Constants.ACC_STATIC),
+                FieldTypeSignature.noSignature(),
+                DexAnnotationSet.empty(),
+                DexValueNull.NULL,
+                deprecated,
+                d8R8Synthesized,
+                // The api level is computed when tracing.
+                AndroidApiLevel.minApiLevelIfEnabledOrUnknown(appView))));
+  }
+
+  private void synthesizeDirectMethods(SyntheticProgramClassBuilder builder) {
+
+    List<DexEncodedMethod> methods = new ArrayList<>(2);
+    methods.add(
+        DexEncodedMethod.builder()
+            .setMethod(classConstructor)
+            .setAccessFlags(MethodAccessFlags.createForClassInitializer())
+            .setCode(generateClassInitializerCode(builder.getType()))
+            .setD8R8Synthesized()
+            .setApiLevelForDefinition(AndroidApiLevel.S)
+            .setApiLevelForCode(AndroidApiLevel.S)
+            .build());
+    methods.add(
+        DexEncodedMethod.builder()
+            .setMethod(getConstMethod)
+            .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+            .setCode(generateGetterCode(builder.getType()))
+            .setD8R8Synthesized()
+            .setApiLevelForDefinition(AndroidApiLevel.S)
+            .setApiLevelForCode(AndroidApiLevel.S)
+            .build());
+    builder.setDirectMethods(methods);
+  }
+
+  private CfCode generateClassInitializerCode(DexType holder) {
+    int maxStack = 3;
+    int maxLocals = 0;
+    ImmutableList<CfTryCatch> tryCatchRanges = ImmutableList.of();
+    ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
+    ImmutableList.Builder<CfInstruction> instructions = ImmutableList.builder();
+    invokeBootstrapMethod(reference, instructions);
+    instructions.add(new CfFieldInstruction(PUTSTATIC, constantValueField));
+    instructions.add(new CfReturnVoid());
+    return new CfCode(
+        holder, maxStack, maxLocals, instructions.build(), tryCatchRanges, localVariables);
+  }
+
+  private void invokeBootstrapMethod(
+      ConstantDynamicReference reference, ImmutableList.Builder<CfInstruction> instructions) {
+    assert reference.getBootstrapMethod().type.isInvokeStatic();
+
+    // 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.isFailedResolution()) {
+      // TODO(b/178172809): Generate code which throws ICCE.
+    }
+    SingleResolutionResult result = resolution.asSingleResolution();
+    assert result.getResolvedMethod().isStatic();
+    assert result.getResolvedHolder().isProgramClass();
+    instructions.add(new CfConstNull());
+    instructions.add(new CfConstString(reference.getName()));
+    instructions.add(new CfConstClass(reference.getType()));
+    instructions.add(new CfInvoke(INVOKESTATIC, bootstrapMethodReference, false));
+    instructions.add(new CfCheckCast(reference.getType()));
+
+    // Ensure that the bootstrap method is accessible from the generated class.
+    MethodAccessFlags flags = result.getResolvedMethod().getAccessFlags();
+    flags.unsetPrivate();
+    flags.setPublic();
+  }
+
+  private CfCode generateGetterCode(DexType holder) {
+    int maxStack = 1;
+    int maxLocals = 0;
+    ImmutableList<CfTryCatch> tryCatchRanges = ImmutableList.of();
+    ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
+    ImmutableList.Builder<CfInstruction> instructions = ImmutableList.builder();
+    instructions.add(new CfFieldInstruction(GETSTATIC, constantValueField));
+    instructions.add(new CfReturn(ValueType.OBJECT));
+    return new CfCode(
+        holder, maxStack, maxLocals, instructions.build(), tryCatchRanges, localVariables);
+  }
+
+  public final DexProgramClass getConstantDynamicProgramClass() {
+    assert clazz != null;
+    return clazz;
+  }
+
+  public void setClass(DexProgramClass clazz) {
+    assert this.clazz == null;
+    assert clazz != null;
+    this.clazz = 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
new file mode 100644
index 0000000..3c0efd7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java
@@ -0,0 +1,11 @@
+// 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.ir.desugar.constantdynamic;
+
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface ConstantDynamicDesugaringEventConsumer {
+
+  void acceptConstantDynamicClass(ConstantDynamicClass lambdaClass, 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
new file mode 100644
index 0000000..a01fdd2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
@@ -0,0 +1,129 @@
+// 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.ir.desugar.constantdynamic;
+
+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;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
+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 {
+
+  private final AppView<?> appView;
+  private final Map<DexType, Map<ConstantDynamicReference, ConstantDynamicClass>>
+      dynamicConstantSyntheticsPerClass = new ConcurrentHashMap<>();
+
+  public ConstantDynamicInstructionDesugaring(AppView<?> appView) {
+    this.appView = appView;
+  }
+
+  @Override
+  public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+    return instruction.isConstDynamic();
+  }
+
+  @Override
+  public Collection<CfInstruction> desugarInstruction(
+      CfInstruction instruction,
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory) {
+    if (instruction.isConstDynamic()) {
+      return desugarConstDynamicInstruction(
+          instruction.asConstDynamic(),
+          freshLocalProvider,
+          localStackAllocator,
+          eventConsumer,
+          context,
+          methodProcessingContext);
+    }
+    return null;
+  }
+
+  private Collection<CfInstruction> desugarConstDynamicInstruction(
+      CfConstDynamic invoke,
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator,
+      ConstantDynamicDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext) {
+    ConstantDynamicClass constantDynamicClass =
+        ensureConstantDynamicClass(invoke, context, methodProcessingContext, eventConsumer);
+    return ImmutableList.of(
+        new CfInvoke(Opcodes.INVOKESTATIC, constantDynamicClass.getConstMethod, false));
+  }
+
+  // Creates a class corresponding to the constant dynamic symbolic reference and context.
+  // TODO(b/178172809): Move this ensure handling to the synthetic items handling and move to
+  //  one class for dynamic constants for each class using dynamic constants.
+  private ConstantDynamicClass ensureConstantDynamicClass(
+      CfConstDynamic constantDynamic,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
+      ConstantDynamicDesugaringEventConsumer eventConsumer) {
+    Map<ConstantDynamicReference, ConstantDynamicClass> dynamicConstantSyntheticsForClass =
+        dynamicConstantSyntheticsPerClass.computeIfAbsent(
+            context.getHolderType(), (ignore) -> new HashMap<>());
+    ConstantDynamicClass result =
+        dynamicConstantSyntheticsForClass.get(constantDynamic.getReference());
+    if (result == null) {
+      synchronized (dynamicConstantSyntheticsForClass) {
+        result = dynamicConstantSyntheticsForClass.get(constantDynamic.getReference());
+        if (result == null) {
+          result =
+              createConstantDynamicClass(
+                  constantDynamic, context, methodProcessingContext, eventConsumer);
+          dynamicConstantSyntheticsForClass.put(constantDynamic.getReference(), result);
+        }
+      }
+    }
+    return result;
+  }
+
+  // TODO(b/178172809): Change to use ensureFixedClass.
+  private ConstantDynamicClass createConstantDynamicClass(
+      CfConstDynamic constantDynamic,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
+      ConstantDynamicDesugaringEventConsumer eventConsumer) {
+    Box<ConstantDynamicClass> box = new Box<>();
+    DexProgramClass clazz =
+        appView
+            .getSyntheticItems()
+            .createClass(
+                SyntheticKind.CONST_DYNAMIC,
+                methodProcessingContext.createUniqueContext(),
+                appView,
+                builder ->
+                    box.set(
+                        new ConstantDynamicClass(
+                            builder, appView, this, context, constantDynamic)));
+    // Immediately set the actual program class on the constant dynamic.
+    ConstantDynamicClass constantDynamicClass = box.get();
+    constantDynamicClass.setClass(clazz);
+    eventConsumer.acceptConstantDynamicClass(constantDynamicClass, context);
+    return constantDynamicClass;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
new file mode 100644
index 0000000..da8257c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
@@ -0,0 +1,61 @@
+// 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.ir.desugar.constantdynamic;
+
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import java.util.Objects;
+
+public class ConstantDynamicReference {
+  private final DexString name;
+  private final DexType type;
+  private final DexMethodHandle bootstrapMethod;
+  private final Object[] bootstrapMethodArguments;
+
+  public ConstantDynamicReference(
+      DexString name,
+      DexType type,
+      DexMethodHandle bootstrapMethod,
+      Object[] bootstrapMethodArguments) {
+    assert bootstrapMethodArguments.length == 0;
+    this.name = name;
+    this.type = type;
+    this.bootstrapMethod = bootstrapMethod;
+    this.bootstrapMethodArguments = bootstrapMethodArguments;
+  }
+
+  public DexString getName() {
+    return name;
+  }
+
+  public DexType getType() {
+    return type;
+  }
+
+  public DexMethodHandle getBootstrapMethod() {
+    return bootstrapMethod;
+  }
+
+  public Object[] getBootstrapMethodArguments() {
+    return bootstrapMethodArguments;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (!(obj instanceof ConstantDynamicReference)) {
+      return false;
+    }
+    ConstantDynamicReference other = (ConstantDynamicReference) obj;
+    return Objects.equals(name, other.name)
+        && Objects.equals(type, other.type)
+        && Objects.equals(bootstrapMethod, other.bootstrapMethod);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(name, type, bootstrapMethod);
+  }
+}
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 a30b406..66e9136 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -100,6 +100,7 @@
 import com.android.tools.r8.ir.desugar.LambdaClass;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.ir.desugar.ProgramAdditions;
+import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
 import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor;
@@ -3516,6 +3517,7 @@
         CfInstructionDesugaringEventConsumer.createForR8(
             appView,
             this::recordLambdaSynthesizingContext,
+            this::recordConstantDynamicSynthesizingContext,
             this::recordTwrCloseResourceMethodSynthesizingContext,
             additions,
             (method, companion) -> {
@@ -3566,6 +3568,13 @@
     }
   }
 
+  private void recordConstantDynamicSynthesizingContext(
+      ConstantDynamicClass constantDynamicClass, ProgramMethod context) {
+    synchronized (synthesizingContexts) {
+      synthesizingContexts.put(constantDynamicClass.getConstantDynamicProgramClass(), context);
+    }
+  }
+
   private void recordTwrCloseResourceMethodSynthesizingContext(
       ProgramMethod closeMethod, ProgramMethod context) {
     synchronized (synthesizingContexts) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 61d74fd..0a05c0f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -39,6 +39,7 @@
     HORIZONTAL_INIT_TYPE_ARGUMENT_1(SYNTHETIC_CLASS_SEPARATOR + "IA$1", 6, false, true),
     HORIZONTAL_INIT_TYPE_ARGUMENT_2(SYNTHETIC_CLASS_SEPARATOR + "IA$2", 7, false, true),
     HORIZONTAL_INIT_TYPE_ARGUMENT_3(SYNTHETIC_CLASS_SEPARATOR + "IA$3", 8, false, true),
+    CONST_DYNAMIC("$Condy", 30, false),
     // Method synthetics.
     ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD("CheckNotZero", 27, true),
     RECORD_HELPER("Record", 9, true),
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/BasicConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/BasicConstantDynamicTest.java
index b0d261e..af9f383 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/BasicConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/BasicConstantDynamicTest.java
@@ -12,6 +12,7 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
@@ -40,6 +41,7 @@
   }
 
   private static final String EXPECTED_OUTPUT = StringUtils.lines("true", "true");
+  private static final Class<?> MAIN_CLASS = A.class;
 
   @Test
   public void testReference() throws Exception {
@@ -49,49 +51,75 @@
 
     testForJvm()
         .addProgramClassFileData(getTransformedClasses())
-        .run(parameters.getRuntime(), A.class)
+        .run(parameters.getRuntime(), MAIN_CLASS)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
+  public void TestD8Cf() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramClassFileData(getTransformedClasses())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .applyIf(
+            // When not desugaring the CF code requires JDK 11.
+            DesugarTestConfiguration::isNotDesugared,
+            r -> {
+              if (parameters.isCfRuntime()
+                  && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+                r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+              } else {
+                r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+              }
+            })
+        .applyIf(
+            DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  @Test
   public void testD8() throws Exception {
     assumeTrue(parameters.getRuntime().isDex());
 
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForD8(parameters.getBackend())
-                .addProgramClassFileData(getTransformedClasses())
-                .setMinApi(parameters.getApiLevel())
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      diagnostics.assertOnlyErrors();
-                      diagnostics.assertErrorsMatch(
-                          allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
-                              diagnosticOrigin(hasParent(Origin.unknown()))));
-                    }));
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
   public void testR8() throws Exception {
     assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
 
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForR8(parameters.getBackend())
-                .addProgramClassFileData(getTransformedClasses())
-                .setMinApi(parameters.getApiLevel())
-                .addKeepMainRule(A.class)
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      diagnostics.assertOnlyErrors();
-                      diagnostics.assertErrorsMatch(
-                          allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
-                              diagnosticOrigin(hasParent(Origin.unknown()))));
-                    }));
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MAIN_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(),
+            r -> {
+              assertThrows(
+                  CompilationFailedException.class,
+                  () ->
+                      r.compileWithExpectedDiagnostics(
+                          diagnostics -> {
+                            diagnostics.assertOnlyErrors();
+                            diagnostics.assertErrorsMatch(
+                                allOf(
+                                    diagnosticMessage(
+                                        containsString(
+                                            "Unsupported dynamic constant (not desugaring)")),
+                                    diagnosticOrigin(hasParent(Origin.unknown()))));
+                          }));
+            },
+            r ->
+                r.run(parameters.getRuntime(), MAIN_CLASS)
+                    .assertSuccessWithOutput(EXPECTED_OUTPUT));
   }
 
   private byte[] getTransformedClasses() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java
index 9286463..48cbeaf 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java
@@ -56,7 +56,8 @@
         .compileWithExpectedDiagnostics(
             diagnostics ->
                 diagnostics.assertErrorMessageThatMatches(
-                    containsString("Unsupported dynamic constant")));
+                    containsString(
+                        "Unsupported dynamic constant (runtime provided bootstrap method)")));
   }
 
   @Test(expected = CompilationFailedException.class)
@@ -70,7 +71,8 @@
         .compileWithExpectedDiagnostics(
             diagnostics ->
                 diagnostics.assertErrorMessageThatMatches(
-                    containsString("Unsupported dynamic constant")));
+                    containsString(
+                        "Unsupported dynamic constant (runtime provided bootstrap method)")));
   }
 
   private byte[] getTransformedMain() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/HierarchyConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/HierarchyConstantDynamicTest.java
index c206b32..edc3cab 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/HierarchyConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/HierarchyConstantDynamicTest.java
@@ -38,6 +38,7 @@
   }
 
   private static final String EXPECTED_OUTPUT = StringUtils.lines("true", "true", "true");
+  private static final Class<?> MAIN_CLASS = A.class;
 
   @Test
   public void testReference() throws Exception {
@@ -55,37 +56,45 @@
   public void testD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
 
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForD8(parameters.getBackend())
-                .addProgramClassFileData(getTransformedClasses())
-                .setMinApi(parameters.getApiLevel())
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      diagnostics.assertOnlyErrors();
-                      diagnostics.assertErrorsMatch(
-                          diagnosticMessage(containsString("Unsupported dynamic constant")));
-                    }));
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
   public void testR8() throws Exception {
     assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
 
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForR8(parameters.getBackend())
-                .addProgramClassFileData(getTransformedClasses())
-                .setMinApi(parameters.getApiLevel())
-                .addKeepMainRule(A.class)
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      diagnostics.assertOnlyErrors();
-                      diagnostics.assertErrorsMatch(
-                          diagnosticMessage(containsString("Unsupported dynamic constant")));
-                    }));
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MAIN_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(),
+            r -> {
+              assertThrows(
+                  CompilationFailedException.class,
+                  () ->
+                      r.compileWithExpectedDiagnostics(
+                          diagnostics -> {
+                            diagnostics.assertOnlyErrors();
+                            diagnostics.assertErrorsMatch(
+                                diagnosticMessage(
+                                    containsString(
+                                        "Unsupported dynamic constant (not desugaring)")));
+                          }));
+            },
+            r ->
+                r.run(parameters.getRuntime(), MAIN_CLASS)
+                    .assertSuccessWithOutput(EXPECTED_OUTPUT));
   }
 
   private Collection<byte[]> getTransformedClasses() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
index d1a4d35..86fad6d 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
@@ -3,20 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar.constantdynamic;
 
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
-import static com.android.tools.r8.OriginMatcher.hasParent;
 import static com.android.tools.r8.utils.DescriptorUtils.JAVA_PACKAGE_SEPARATOR;
 import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-import static org.hamcrest.CoreMatchers.allOf;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThrows;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
@@ -24,7 +17,6 @@
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.utils.ArchiveResourceProvider;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ZipUtils;
@@ -45,8 +37,7 @@
 @RunWith(Parameterized.class)
 public class JacocoConstantDynamicTest extends TestBase {
 
-  @Parameter(0)
-  public TestParameters parameters;
+  @Parameter() public TestParameters parameters;
 
   @Parameter(1)
   public boolean useConstantDynamic;
@@ -140,25 +131,11 @@
         assertFalse(Files.exists(agentOutput));
       }
     } else {
-      assertThrows(
-          CompilationFailedException.class,
-          () -> {
-            ArchiveResourceProvider provider =
-                ArchiveResourceProvider.fromArchive(testClasses.getInstrumented(), true);
-            testForD8(parameters.getBackend())
-                .addProgramResourceProviders(provider)
-                .addProgramFiles(ToolHelper.JACOCO_AGENT)
-                .setMinApi(parameters.getApiLevel())
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      // Check that the error is reported as an error to the diagnostics handler.
-                      diagnostics.assertOnlyErrors();
-                      diagnostics.assertErrorsMatch(
-                          allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
-                              diagnosticOrigin(hasParent(provider.getOrigin()))));
-                    });
-          });
+      testForD8(parameters.getBackend())
+          .addProgramFiles(testClasses.getInstrumented())
+          .addProgramFiles(ToolHelper.JACOCO_AGENT)
+          .setMinApi(parameters.getApiLevel())
+          .compile();
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleNamedConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleNamedConstantDynamicTest.java
index 69cd883..1f7b9d1 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleNamedConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleNamedConstantDynamicTest.java
@@ -12,6 +12,7 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
@@ -42,6 +43,7 @@
 
   private static final String EXPECTED_OUTPUT =
       StringUtils.lines("true", "true", "true", "true", "true");
+  private static final Class<?> MAIN_CLASS = A.class;
 
   @Test
   public void testReference() throws Exception {
@@ -51,28 +53,39 @@
 
     testForJvm()
         .addProgramClassFileData(getTransformedClasses())
-        .run(parameters.getRuntime(), A.class)
+        .run(parameters.getRuntime(), MAIN_CLASS)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
+  public void TestD8Cf() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramClassFileData(getTransformedClasses())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .applyIf(
+            // When not desugaring the CF code requires JDK 11.
+            DesugarTestConfiguration::isNotDesugared,
+            r -> {
+              if (parameters.isCfRuntime()
+                  && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+                r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+              } else {
+                r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+              }
+            })
+        .applyIf(
+            DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  @Test
   public void testD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
 
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForD8(parameters.getBackend())
-                .addProgramClassFileData(getTransformedClasses())
-                .setMinApi(parameters.getApiLevel())
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      diagnostics.assertOnlyErrors();
-                      diagnostics.assertErrorsMatch(
-                          allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
-                              diagnosticOrigin(hasParent(Origin.unknown()))));
-                    }));
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
@@ -80,21 +93,36 @@
     assumeTrue(
         parameters.getRuntime().isDex() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
 
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForR8(parameters.getBackend())
-                .addProgramClassFileData(getTransformedClasses())
-                .setMinApi(parameters.getApiLevel())
-                .addKeepMainRule(A.class)
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      diagnostics.assertOnlyErrors();
-                      diagnostics.assertErrorsMatch(
-                          allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
-                              diagnosticOrigin(hasParent(Origin.unknown()))));
-                    }));
+    testForR8(parameters.getBackend())
+        .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(),
+            r -> {
+              assertThrows(
+                  CompilationFailedException.class,
+                  () ->
+                      r.compileWithExpectedDiagnostics(
+                          diagnostics -> {
+                            diagnostics.assertOnlyErrors();
+                            diagnostics.assertErrorsMatch(
+                                allOf(
+                                    diagnosticMessage(
+                                        containsString(
+                                            "Unsupported dynamic constant (not desugaring)")),
+                                    diagnosticOrigin(hasParent(Origin.unknown()))));
+                          }));
+            },
+            r ->
+                r.run(parameters.getRuntime(), MAIN_CLASS)
+                    .assertSuccessWithOutput(EXPECTED_OUTPUT));
   }
 
   private byte[] getTransformedClasses() throws IOException {
@@ -138,6 +166,9 @@
     }
 
     private static Object myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) {
+      // TODO(b/178172809): Use a un-moddeled library method to prevent R8 from seeing that name
+      //  is unused.
+      name.toCharArray();
       return new Object();
     }
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleTypesConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleTypesConstantDynamicTest.java
index b04792f..5ee0e8f 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleTypesConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleTypesConstantDynamicTest.java
@@ -12,6 +12,7 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
@@ -41,6 +42,7 @@
 
   private static final String EXPECTED_OUTPUT =
       StringUtils.lines("true", "true", "true", "true", "true");
+  private static final Class<?> MAIN_CLASS = A.class;
 
   @Test
   public void testReference() throws Exception {
@@ -55,23 +57,34 @@
   }
 
   @Test
+  public void TestD8Cf() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramClassFileData(getTransformedClasses())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .applyIf(
+            // When not desugaring the CF code requires JDK 11.
+            DesugarTestConfiguration::isNotDesugared,
+            r -> {
+              if (parameters.isCfRuntime()
+                  && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+                r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+              } else {
+                r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+              }
+            })
+        .applyIf(
+            DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  @Test
   public void testD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
 
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForD8(parameters.getBackend())
-                .addProgramClassFileData(getTransformedClasses())
-                .setMinApi(parameters.getApiLevel())
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      diagnostics.assertOnlyErrors();
-                      diagnostics.assertErrorsMatch(
-                          allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
-                              diagnosticOrigin(hasParent(Origin.unknown()))));
-                    }));
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
@@ -79,21 +92,36 @@
     assumeTrue(
         parameters.getRuntime().isDex() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
 
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForR8(parameters.getBackend())
-                .addProgramClassFileData(getTransformedClasses())
-                .setMinApi(parameters.getApiLevel())
-                .addKeepMainRule(A.class)
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      diagnostics.assertOnlyErrors();
-                      diagnostics.assertErrorsMatch(
-                          allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
-                              diagnosticOrigin(hasParent(Origin.unknown()))));
-                    }));
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MAIN_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(),
+            r -> {
+              assertThrows(
+                  CompilationFailedException.class,
+                  () ->
+                      r.compileWithExpectedDiagnostics(
+                          diagnostics -> {
+                            diagnostics.assertOnlyErrors();
+                            diagnostics.assertErrorsMatch(
+                                allOf(
+                                    diagnosticMessage(
+                                        containsString(
+                                            "Unsupported dynamic constant (not desugaring)")),
+                                    diagnosticOrigin(hasParent(Origin.unknown()))));
+                          }));
+            },
+            r ->
+                r.run(parameters.getRuntime(), MAIN_CLASS)
+                    .assertSuccessWithOutput(EXPECTED_OUTPUT));
   }
 
   private byte[] getTransformedClasses() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java
index 24c9ed8..2021777 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java
@@ -44,6 +44,7 @@
 
   private static final String EXPECTED_OUTPUT =
       StringUtils.lines("true", "true", "true", "true", "true");
+  private static final Class<?> MAIN_CLASS = A.class;
 
   @Test
   public void testReference() throws Exception {
@@ -53,11 +54,30 @@
 
     testForJvm()
         .addProgramClassFileData(getTransformedClasses())
-        .run(parameters.getRuntime(), A.class)
+        .run(parameters.getRuntime(), MAIN_CLASS)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
+  public void TestD8Cf() throws Exception {
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForD8(Backend.CF)
+                .addProgramClassFileData(getTransformedClasses())
+                .setMinApi(parameters.getApiLevel())
+                .compileWithExpectedDiagnostics(
+                    diagnostics -> {
+                      diagnostics.assertOnlyErrors();
+                      diagnostics.assertErrorsMatch(
+                          allOf(
+                              diagnosticMessage(
+                                  containsString("Unsupported dynamic constant (different owner)")),
+                              diagnosticOrigin(hasParent(Origin.unknown()))));
+                    }));
+  }
+
+  @Test
   public void testD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
 
@@ -72,7 +92,8 @@
                       diagnostics.assertOnlyErrors();
                       diagnostics.assertErrorsMatch(
                           allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
+                              diagnosticMessage(
+                                  containsString("Unsupported dynamic constant (different owner)")),
                               diagnosticOrigin(hasParent(Origin.unknown()))));
                     }));
   }
@@ -87,13 +108,14 @@
             testForR8(parameters.getBackend())
                 .addProgramClassFileData(getTransformedClasses())
                 .setMinApi(parameters.getApiLevel())
-                .addKeepMainRule(A.class)
+                .addKeepMainRule(MAIN_CLASS)
                 .compileWithExpectedDiagnostics(
                     diagnostics -> {
                       diagnostics.assertOnlyErrors();
                       diagnostics.assertErrorsMatch(
                           allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
+                              diagnosticMessage(
+                                  containsString("Unsupported dynamic constant (different owner)")),
                               diagnosticOrigin(hasParent(Origin.unknown()))));
                     }));
   }