VarHandle desugaring: Library desugaring of ConcurrentLinkedQueue

Enable VarHandle desugaring in L8 and add ConcurrentLinkedQueue to all
JDK11 based desugared library configurations.

Bug: b/246860430
Bug: b/247076137
Change-Id: Id462e53e33bf229bcda7758e4817073b9dcdc27e
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index f3d0764..d6649f7 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -24,7 +24,8 @@
     {
       "api_level_below_or_equal": 32,
       "rewrite_prefix": {
-        "java.util.concurrent.DesugarTimeUnit": "j$.util.concurrent.DesugarTimeUnit"
+        "java.util.concurrent.DesugarTimeUnit": "j$.util.concurrent.DesugarTimeUnit",
+        "java.util.concurrent.ConcurrentLinkedQueue": "j$.util.concurrent.ConcurrentLinkedQueue"
       },
       "retarget_method": {
         "java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit#of(java.time.temporal.ChronoUnit)": "java.util.concurrent.DesugarTimeUnit",
@@ -300,6 +301,12 @@
   ],
   "library_flags": [
     {
+      "api_level_below_or_equal": 32,
+      "rewrite_prefix": {
+        "java.util.concurrent.Helpers": "j$.util.concurrent.Helpers"
+      }
+    },
+    {
       "api_level_below_or_equal": 30,
       "rewrite_prefix": {
         "jdk.internal.": "j$.jdk.internal.",
@@ -342,7 +349,6 @@
         "java.util.KeyValueHolder": "j$.util.KeyValueHolder",
         "java.util.SortedSet$1": "j$.util.SortedSet$1",
         "java.util.Tripwire": "j$.util.Tripwire",
-        "java.util.concurrent.Helpers": "j$.util.concurrent.Helpers",
         "java.util.ConversionRuntimeException": "j$.util.ConversionRuntimeException"
       },
       "rewrite_derived_prefix": {
@@ -381,6 +387,9 @@
     "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$TreeBin { int lockState; }",
     "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap { int sizeCtl; int transferIndex; long baseCount; int cellsBusy; }",
     "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$CounterCell { long value; }",
+    "-keepclassmembers class j$.util.concurrent.ConcurrentLinkedQueue { j$.util.concurrent.ConcurrentLinkedQueue$Node head; j$.util.concurrent.ConcurrentLinkedQueue$Node tail; }",
+    "-keep,allowshrinking class j$.util.concurrent.ConcurrentLinkedQueue$Node",
+    "-keepclassmembers class j$.util.concurrent.ConcurrentLinkedQueue$Node { j$.util.concurrent.ConcurrentLinkedQueue$Node next; java.lang.Object item; }",
     "-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); public static final !synthetic <fields>; }",
     "-keeppackagenames java.**",
     "-keeppackagenames j$.**",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json b/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json
index 119b674..eb7db99 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json
@@ -6,6 +6,12 @@
   "support_all_callbacks_from_library": false,
   "common_flags": [
     {
+      "api_level_below_or_equal": 32,
+      "rewrite_prefix": {
+        "java.util.concurrent.ConcurrentLinkedQueue": "j$.util.concurrent.ConcurrentLinkedQueue"
+      }
+    },
+    {
       "api_level_below_or_equal": 23,
       "maintain_prefix": [
         "java.util.function.",
@@ -14,8 +20,19 @@
     }
   ],
   "program_flags": [],
-  "library_flags": [],
+  "library_flags": [
+    {
+      "api_level_below_or_equal": 32,
+      "rewrite_prefix": {
+        "java.util.concurrent.Helpers": "j$.util.concurrent.Helpers"
+      }
+    }
+  ],
   "shrinker_config": [
+    "-keepclassmembers class j$.** extends java.io.Serializable { void <init>(); private static final java.io.ObjectStreamField[] serialPersistentFields; static final long serialVersionUID; java.lang.Object readResolve(); java.lang.Object writeReplace(); private void readObject(java.io.ObjectInputStream); private void writeObject(java.io.ObjectOutputStream); private void readObjectNoData(); }",
+    "-keepclassmembers class j$.util.concurrent.ConcurrentLinkedQueue { j$.util.concurrent.ConcurrentLinkedQueue$Node head; j$.util.concurrent.ConcurrentLinkedQueue$Node tail; }",
+    "-keep,allowshrinking class j$.util.concurrent.ConcurrentLinkedQueue$Node",
+    "-keepclassmembers class j$.util.concurrent.ConcurrentLinkedQueue$Node { j$.util.concurrent.ConcurrentLinkedQueue$Node next; java.lang.Object item; }",
     "-keeppackagenames java.**",
     "-keeppackagenames j$.**",
     "-keepattributes Signature",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index 5269d86..8a40f4c 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -34,7 +34,8 @@
         "java.net.URLDecoder": "j$.net.URLDecoder",
         "java.net.URLEncoder": "j$.net.URLEncoder",
         "java.io.DesugarInputStream": "j$.io.DesugarInputStream",
-        "java.util.concurrent.DesugarTimeUnit": "j$.util.concurrent.DesugarTimeUnit"
+        "java.util.concurrent.DesugarTimeUnit": "j$.util.concurrent.DesugarTimeUnit",
+        "java.util.concurrent.ConcurrentLinkedQueue": "j$.util.concurrent.ConcurrentLinkedQueue"
       },
       "retarget_method": {
         "java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit#of(java.time.temporal.ChronoUnit)": "java.util.concurrent.DesugarTimeUnit",
@@ -476,7 +477,8 @@
       "rewrite_prefix": {
         "desugar.": "j$.desugar.",
         "libcore.": "j$.libcore.",
-        "sun.security.action.": "j$.sun.security.action."
+        "sun.security.action.": "j$.sun.security.action.",
+        "java.util.concurrent.Helpers": "j$.util.concurrent.Helpers"
       }
     },
     {
@@ -588,6 +590,9 @@
     "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$TreeBin { int lockState; }",
     "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap { int sizeCtl; int transferIndex; long baseCount; int cellsBusy; }",
     "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$CounterCell { long value; }",
+    "-keepclassmembers class j$.util.concurrent.ConcurrentLinkedQueue { j$.util.concurrent.ConcurrentLinkedQueue$Node head; j$.util.concurrent.ConcurrentLinkedQueue$Node tail; }",
+    "-keep,allowshrinking class j$.util.concurrent.ConcurrentLinkedQueue$Node",
+    "-keepclassmembers class j$.util.concurrent.ConcurrentLinkedQueue$Node { j$.util.concurrent.ConcurrentLinkedQueue$Node next; java.lang.Object item; }",
     "-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); public static final !synthetic <fields>; }",
     "-keeppackagenames java.**",
     "-keeppackagenames j$.**",
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index aea1c45..1acbd0c 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -294,13 +294,6 @@
               appView.setNamingLens(
                   RecordRewritingNamingLens.createRecordRewritingNamingLens(appView)));
 
-      timing.time(
-          "Create MethodHandle.Lookup rewriting lens",
-          () ->
-              appView.setNamingLens(
-                  VarHandleDesugaringRewritingNamingLens
-                      .createVarHandleDesugaringRewritingNamingLens(appView)));
-
       if (options.isGeneratingDex()
           && hasDexResources
           && hasClassResources
@@ -336,6 +329,14 @@
 
       finalizeApplication(appView, executor, timing);
 
+      // Add the VarHandle naming lens after synthetic finalization.
+      timing.time(
+          "Create MethodHandle.Lookup rewriting lens",
+          () ->
+              appView.setNamingLens(
+                  VarHandleDesugaringRewritingNamingLens
+                      .createVarHandleDesugaringRewritingNamingLens(appView)));
+
       timing.end(); // post-converter
 
       if (options.isGeneratingClassFiles()) {
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 1dddf3f..56558ed 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAmender;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.naming.PrefixRewritingNamingLens;
+import com.android.tools.r8.naming.VarHandleDesugaringRewritingNamingLens;
 import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.shaking.AnnotationRemover;
@@ -102,12 +103,15 @@
             options.enableSwitchRewriting = false;
             assert options.enableStringSwitchConversion;
             options.enableStringSwitchConversion = false;
+            assert !options.enableVarHandleDesugaring;
+            options.enableVarHandleDesugaring = true;
 
             desugar(app, options, executorService);
 
             options.forceAnnotateSynthetics = false;
             options.enableSwitchRewriting = true;
             options.enableStringSwitchConversion = true;
+            options.enableVarHandleDesugaring = false;
           });
       if (shrink) {
         R8.run(r8Command, executorService);
@@ -140,6 +144,9 @@
       SyntheticFinalization.finalize(appView, timing, executor);
 
       appView.setNamingLens(PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView));
+      appView.setNamingLens(
+          VarHandleDesugaringRewritingNamingLens.createVarHandleDesugaringRewritingNamingLens(
+              appView));
       new GenericSignatureRewriter(appView).run(appView.appInfo().classes(), executor);
 
       new CfApplicationWriter(appView, options.getMarker(Tool.L8))
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index 49bfc86..311a287 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -219,6 +219,25 @@
       assert newProgramClasses != null;
       this.programClasses.clear();
       this.programClasses.addAll(newProgramClasses);
+
+      DexApplicationReadFlags.Builder builder = DexApplicationReadFlags.builder();
+      builder.setHasReadProgramClassFromDex(this.flags.hasReadProgramClassFromDex());
+      builder.setHasReadProgramClassFromCf(this.flags.hasReadProgramClassFromCf());
+      this.programClasses.forEach(
+          clazz -> {
+            DexType type = clazz.getType();
+            if (flags.getRecordWitnesses().contains(type)) {
+              builder.addRecordWitness(type);
+            }
+            if (flags.getVarHandleWitnesses().contains(type)) {
+              builder.addVarHandleWitness(type);
+            }
+            if (flags.getMethodHandlesLookupWitnesses().contains(type)) {
+              builder.addMethodHandlesLookupWitness(type);
+            }
+          });
+      this.flags = builder.build();
+
       return self();
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 4df3ab0..1ba1122 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -80,6 +80,8 @@
   public static final String varHandleDescriptorString = "Ljava/lang/invoke/VarHandle;";
   public static final String desugarMethodHandlesLookupDescriptorString =
       "Lcom/android/tools/r8/DesugarMethodHandlesLookup;";
+  public static final String methodHandlesLookupDescriptorString =
+      "Ljava/lang/invoke/MethodHandles$Lookup;";
   public static final String dalvikAnnotationOptimizationPrefixString =
       "Ldalvik/annotation/optimization/";
 
@@ -275,7 +277,7 @@
   public final DexString methodHandleDescriptor = createString("Ljava/lang/invoke/MethodHandle;");
   public final DexString methodHandlesDescriptor = createString("Ljava/lang/invoke/MethodHandles;");
   public final DexString methodHandlesLookupDescriptor =
-      createString("Ljava/lang/invoke/MethodHandles$Lookup;");
+      createString(methodHandlesLookupDescriptorString);
   public final DexString methodTypeDescriptor = createString("Ljava/lang/invoke/MethodType;");
   public final DexString invocationHandlerDescriptor =
       createString("Ljava/lang/reflect/InvocationHandler;");
@@ -2708,9 +2710,10 @@
     if (result == null) {
       result = new DexType(descriptor);
       assert result.isArrayType()
-          || result.isClassType()
-          || result.isPrimitiveType()
-          || result.isVoidType();
+              || result.isClassType()
+              || result.isPrimitiveType()
+              || result.isVoidType()
+          : descriptor.toString();
       assert !isInternalSentinel(result);
       types.put(descriptor, result);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 42497b9..dc31133 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -626,6 +626,14 @@
         int limit = 11;
         for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
           if (!clazz.type.descriptor.startsWith(neverMergePrefix)) {
+            boolean hasExceptionPrefix = false;
+            for (DexString exceptionPrefix : neverMerge.getExceptionPrefixes()) {
+              hasExceptionPrefix =
+                  hasExceptionPrefix | clazz.type.descriptor.startsWith(exceptionPrefix);
+            }
+            if (hasExceptionPrefix) {
+              continue;
+            }
             if (limit-- < 0) {
               message.append("..");
               break;
diff --git a/src/main/java/com/android/tools/r8/naming/VarHandleDesugaringRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/VarHandleDesugaringRewritingNamingLens.java
index cc9c83f..0d7078f 100644
--- a/src/main/java/com/android/tools/r8/naming/VarHandleDesugaringRewritingNamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/VarHandleDesugaringRewritingNamingLens.java
@@ -13,6 +13,8 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.naming.NamingLens.NonIdentityNamingLens;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.IdentityHashMap;
+import java.util.Map;
 
 // Naming lens for VarHandle desugaring rewriting. Rewriting java.lang.invoke.MethodHandles$Lookup
 // to com.android.tools.r8.DesugarMethodHandlesLookup.
@@ -20,26 +22,80 @@
 
   private final DexItemFactory factory;
   private final NamingLens namingLens;
+  private final Map<DexType, DexString> mapping;
 
   public static NamingLens createVarHandleDesugaringRewritingNamingLens(AppView<?> appView) {
+    DexItemFactory factory = appView.dexItemFactory();
     if (appView.options().shouldDesugarVarHandle()
-        && (appView
-                    .appInfo()
-                    .definitionForWithoutExistenceAssert(appView.dexItemFactory().lookupType)
-                != null
-            || appView
-                    .appInfo()
-                    .definitionForWithoutExistenceAssert(appView.dexItemFactory().varHandleType)
+        && (appView.appInfo().definitionForWithoutExistenceAssert(factory.lookupType) != null
+            || appView.appInfo().definitionForWithoutExistenceAssert(factory.varHandleType)
                 != null)) {
-      return new VarHandleDesugaringRewritingNamingLens(appView);
+
+      // Prune all inner classes attributes referring to MethodHandles$Lookup, as that is rewritten
+      // to the toplevel class DesugarMethodHandlesLookup.
+      appView
+          .appInfo()
+          .classes()
+          .forEach(
+              clazz -> {
+                clazz.removeInnerClasses(
+                    innerClassAttribute -> innerClassAttribute.getInner() == factory.lookupType);
+              });
+
+      // Function to prefix type namespace, e.g. rename L... to Lj$/...
+      Map<DexType, DexString> mapping = new IdentityHashMap<>();
+      addRewritingForGlobalSynthetic(
+          appView, factory.lookupType, factory.desugarMethodHandlesLookupType, mapping);
+      addRewritingForGlobalSynthetic(
+          appView, factory.varHandleType, factory.desugarVarHandleType, mapping);
+      return new VarHandleDesugaringRewritingNamingLens(appView, mapping);
     }
     return appView.getNamingLens();
   }
 
-  public VarHandleDesugaringRewritingNamingLens(AppView<?> appView) {
+  private static void addRewritingForGlobalSynthetic(
+      AppView<?> appView,
+      DexType globalSynthetic,
+      DexType desugaredGlobalSynthetic,
+      Map<DexType, DexString> mapping) {
+    DexItemFactory factory = appView.dexItemFactory();
+    // The VarHandle global synthetics and synthetics derived from them are rewritten to use the
+    // desugared name.
+    assert appView.appInfo().getSyntheticItems().isFinalized();
+    String globalSyntheticString = globalSynthetic.descriptor.toString();
+    DexString currentPrefix =
+        factory.createString(
+            globalSyntheticString.substring(0, globalSyntheticString.length() - 1));
+    String desugaredGlobalSyntheticString = desugaredGlobalSynthetic.descriptor.toString();
+    DexString newPrefix =
+        appView.options().synthesizedClassPrefix.isEmpty()
+            ? factory.createString(
+                "L"
+                    + desugaredGlobalSyntheticString.substring(
+                        1, desugaredGlobalSyntheticString.length() - 1))
+            : factory.createString(
+                "L"
+                    + appView.options().synthesizedClassPrefix
+                    + desugaredGlobalSyntheticString.substring(
+                        1, desugaredGlobalSyntheticString.length() - 1));
+    // Rewrite the global synthetic in question and all the synthetics derived from it.
+    appView
+        .appInfo()
+        .getSyntheticItems()
+        .collectSyntheticsFromContext(globalSynthetic)
+        .forEach(
+            synthetic ->
+                mapping.put(
+                    synthetic,
+                    synthetic.descriptor.withNewPrefix(currentPrefix, newPrefix, factory)));
+  }
+
+  private VarHandleDesugaringRewritingNamingLens(
+      AppView<?> appView, Map<DexType, DexString> mapping) {
     super(appView.dexItemFactory());
     this.factory = appView.dexItemFactory();
     this.namingLens = appView.getNamingLens();
+    this.mapping = mapping;
   }
 
   private boolean isRenamed(DexType type) {
@@ -49,12 +105,7 @@
   private DexString getRenaming(DexType type) {
     assert type != factory.desugarMethodHandlesLookupType;
     assert type != factory.desugarVarHandleType;
-    if (type == factory.lookupType) {
-      return factory.desugarMethodHandlesLookupType.descriptor;
-    } else if (type == factory.varHandleType) {
-      return factory.desugarVarHandleType.descriptor;
-    }
-    return null;
+    return mapping.get(type);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 4ea11dc..bf4029e 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -49,6 +49,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -206,6 +207,33 @@
   private final ContextsForGlobalSynthetics globalContexts;
   private final GlobalSyntheticsStrategy globalSyntheticsStrategy;
 
+  public Set<DexType> collectSyntheticsFromContext(DexType context) {
+    Set<DexType> result = Sets.newIdentityHashSet();
+    committed
+        .getMethods()
+        .forEach(
+            (synthetic, methodReferences) -> {
+              methodReferences.forEach(
+                  methodReference -> {
+                    if (methodReference.getContext().getSynthesizingContextType() == context) {
+                      result.add(synthetic);
+                    }
+                  });
+            });
+    committed
+        .getClasses()
+        .forEach(
+            (synthetic, classReferences) -> {
+              classReferences.forEach(
+                  classReference -> {
+                    if (classReference.getContext().getSynthesizingContextType() == context) {
+                      result.add(synthetic);
+                    }
+                  });
+            });
+    return result;
+  }
+
   public SyntheticNaming getNaming() {
     return naming;
   }
diff --git a/src/test/java/com/android/tools/r8/L8TestBuilder.java b/src/test/java/com/android/tools/r8/L8TestBuilder.java
index 7d2ea31..b299021 100644
--- a/src/test/java/com/android/tools/r8/L8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/L8TestBuilder.java
@@ -32,6 +32,7 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 public class L8TestBuilder {
 
@@ -247,6 +248,7 @@
     // in the vanilla desugared library.
     // Vanilla desugared library compilation should have no warnings.
     assertTrue(
+        warnings.stream().map(Diagnostic::getDiagnosticMessage).collect(Collectors.joining()),
         warnings.isEmpty()
             || warnings.stream()
                 .allMatch(warn -> warn.getDiagnosticMessage().contains("org.testng.Assert")));
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java
index 833e949..cb6c812 100644
--- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java
@@ -111,7 +111,7 @@
     // forwarding of Unsafe.compareAndSwapObject.
     MethodReference firstBackportFromDesugarVarHandle =
         SyntheticItemsTestUtils.syntheticBackportWithForwardingMethod(
-            Reference.classFromDescriptor("Ljava/lang/invoke/VarHandle;"),
+            Reference.classFromDescriptor("Lcom/android/tools/r8/DesugarVarHandle;"),
             0,
             Reference.method(
                 Reference.classFromDescriptor("Lsun/misc/Unsafe;"),
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentLinkedQueueTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentLinkedQueueTest.java
new file mode 100644
index 0000000..b4028c9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentLinkedQueueTest.java
@@ -0,0 +1,149 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.SPECIFICATIONS_WITH_CF2CF;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_MINIMAL;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ConcurrentLinkedQueueTest extends DesugaredLibraryTestBase {
+
+  @Parameter(0)
+  public static TestParameters parameters;
+
+  @Parameter(1)
+  public static LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+  @Parameter(2)
+  public static CompilationSpecification compilationSpecification;
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        // TODO(134732760): Support Dalvik VMs, currently fails because libjavacrypto is required
+        // and present only in ART runtimes.
+        getTestParameters()
+            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+            .withAllApiLevels()
+            .build(),
+        ImmutableList.of(JDK11_MINIMAL, JDK11, JDK11_PATH),
+        SPECIFICATIONS_WITH_CF2CF);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // Right now we only expect one backport coming out of DesugarVarHandle - the backport with
+    // forwarding of Unsafe.compareAndSwapObject.
+    MethodReference firstBackportFromDesugarVarHandle =
+        SyntheticItemsTestUtils.syntheticBackportWithForwardingMethod(
+            Reference.classFromDescriptor("Lj$/com/android/tools/r8/DesugarVarHandle;"),
+            0,
+            Reference.method(
+                Reference.classFromDescriptor("Lsun/misc/Unsafe;"),
+                "compareAndSwapObject",
+                ImmutableList.of(
+                    Reference.typeFromDescriptor("Ljava/lang/Object;"),
+                    Reference.LONG,
+                    Reference.typeFromDescriptor("Ljava/lang/Object;"),
+                    Reference.typeFromDescriptor("Ljava/lang/Object;")),
+                Reference.BOOL));
+
+    assertThat(
+        inspector.clazz(
+            DescriptorUtils.descriptorToJavaType(DexItemFactory.varHandleDescriptorString)),
+        not(isPresent()));
+    assertThat(
+        inspector.clazz(
+            DescriptorUtils.descriptorToJavaType(
+                DexItemFactory.methodHandlesLookupDescriptorString)),
+        not(isPresent()));
+    assertThat(
+        inspector.clazz(
+            "j$." + DescriptorUtils.descriptorToJavaType(DexItemFactory.varHandleDescriptorString)),
+        not(isPresent()));
+    assertThat(
+        inspector.clazz(
+            "j$."
+                + DescriptorUtils.descriptorToJavaType(
+                    DexItemFactory.methodHandlesLookupDescriptorString)),
+        not(isPresent()));
+    assertThat(
+        inspector.clazz(
+            DescriptorUtils.descriptorToJavaType(DexItemFactory.desugarVarHandleDescriptorString)),
+        not(isPresent()));
+    assertThat(
+        inspector.clazz(
+            DescriptorUtils.descriptorToJavaType(
+                DexItemFactory.desugarMethodHandlesLookupDescriptorString)),
+        not(isPresent()));
+
+    boolean usesNativeVarHandle =
+        parameters.asDexRuntime().getVersion().isNewerThanOrEqual(Version.V13_0_0)
+            && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.T);
+    assertThat(
+        inspector.clazz(
+            "j$."
+                + DescriptorUtils.descriptorToJavaType(
+                    DexItemFactory.desugarVarHandleDescriptorString)),
+        usesNativeVarHandle ? not(isPresent()) : isPresent());
+    assertThat(
+        inspector.clazz(firstBackportFromDesugarVarHandle.getHolderClass()),
+        usesNativeVarHandle ? not(isPresent()) : isPresent());
+    // Currently DesugarMethodHandlesLookup this is fully inlined by R8.
+    assertThat(
+        inspector.clazz(
+            "j$."
+                + DescriptorUtils.descriptorToJavaType(
+                    DexItemFactory.desugarMethodHandlesLookupDescriptorString)),
+        usesNativeVarHandle || compilationSpecification.isL8Shrink()
+            ? not(isPresent())
+            : isPresent());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Executor.class)
+        .compile()
+        .inspectL8(this::inspect)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      Queue<String> queue = new ConcurrentLinkedQueue<>();
+      queue.add("Hello, world!");
+      System.out.println(queue.poll());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentLinkedQueueTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentLinkedQueueTests.java
index a711203..c7d7c3a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentLinkedQueueTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentLinkedQueueTests.java
@@ -13,7 +13,11 @@
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
 import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestParameters;
@@ -24,9 +28,16 @@
 import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
 import com.android.tools.r8.desugar.desugaredlibrary.test.DesugaredLibraryTestCompileResult;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collections;
@@ -71,7 +82,7 @@
             .withApiLevel(AndroidApiLevel.N)
             .build(),
         ImmutableList.of(JDK11_MINIMAL, JDK11, JDK11_PATH),
-        ImmutableList.of(D8_L8DEBUG, D8_L8SHRINK));
+        ImmutableSet.of(D8_L8DEBUG, D8_L8SHRINK));
   }
 
   @BeforeClass
@@ -88,33 +99,81 @@
         new Path[] {jdk11MathTestsDir.resolve(WHITEBOX + CLASS_EXTENSION)};
   }
 
-  private static void ranWithSuccessOrFailures(String testName, SingleTestRunResult result) {
-    // Tests use ThreadLocalRandom, so success or failure is random. Note this is only for
-    // VMs where the internal implementation is not based on JDK11.
-    assertTrue(
-        result.getStdOut().contains(StringUtils.lines(testName + ": SUCCESS"))
-            || result
-                .getStdOut()
-                .contains(StringUtils.lines("Tests result in " + testName + ": FAILURE")));
-    if (result.getStdOut().contains(StringUtils.lines(testName + ": SUCCESS"))) {
-      assertTrue(
-          result.toString(),
-          result.getStdOut().contains("Total tests run: 37, Failures: 0, Skips: 0"));
-    } else {
-      assertTrue(
-          result.toString(),
-          result.getStdOut().contains("Total tests run: 37, Failures: 1, Skips: 0")
-              || result.getStdOut().contains("Total tests run: 37, Failures: 2, Skips: 0")
-              || result.getStdOut().contains("Total tests run: 37, Failures: 3, Skips: 0")
-              || result.getStdOut().contains("Total tests run: 37, Failures: 4, Skips: 0")
-              || result.getStdOut().contains("Total tests run: 37, Failures: 5, Skips: 0")
-              || result.getStdOut().contains("Total tests run: 37, Failures: 6, Skips: 0")
-              || result.getStdOut().contains("Total tests run: 37, Failures: 7, Skips: 0")
-              || result.getStdOut().contains("Total tests run: 37, Failures: 8, Skips: 0"));
-    }
+  private void inspect(CodeInspector inspector) {
+    // Right now we only expect one backport coming out of DesugarVarHandle - the backport with
+    // forwarding of Unsafe.compareAndSwapObject.
+    MethodReference firstBackportFromDesugarVarHandle =
+        SyntheticItemsTestUtils.syntheticBackportWithForwardingMethod(
+            Reference.classFromDescriptor("Lj$/com/android/tools/r8/DesugarVarHandle;"),
+            0,
+            Reference.method(
+                Reference.classFromDescriptor("Lsun/misc/Unsafe;"),
+                "compareAndSwapObject",
+                ImmutableList.of(
+                    Reference.typeFromDescriptor("Ljava/lang/Object;"),
+                    Reference.LONG,
+                    Reference.typeFromDescriptor("Ljava/lang/Object;"),
+                    Reference.typeFromDescriptor("Ljava/lang/Object;")),
+                Reference.BOOL));
+
+    assertThat(
+        inspector.clazz(
+            DescriptorUtils.descriptorToJavaType(DexItemFactory.varHandleDescriptorString)),
+        not(isPresent()));
+    assertThat(
+        inspector.clazz(
+            DescriptorUtils.descriptorToJavaType(
+                DexItemFactory.methodHandlesLookupDescriptorString)),
+        not(isPresent()));
+    assertThat(
+        inspector.clazz(
+            "j$." + DescriptorUtils.descriptorToJavaType(DexItemFactory.varHandleDescriptorString)),
+        not(isPresent()));
+    assertThat(
+        inspector.clazz(
+            "j$."
+                + DescriptorUtils.descriptorToJavaType(
+                    DexItemFactory.methodHandlesLookupDescriptorString)),
+        not(isPresent()));
+    assertThat(
+        inspector.clazz(
+            DescriptorUtils.descriptorToJavaType(DexItemFactory.desugarVarHandleDescriptorString)),
+        not(isPresent()));
+    assertThat(
+        inspector.clazz(
+            DescriptorUtils.descriptorToJavaType(
+                DexItemFactory.desugarMethodHandlesLookupDescriptorString)),
+        not(isPresent()));
+
+    boolean usesNativeVarHandle =
+        parameters.asDexRuntime().getVersion().isNewerThanOrEqual(Version.V13_0_0)
+            && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.T);
+    assertThat(
+        inspector.clazz(
+            "j$."
+                + DescriptorUtils.descriptorToJavaType(
+                    DexItemFactory.desugarVarHandleDescriptorString)),
+        usesNativeVarHandle ? not(isPresent()) : isPresent());
+    assertThat(
+        inspector.clazz(firstBackportFromDesugarVarHandle.getHolderClass()),
+        usesNativeVarHandle ? not(isPresent()) : isPresent());
+    // Currently DesugarMethodHandlesLookup this is fully inlined by R8.
+    assertThat(
+        inspector.clazz(
+            "j$."
+                + DescriptorUtils.descriptorToJavaType(
+                    DexItemFactory.desugarMethodHandlesLookupDescriptorString)),
+        usesNativeVarHandle || compilationSpecification.isL8Shrink()
+            ? not(isPresent())
+            : isPresent());
   }
 
   void runTest(List<String> toRun) throws Exception {
+    // Skip test with minimal configuration before API level 24, as the test use stream.
+    assumeTrue(
+        libraryDesugaringSpecification != JDK11_MINIMAL
+            || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+
     String verbosity = "2";
     DesugaredLibraryTestCompileResult<?> compileResult =
         testForDesugaredLibrary(
@@ -125,30 +184,14 @@
             // internal state of the implementation, so desugaring is needed for the program here.
             .addOptionsModification(options -> options.enableVarHandleDesugaring = true)
             .compile()
+            .inspectL8(this::inspect)
             .withArt6Plus64BitsLib();
     for (String success : toRun) {
       SingleTestRunResult<?> result =
           compileResult.run(parameters.getRuntime(), "TestNGMainRunner", verbosity, success);
-      if ((parameters.asDexRuntime().getVersion().equals(Version.V5_1_1)
-              || parameters.asDexRuntime().getVersion().equals(Version.V6_0_1))
-          && libraryDesugaringSpecification == JDK11_MINIMAL) {
-        // Some tests use streams, so which is not desugared with JDK11_MINIMAL. These tests are
-        // somehow skipped by the test runner used in the JDK11 tests.
-        assertTrue(result.getStdOut().contains("Total tests run: 9, Failures: 0, Skips: 7"));
-        assertTrue(result.getStdOut().contains(StringUtils.lines(success + ": SUCCESS")));
-      } else if (parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V12_0_0)) {
-        ranWithSuccessOrFailures(success, result);
-      } else {
-        assertTrue(parameters.asDexRuntime().getVersion().isNewerThanOrEqual(Version.V13_0_0));
-        if (parameters.getApiLevel() == AndroidApiLevel.B) {
-          ranWithSuccessOrFailures(success, result);
-        } else {
-          // No desugaring and JDK11 based runtime implementation.
-          assertTrue(
-              "Failure in " + success + "\n" + result,
-              result.getStdOut().contains(StringUtils.lines(success + ": SUCCESS")));
-        }
-      }
+      assertTrue(
+          "Failure in " + success + "\n" + result,
+          result.getStdOut().contains(StringUtils.lines(success + ": SUCCESS")));
     }
   }