Preserve suppressed exception behavior on VMs with native support.

Bug: 195500117
Change-Id: Ie3f4a350f289ad678e25e6a243ba77b9fd23da0c
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 7005bba..282f4f1 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
@@ -162,7 +162,7 @@
 
     @Override
     public void acceptTwrCloseResourceMethod(ProgramMethod closeMethod, ProgramMethod context) {
-      methodProcessor.scheduleDesugaredMethodForProcessing(closeMethod);
+      methodProcessor.scheduleMethodForProcessing(closeMethod, this);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index d7f7109..b934c40 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.cf.code.CfArrayStore;
 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.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
@@ -109,6 +110,7 @@
     factory.createSynthesizedType("[Ljava/lang/CharSequence;");
     factory.createSynthesizedType("[Ljava/lang/Class;");
     factory.createSynthesizedType("[Ljava/lang/Object;");
+    factory.createSynthesizedType("[Ljava/lang/Throwable;");
     factory.createSynthesizedType("[Ljava/util/Map$Entry;");
   }
 
@@ -511,10 +513,14 @@
     CfLabel label15 = new CfLabel();
     CfLabel label16 = new CfLabel();
     CfLabel label17 = new CfLabel();
+    CfLabel label18 = new CfLabel();
+    CfLabel label19 = new CfLabel();
+    CfLabel label20 = new CfLabel();
+    CfLabel label21 = new CfLabel();
     return new CfCode(
         method.holder,
+        6,
         4,
-        3,
         ImmutableList.of(
             label0,
             new CfLoad(ValueType.OBJECT, 1),
@@ -749,7 +755,7 @@
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
                 new ArrayDeque<>(Arrays.asList())),
-            new CfGoto(label16),
+            new CfGoto(label20),
             label12,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
@@ -763,10 +769,76 @@
             new CfStore(ValueType.OBJECT, 2),
             label13,
             new CfLoad(ValueType.OBJECT, 0),
-            new CfIf(If.Type.EQ, ValueType.OBJECT, label14),
-            new CfLoad(ValueType.OBJECT, 0),
-            new CfGoto(label15),
+            new CfIf(If.Type.EQ, ValueType.OBJECT, label19),
             label14,
+            new CfConstClass(options.itemFactory.throwableType),
+            new CfConstString(options.itemFactory.createString("addSuppressed")),
+            new CfConstNumber(1, ValueType.INT),
+            new CfNewArray(options.itemFactory.createType("[Ljava/lang/Class;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfConstNumber(0, ValueType.INT),
+            new CfConstClass(options.itemFactory.throwableType),
+            new CfArrayStore(MemberType.OBJECT),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.classType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Ljava/lang/reflect/Method;"),
+                        options.itemFactory.stringType,
+                        options.itemFactory.createType("[Ljava/lang/Class;")),
+                    options.itemFactory.createString("getDeclaredMethod")),
+                false),
+            new CfStore(ValueType.OBJECT, 3),
+            label15,
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfConstNumber(1, ValueType.INT),
+            new CfNewArray(options.itemFactory.createType("[Ljava/lang/Object;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfConstNumber(0, ValueType.INT),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfArrayStore(MemberType.OBJECT),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/reflect/Method;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.objectType,
+                        options.itemFactory.objectType,
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
+                    options.itemFactory.createString("invoke")),
+                false),
+            new CfStackInstruction(CfStackInstruction.Opcode.Pop),
+            label16,
+            new CfGoto(label18),
+            label17,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initialized(options.itemFactory.throwableType),
+                      FrameType.initialized(options.itemFactory.objectType),
+                      FrameType.initialized(options.itemFactory.throwableType)
+                    }),
+                new ArrayDeque<>(
+                    Arrays.asList(
+                        FrameType.initialized(
+                            options.itemFactory.createType("Ljava/lang/Exception;"))))),
+            new CfStore(ValueType.OBJECT, 3),
+            label18,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initialized(options.itemFactory.throwableType),
+                      FrameType.initialized(options.itemFactory.objectType),
+                      FrameType.initialized(options.itemFactory.throwableType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfThrow(),
+            label19,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0, 1, 2},
@@ -777,19 +849,8 @@
                     }),
                 new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 2),
-            label15,
-            new CfFrame(
-                new Int2ReferenceAVLTreeMap<>(
-                    new int[] {0, 1, 2},
-                    new FrameType[] {
-                      FrameType.initialized(options.itemFactory.throwableType),
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.throwableType)
-                    }),
-                new ArrayDeque<>(
-                    Arrays.asList(FrameType.initialized(options.itemFactory.throwableType)))),
             new CfThrow(),
-            label16,
+            label20,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0, 1},
@@ -799,7 +860,7 @@
                     }),
                 new ArrayDeque<>(Arrays.asList())),
             new CfReturnVoid(),
-            label17),
+            label21),
         ImmutableList.of(
             new CfTryCatch(
                 label2,
@@ -841,7 +902,12 @@
                 label0,
                 label11,
                 ImmutableList.of(options.itemFactory.throwableType),
-                ImmutableList.of(label12))),
+                ImmutableList.of(label12)),
+            new CfTryCatch(
+                label14,
+                label16,
+                ImmutableList.of(options.itemFactory.createType("Ljava/lang/Exception;")),
+                ImmutableList.of(label17))),
         ImmutableList.of());
   }
 
@@ -9079,6 +9145,163 @@
         ImmutableList.of());
   }
 
+  public static CfCode ThrowableMethods_addSuppressed(InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        6,
+        3,
+        ImmutableList.of(
+            label0,
+            new CfConstClass(options.itemFactory.throwableType),
+            new CfConstString(options.itemFactory.createString("addSuppressed")),
+            new CfConstNumber(1, ValueType.INT),
+            new CfNewArray(options.itemFactory.createType("[Ljava/lang/Class;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfConstNumber(0, ValueType.INT),
+            new CfConstClass(options.itemFactory.throwableType),
+            new CfArrayStore(MemberType.OBJECT),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.classType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Ljava/lang/reflect/Method;"),
+                        options.itemFactory.stringType,
+                        options.itemFactory.createType("[Ljava/lang/Class;")),
+                    options.itemFactory.createString("getDeclaredMethod")),
+                false),
+            new CfStore(ValueType.OBJECT, 2),
+            label1,
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfConstNumber(1, ValueType.INT),
+            new CfNewArray(options.itemFactory.createType("[Ljava/lang/Object;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfConstNumber(0, ValueType.INT),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfArrayStore(MemberType.OBJECT),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/reflect/Method;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.objectType,
+                        options.itemFactory.objectType,
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
+                    options.itemFactory.createString("invoke")),
+                false),
+            new CfStackInstruction(CfStackInstruction.Opcode.Pop),
+            label2,
+            new CfGoto(label4),
+            label3,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initialized(options.itemFactory.throwableType),
+                      FrameType.initialized(options.itemFactory.throwableType)
+                    }),
+                new ArrayDeque<>(
+                    Arrays.asList(
+                        FrameType.initialized(
+                            options.itemFactory.createType("Ljava/lang/Exception;"))))),
+            new CfStore(ValueType.OBJECT, 2),
+            label4,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initialized(options.itemFactory.throwableType),
+                      FrameType.initialized(options.itemFactory.throwableType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfReturnVoid(),
+            label5),
+        ImmutableList.of(
+            new CfTryCatch(
+                label0,
+                label2,
+                ImmutableList.of(options.itemFactory.createType("Ljava/lang/Exception;")),
+                ImmutableList.of(label3))),
+        ImmutableList.of());
+  }
+
+  public static CfCode ThrowableMethods_getSuppressed(InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        3,
+        2,
+        ImmutableList.of(
+            label0,
+            new CfConstClass(options.itemFactory.throwableType),
+            new CfConstString(options.itemFactory.createString("getSuppressed")),
+            new CfConstNumber(0, ValueType.INT),
+            new CfNewArray(options.itemFactory.createType("[Ljava/lang/Class;")),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.classType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Ljava/lang/reflect/Method;"),
+                        options.itemFactory.stringType,
+                        options.itemFactory.createType("[Ljava/lang/Class;")),
+                    options.itemFactory.createString("getDeclaredMethod")),
+                false),
+            new CfStore(ValueType.OBJECT, 1),
+            label1,
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfConstNumber(0, ValueType.INT),
+            new CfNewArray(options.itemFactory.createType("[Ljava/lang/Object;")),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/reflect/Method;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.objectType,
+                        options.itemFactory.objectType,
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
+                    options.itemFactory.createString("invoke")),
+                false),
+            new CfCheckCast(options.itemFactory.createType("[Ljava/lang/Throwable;")),
+            label2,
+            new CfReturn(ValueType.OBJECT),
+            label3,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0},
+                    new FrameType[] {FrameType.initialized(options.itemFactory.throwableType)}),
+                new ArrayDeque<>(
+                    Arrays.asList(
+                        FrameType.initialized(
+                            options.itemFactory.createType("Ljava/lang/Exception;"))))),
+            new CfStore(ValueType.OBJECT, 1),
+            label4,
+            new CfConstNumber(0, ValueType.INT),
+            new CfNewArray(options.itemFactory.createType("[Ljava/lang/Throwable;")),
+            new CfReturn(ValueType.OBJECT),
+            label5),
+        ImmutableList.of(
+            new CfTryCatch(
+                label0,
+                label2,
+                ImmutableList.of(options.itemFactory.createType("Ljava/lang/Exception;")),
+                ImmutableList.of(label3))),
+        ImmutableList.of());
+  }
+
   public static CfCode UnsafeMethods_compareAndSwapObject(
       InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java
index 716b6e2..1b5b27c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java
@@ -3,15 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.desugar.twr;
 
-import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfNewArray;
-import com.android.tools.r8.cf.code.CfStackInstruction;
-import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -19,7 +16,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
@@ -27,9 +23,11 @@
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
-import org.jetbrains.annotations.NotNull;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
 import org.objectweb.asm.Opcodes;
 
 public class TwrInstructionDesugaring implements CfInstructionDesugaring {
@@ -66,58 +64,85 @@
     if (isTwrCloseResourceInvoke(instruction)) {
       return rewriteTwrCloseResourceInvoke(eventConsumer, context, methodProcessingContext);
     }
-    if (isTwrSuppressedInvoke(instruction, addSuppressed)) {
-      return rewriteTwrAddSuppressedInvoke();
-    }
-    if (isTwrSuppressedInvoke(instruction, getSuppressed)) {
-      return rewriteTwrGetSuppressedInvoke();
+    if (!appView.options().canUseSuppressedExceptions()) {
+      if (isTwrSuppressedInvoke(instruction, addSuppressed)) {
+        return rewriteTwrAddSuppressedInvoke(eventConsumer, methodProcessingContext);
+      }
+      if (isTwrSuppressedInvoke(instruction, getSuppressed)) {
+        return rewriteTwrGetSuppressedInvoke(eventConsumer, methodProcessingContext);
+      }
     }
     return null;
   }
 
-  private Collection<CfInstruction> rewriteTwrAddSuppressedInvoke() {
-    // Remove Throwable::addSuppressed(Throwable) call.
-    return ImmutableList.of(new CfStackInstruction(Opcode.Pop), new CfStackInstruction(Opcode.Pop));
+  private Collection<CfInstruction> rewriteTwrAddSuppressedInvoke(
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
+    DexItemFactory factory = appView.dexItemFactory();
+    DexProto proto =
+        factory.createProto(factory.voidType, factory.throwableType, factory.throwableType);
+    return createAndCallSyntheticMethod(
+        SyntheticKind.BACKPORT,
+        proto,
+        BackportedMethods::ThrowableMethods_addSuppressed,
+        methodProcessingContext,
+        eventConsumer::acceptBackportedMethod,
+        methodProcessingContext.getMethodContext());
   }
 
-  private Collection<CfInstruction> rewriteTwrGetSuppressedInvoke() {
-    // Replace call to Throwable::getSuppressed() with new Throwable[0].
-    return ImmutableList.of(
-        new CfStackInstruction(Opcode.Pop),
-        new CfConstNumber(0, ValueType.INT),
-        new CfNewArray(dexItemFactory.createArrayType(1, dexItemFactory.throwableType)));
+  private Collection<CfInstruction> rewriteTwrGetSuppressedInvoke(
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
+    DexItemFactory factory = appView.dexItemFactory();
+    DexProto proto =
+        factory.createProto(
+            factory.createArrayType(1, factory.throwableType), factory.throwableType);
+    return createAndCallSyntheticMethod(
+        SyntheticKind.BACKPORT,
+        proto,
+        BackportedMethods::ThrowableMethods_getSuppressed,
+        methodProcessingContext,
+        eventConsumer::acceptBackportedMethod,
+        methodProcessingContext.getMethodContext());
   }
 
-  @NotNull
   private ImmutableList<CfInstruction> rewriteTwrCloseResourceInvoke(
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext) {
     // Synthesize a new method.
-    ProgramMethod closeMethod = createSyntheticCloseResourceMethod(methodProcessingContext);
-    eventConsumer.acceptTwrCloseResourceMethod(closeMethod, context);
-    // Rewrite the invoke to the new synthetic.
-    return ImmutableList.of(new CfInvoke(Opcodes.INVOKESTATIC, closeMethod.getReference(), false));
+    return createAndCallSyntheticMethod(
+        SyntheticKind.TWR_CLOSE_RESOURCE,
+        twrCloseResourceProto,
+        BackportedMethods::CloseResourceMethod_closeResourceImpl,
+        methodProcessingContext,
+        eventConsumer::acceptTwrCloseResourceMethod,
+        context);
   }
 
-  private ProgramMethod createSyntheticCloseResourceMethod(
-      MethodProcessingContext methodProcessingContext) {
-    return appView
-        .getSyntheticItems()
-        .createMethod(
-            SyntheticKind.TWR_CLOSE_RESOURCE,
-            methodProcessingContext.createUniqueContext(),
-            appView,
-            methodBuilder ->
-                methodBuilder
-                    .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
-                    .setProto(twrCloseResourceProto)
-                    // Will be traced by the enqueuer.
-                    .disableAndroidApiLevelCheck()
-                    .setCode(
-                        m ->
-                            BackportedMethods.CloseResourceMethod_closeResourceImpl(
-                                appView.options(), m)));
+  private ImmutableList<CfInstruction> createAndCallSyntheticMethod(
+      SyntheticKind kind,
+      DexProto proto,
+      BiFunction<InternalOptions, DexMethod, CfCode> generator,
+      MethodProcessingContext methodProcessingContext,
+      BiConsumer<ProgramMethod, ProgramMethod> eventConsumerCallback,
+      ProgramMethod context) {
+    ProgramMethod method =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                kind,
+                methodProcessingContext.createUniqueContext(),
+                appView,
+                builder ->
+                    builder
+                        // Will be traced by the enqueuer.
+                        .disableAndroidApiLevelCheck()
+                        .setProto(proto)
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        .setCode(methodSig -> generator.apply(appView.options(), methodSig)));
+    eventConsumerCallback.accept(method, context);
+    return ImmutableList.of(new CfInvoke(Opcodes.INVOKESTATIC, method.getReference(), false));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 1541441..f3dcf92 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1920,6 +1920,7 @@
   }
 
   public boolean canUseSuppressedExceptions() {
+    // TODO(b/214239152): Suppressed exceptions are @hide from at least 4.0.1 / Android I / API 14.
     return !isDesugaring() || hasMinApi(AndroidApiLevel.K);
   }
 
diff --git a/src/test/examplesAndroidO/trywithresources/TryWithResources.java b/src/test/examplesAndroidO/trywithresources/TryWithResources.java
index ac779d6..2c407cf 100644
--- a/src/test/examplesAndroidO/trywithresources/TryWithResources.java
+++ b/src/test/examplesAndroidO/trywithresources/TryWithResources.java
@@ -36,20 +36,12 @@
       dumpException(cause, indent + "  cause: ");
     }
 
-    // Dump suppressed UNLESS it is a desugared code running
-    // on JVM, in which case we avoid dumping suppressed, since
-    // the output will be used for comparison with desugared code
-    // running on device.
-    if (!desugaredCodeRunningOnJvm()) {
-      Throwable[] suppressed = e.getSuppressed();
-      for (int i = 0; i < suppressed.length; i++) {
-        dumpException(suppressed[i], indent + "supp[" + i + "]: ");
-      }
+    Throwable[] suppressed = e.getSuppressed();
+    for (int i = 0; i < suppressed.length; i++) {
+      dumpException(suppressed[i], indent + "supp[" + i + "]: ");
     }
   }
 
-  abstract boolean desugaredCodeRunningOnJvm();
-
   // --- TEST SYMBOLS ---
 
   static class Resource implements Closeable {
@@ -190,9 +182,7 @@
       packer.act(new RuntimeException("original exception Z"));
 
       for (Throwable unpacked : unpacker.get()) {
-        if (!desugaredCodeRunningOnJvm()) {
-          dumpException(unpacked);
-        }
+        dumpException(unpacked);
       }
     }
   }
diff --git a/src/test/examplesAndroidO/trywithresources/TryWithResourcesDesugaredTests.java b/src/test/examplesAndroidO/trywithresources/TryWithResourcesDesugaredTests.java
index 10a96f9..65e7221 100644
--- a/src/test/examplesAndroidO/trywithresources/TryWithResourcesDesugaredTests.java
+++ b/src/test/examplesAndroidO/trywithresources/TryWithResourcesDesugaredTests.java
@@ -4,19 +4,6 @@
 package trywithresources;
 
 public class TryWithResourcesDesugaredTests extends TryWithResources {
-  private boolean isAndroid() {
-    try {
-      Class.forName("dalvik.system.VMRuntime");
-      return true;
-    } catch (Exception ignored) {
-    }
-    return false;
-  }
-
-  @Override
-  boolean desugaredCodeRunningOnJvm() {
-    return !isAndroid();
-  }
 
   public static void main(String[] args) throws Exception {
     new TryWithResourcesDesugaredTests().test();
diff --git a/src/test/examplesAndroidO/trywithresources/TryWithResourcesNotDesugaredTests.java b/src/test/examplesAndroidO/trywithresources/TryWithResourcesNotDesugaredTests.java
index d2a05c3..8180fb1 100644
--- a/src/test/examplesAndroidO/trywithresources/TryWithResourcesNotDesugaredTests.java
+++ b/src/test/examplesAndroidO/trywithresources/TryWithResourcesNotDesugaredTests.java
@@ -4,10 +4,6 @@
 package trywithresources;
 
 public class TryWithResourcesNotDesugaredTests extends TryWithResources {
-  @Override
-  boolean desugaredCodeRunningOnJvm() {
-    return false;
-  }
 
   public static void main(String[] args) throws Exception {
     new TryWithResourcesNotDesugaredTests().test();
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index dfebfd7..6006f32 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -99,6 +99,7 @@
           Assert.assertTrue(
               descriptor.endsWith(getCompanionClassNameSuffix() + ";")
                   || SyntheticItemsTestUtils.isExternalTwrCloseMethod(reference)
+                  || SyntheticItemsTestUtils.isMaybeExternalSuppressedExceptionMethod(reference)
                   || SyntheticItemsTestUtils.isExternalLambda(reference)
                   || SyntheticItemsTestUtils.isExternalStaticInterfaceCall(reference)
                   || descriptor.equals(mainClassDescriptor));
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 21113a9..d72151f 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1788,6 +1788,10 @@
     return AndroidApiLevel.K;
   }
 
+  public static AndroidApiLevel apiLevelWithSuppressedExceptionsSupport() {
+    return AndroidApiLevel.K;
+  }
+
   public static AndroidApiLevel apiLevelWithPcAsLineNumberSupport() {
     return AndroidApiLevel.O;
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/suppressedexceptions/SuppressedExceptionsTest.java b/src/test/java/com/android/tools/r8/desugar/suppressedexceptions/SuppressedExceptionsTest.java
new file mode 100644
index 0000000..e1d397a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/suppressedexceptions/SuppressedExceptionsTest.java
@@ -0,0 +1,143 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.suppressedexceptions;
+
+import static com.android.tools.r8.desugar.suppressedexceptions.TwrSuppressedExceptionsTest.getInvokesTo;
+import static com.android.tools.r8.desugar.suppressedexceptions.TwrSuppressedExceptionsTest.hasInvokesTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SuppressedExceptionsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public SuppressedExceptionsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public boolean runtimeHasSuppressedExceptionsSupport() {
+    // TODO(b/214239152): Update this if desugaring is changed.
+    // Despite 4.0.4 being API level 15 and add suppressed being officially added in 19 it is
+    // actually implemented. Thus, the backport implementation will use the functionality and run
+    // as expected by RI.
+    return parameters.isCfRuntime()
+        || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V4_0_4);
+  }
+
+  public boolean apiLevelHasSuppressedExceptionsSupport() {
+    return parameters
+        .getApiLevel()
+        .isGreaterThanOrEqualTo(apiLevelWithSuppressedExceptionsSupport());
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramClasses(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(
+            runtimeHasSuppressedExceptionsSupport() ? StringUtils.lines("FOO") : "NONE")
+        .inspectIf(
+            DesugarTestConfiguration::isDesugared,
+            inspector ->
+                hasInvokesTo(
+                    inspector.clazz(TestClass.class).uniqueMethodWithName("main"),
+                    "getSuppressed",
+                    apiLevelHasSuppressedExceptionsSupport() ? 1 : 0))
+        .inspectIf(
+            DesugarTestConfiguration::isNotDesugared,
+            inspector ->
+                hasInvokesTo(
+                    inspector.clazz(TestClass.class).uniqueMethodWithName("main"),
+                    "getSuppressed",
+                    1));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeTrue(
+        "R8 does not desugar CF so only run the high API variant.",
+        parameters.isDexRuntime() || parameters.getApiLevel().isGreaterThan(AndroidApiLevel.B));
+    testForR8(parameters.getBackend())
+        .addInnerClasses(SuppressedExceptionsTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(
+            runtimeHasSuppressedExceptionsSupport() ? StringUtils.lines("FOO") : "NONE")
+        .inspect(
+            inspector -> {
+              hasInvokesTo(
+                  inspector.clazz(TestClass.class).uniqueMethodWithName("main"),
+                  "getSuppressed",
+                  apiLevelHasSuppressedExceptionsSupport() ? 1 : 0);
+              IntBox gets = new IntBox(0);
+              IntBox adds = new IntBox(0);
+              inspector.forAllClasses(
+                  c ->
+                      c.forAllMethods(
+                          m -> {
+                            gets.increment(getInvokesTo(m, "getSuppressed").size());
+                            adds.increment(getInvokesTo(m, "addSuppressed").size());
+                          }));
+              if (apiLevelHasSuppressedExceptionsSupport()) {
+                assertEquals(1, gets.get());
+                assertEquals(1, adds.get());
+              } else {
+                assertEquals(0, gets.get());
+                assertEquals(0, adds.get());
+              }
+            });
+  }
+
+  static class TestClass {
+
+    public static void foo() {
+      throw new RuntimeException("FOO");
+    }
+
+    public static void bar() {
+      try {
+        foo();
+      } catch (RuntimeException e) {
+        RuntimeException bar = new RuntimeException("BAR");
+        bar.addSuppressed(e);
+        throw bar;
+      }
+    }
+
+    public static void main(String[] args) {
+      try {
+        bar();
+      } catch (RuntimeException e) {
+        Throwable[] suppressed = e.getSuppressed();
+        if (suppressed.length == 0) {
+          System.out.println("NONE");
+        } else {
+          for (Throwable throwable : suppressed) {
+            System.out.println(throwable.getMessage());
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/suppressedexceptions/TwrSuppressedExceptionsTest.java b/src/test/java/com/android/tools/r8/desugar/suppressedexceptions/TwrSuppressedExceptionsTest.java
new file mode 100644
index 0000000..e80af63
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/suppressedexceptions/TwrSuppressedExceptionsTest.java
@@ -0,0 +1,188 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.suppressedexceptions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.Closeable;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class TwrSuppressedExceptionsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public TwrSuppressedExceptionsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public boolean runtimeHasSuppressedExceptionsSupport() {
+    // TODO(b/214239152): Update this if desugaring is changed.
+    // Despite 4.0.4 being API level 15 and add suppressed being officially added in 19 it is
+    // actually implemented. Thus, the backport implementation will use the functionality and run
+    // as expected by RI.
+    return parameters.isCfRuntime()
+        || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V4_0_4);
+  }
+
+  public boolean apiLevelHasSuppressedExceptionsSupport() {
+    return parameters
+        .getApiLevel()
+        .isGreaterThanOrEqualTo(apiLevelWithSuppressedExceptionsSupport());
+  }
+
+  public boolean apiLevelHasTwrCloseResourceSupport() {
+    return parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithTwrCloseResourceSupport());
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramClasses(TestClass.class, MyClosable.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(
+            runtimeHasSuppressedExceptionsSupport() ? StringUtils.lines("CLOSE") : "NONE")
+        .inspectIf(
+            DesugarTestConfiguration::isDesugared,
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(TestClass.class);
+              hasInvokesTo(
+                  clazz.uniqueMethodWithName("bar"),
+                  "$closeResource",
+                  apiLevelHasTwrCloseResourceSupport() ? 4 : 0);
+              if (apiLevelHasSuppressedExceptionsSupport()) {
+                hasInvokesTo(clazz.mainMethod(), "getSuppressed", 1);
+              } else {
+                inspector.forAllClasses(
+                    c ->
+                        c.forAllMethods(
+                            m -> {
+                              hasInvokesTo(m, "getSuppressed", 0);
+                              hasInvokesTo(m, "addSuppressed", 0);
+                            }));
+              }
+            })
+        .inspectIf(
+            DesugarTestConfiguration::isNotDesugared,
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(TestClass.class);
+              hasInvokesTo(clazz.uniqueMethodWithName("bar"), "$closeResource", 4);
+              hasInvokesTo(clazz.mainMethod(), "getSuppressed", 1);
+            });
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeTrue(
+        "R8 does not desugar CF so only run the high API variant.",
+        parameters.isDexRuntime() || parameters.getApiLevel().isGreaterThan(AndroidApiLevel.B));
+    testForR8(parameters.getBackend())
+        .addInnerClasses(TwrSuppressedExceptionsTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        // TODO(b/214250388): Don't warn about AutoClosable in synthesized code.
+        .apply(
+            b -> {
+              if (!parameters.isCfRuntime() && !apiLevelHasTwrCloseResourceSupport()) {
+                b.addDontWarn(AutoCloseable.class);
+              }
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(
+            runtimeHasSuppressedExceptionsSupport() ? StringUtils.lines("CLOSE") : "NONE")
+        .inspect(
+            inspector -> {
+              IntBox gets = new IntBox(0);
+              IntBox adds = new IntBox(0);
+              inspector.forAllClasses(
+                  c ->
+                      c.forAllMethods(
+                          m -> {
+                            gets.increment(getInvokesTo(m, "getSuppressed").size());
+                            adds.increment(getInvokesTo(m, "addSuppressed").size());
+                          }));
+              if (apiLevelHasSuppressedExceptionsSupport()) {
+                hasInvokesTo(inspector.clazz(TestClass.class).mainMethod(), "getSuppressed", 1);
+                assertEquals(1, gets.get());
+                assertEquals(1, adds.get());
+              } else {
+                assertEquals(0, gets.get());
+                assertEquals(0, adds.get());
+              }
+            });
+  }
+
+  public static void hasInvokesTo(MethodSubject method, String callee, int count) {
+    List<InstructionSubject> getSuppressedCalls = getInvokesTo(method, callee);
+    assertEquals(count, getSuppressedCalls.size());
+  }
+
+  public static List<InstructionSubject> getInvokesTo(MethodSubject method, String callee) {
+    return method
+        .streamInstructions()
+        .filter(i -> i.isInvoke() && i.getMethod().getName().toString().equals(callee))
+        .collect(Collectors.toList());
+  }
+
+  static class MyClosable implements Closeable {
+
+    @Override
+    public void close() {
+      throw new RuntimeException("CLOSE");
+    }
+  }
+
+  static class TestClass {
+
+    public static void foo() {
+      throw new RuntimeException("FOO");
+    }
+
+    public static void bar() {
+      // Use twr twice to have javac generate a shared $closeResource helper.
+      try (MyClosable closable = new MyClosable()) {
+        foo();
+      }
+      try (MyClosable closable = new MyClosable()) {
+        foo();
+      }
+    }
+
+    public static void main(String[] args) {
+      try {
+        bar();
+      } catch (Exception e) {
+        Throwable[] suppressed = e.getSuppressed();
+        if (suppressed.length == 0) {
+          System.out.println("NONE");
+        } else {
+          for (Throwable throwable : suppressed) {
+            System.out.println(throwable.getMessage());
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
index 1f15b61..10d15e9 100644
--- a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
@@ -75,10 +75,14 @@
         .assertSuccessWithOutput(EXPECTED)
         .inspect(
             inspector -> {
-              // There should be exactly one synthetic class besides the three program classes.
+              // There should be two synthetic classes besides the three program classes.
+              // One for the desugar version of TWR $closeResource and one for the
+              // Throwable.addSuppressed that is still present in the original $closeResource.
+              // TODO(b/214329923): If the original $closeResource is pruned this will decrease.
+              // TODO(b/168568827): Once we support a nested addSuppressed this will increase.
               int expectedSynthetics =
                   parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport())
-                      ? 1
+                      ? 2
                       : 0;
               assertEquals(INPUT_CLASSES + expectedSynthetics, inspector.allClasses().size());
             });
@@ -91,6 +95,7 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
         .addKeepClassAndMembersRules(Foo.class, Bar.class)
+        // TODO(b/214250388): Don't warn about synthetic code.
         .applyIf(
             parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport()),
             builder -> builder.addDontWarn("java.lang.AutoCloseable"))
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/CloseResourceMethod.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/CloseResourceMethod.java
index 1167e5a..fb6a937 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/CloseResourceMethod.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/CloseResourceMethod.java
@@ -49,9 +49,18 @@
         }
       }
     } catch (Throwable e) {
-      // NOTE: we don't call addSuppressed(...) since the call will be removed
-      // by try-with-resource desugar anyways.
-      throw throwable != null ? throwable : e;
+      if (throwable != null) {
+        // TODO(b/168568827): Directly call Throwable.addSuppressed once fixed.
+        try {
+          Method method = Throwable.class.getDeclaredMethod("addSuppressed", Throwable.class);
+          method.invoke(throwable, e);
+        } catch (Exception ignore) {
+          // Don't add anything when not natively supported.
+        }
+        throw throwable;
+      } else {
+        throw e;
+      }
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index ce75bd9..f955ca3 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -51,6 +51,7 @@
           ShortMethods.class,
           StreamMethods.class,
           StringMethods.class,
+          ThrowableMethods.class,
           UnsafeMethods.class);
 
   protected final TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/ThrowableMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/ThrowableMethods.java
new file mode 100644
index 0000000..ba17959
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/ThrowableMethods.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.backports;
+
+import java.lang.reflect.Method;
+
+public final class ThrowableMethods {
+
+  public static void addSuppressed(Throwable receiver, Throwable suppressed) {
+    try {
+      Method method = Throwable.class.getDeclaredMethod("addSuppressed", Throwable.class);
+      method.invoke(receiver, suppressed);
+    } catch (Exception e) {
+      // Don't add anything when not natively supported.
+    }
+  }
+
+  public static Throwable[] getSuppressed(Throwable receiver) {
+    try {
+      Method method = Throwable.class.getDeclaredMethod("getSuppressed");
+      return (Throwable[]) method.invoke(receiver);
+    } catch (Exception e) {
+      // Don't return any when not natively supported.
+      return new Throwable[0];
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index 6264a4b..bb46834 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -109,6 +109,11 @@
     return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.TWR_CLOSE_RESOURCE);
   }
 
+  public static boolean isMaybeExternalSuppressedExceptionMethod(ClassReference reference) {
+    // The suppressed exception methods are grouped with the backports.
+    return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.BACKPORT);
+  }
+
   public static boolean isExternalOutlineClass(ClassReference reference) {
     return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.OUTLINE);
   }