Hygienic desugaring of try-with-resource $closeResource.

Bug: 158159959
Change-Id: I0153bad42811d5ba2b051a725fa00bf6d95e5e60
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index cab5904..c4daac1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.horizontalclassmerging.SyntheticArgumentClass;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
-import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.SyntheticNaming;
@@ -41,7 +40,11 @@
   // Bundletool is merging classes that may originate from a build with an old version of R8.
   // Allow merging of classes that use names from older versions of R8.
   private static List<String> OLD_SYNTHESIZED_NAMES =
-      ImmutableList.of("$r8$backportedMethods$utility", "$r8$java8methods$utility", "-$$Lambda$");
+      ImmutableList.of(
+          "$r8$backportedMethods$utility",
+          "$r8$java8methods$utility",
+          "$r8$twr$utility",
+          "-$$Lambda$");
 
   public final DexString descriptor;
   private String toStringCache = null;
@@ -333,7 +336,6 @@
         || name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX) // Could collide.
         || name.contains(DISPATCH_CLASS_NAME_SUFFIX) // Shared on reference.
         || name.contains(OutlineOptions.CLASS_NAME) // Global singleton.
-        || name.contains(TwrCloseResourceRewriter.UTILITY_CLASS_NAME) // Global singleton.
         || name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME) // Global singleton.
         || name.contains(ServiceLoaderRewriter.SERVICE_LOADER_CLASS_NAME); // Global singleton.
   }
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 bc20e17..d88d48e 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
@@ -240,7 +240,9 @@
           new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
       this.backportedMethodRewriter = new BackportedMethodRewriter(appView);
       this.twrCloseResourceRewriter =
-          enableTwrCloseResourceDesugaring() ? new TwrCloseResourceRewriter(appView, this) : null;
+          TwrCloseResourceRewriter.enableTwrCloseResourceDesugaring(appView.options())
+              ? new TwrCloseResourceRewriter(appView)
+              : null;
       this.d8NestBasedAccessDesugaring =
           options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
       this.lambdaMerger = null;
@@ -274,8 +276,9 @@
             ? new InterfaceMethodRewriter(appView, this)
             : null;
     this.twrCloseResourceRewriter =
-        (options.desugarState == DesugarState.ON && enableTwrCloseResourceDesugaring())
-            ? new TwrCloseResourceRewriter(appView, this)
+        (TwrCloseResourceRewriter.enableTwrCloseResourceDesugaring(options)
+                && !appView.enableWholeProgramOptimizations())
+            ? new TwrCloseResourceRewriter(appView)
             : null;
     this.backportedMethodRewriter =
         (options.desugarState == DesugarState.ON && !appView.enableWholeProgramOptimizations())
@@ -382,20 +385,6 @@
     this(AppView.createForD8(appInfo), timing, printer, MainDexTracingResult.NONE);
   }
 
-  private boolean enableTwrCloseResourceDesugaring() {
-    return enableTryWithResourcesDesugaring() && !options.canUseTwrCloseResourceMethod();
-  }
-
-  private boolean enableTryWithResourcesDesugaring() {
-    switch (options.tryWithResourcesDesugaring) {
-      case Off:
-        return false;
-      case Auto:
-        return !options.canUseSuppressedExceptions();
-    }
-    throw new Unreachable();
-  }
-
   private void removeLambdaDeserializationMethods() {
     if (lambdaRewriter != null) {
       lambdaRewriter.removeLambdaDeserializationMethods(appView.appInfo().classes());
@@ -441,11 +430,9 @@
     }
   }
 
-  private void synthesizeTwrCloseResourceUtilityClass(
-      Builder<?> builder, ExecutorService executorService)
-      throws ExecutionException {
+  private void processTwrCloseResourceUtilityMethods() {
     if (twrCloseResourceRewriter != null) {
-      twrCloseResourceRewriter.synthesizeUtilityClass(builder, executorService, options);
+      twrCloseResourceRewriter.processSynthesizedMethods(this);
     }
   }
 
@@ -525,6 +512,7 @@
 
     // Synthesize lambda classes and commit to the app in full.
     synthesizeLambdaClasses(executor);
+    processTwrCloseResourceUtilityMethods();
     if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
       appView.setAppInfo(
           new AppInfo(
@@ -536,7 +524,6 @@
     }
 
     desugarInterfaceMethods(builder, ExcludeDexResources, executor);
-    synthesizeTwrCloseResourceUtilityClass(builder, executor);
     processSynthesizedJava8UtilityClasses(executor);
     synthesizeRetargetClass(builder, executor);
     synthesizeInvokeSpecialBridges(executor);
@@ -823,7 +810,6 @@
     feedback.updateVisibleOptimizationInfo();
 
     printPhase("Utility classes synthesis");
-    synthesizeTwrCloseResourceUtilityClass(builder, executorService);
     processSynthesizedJava8UtilityClasses(executorService);
     synthesizeRetargetClass(builder, executorService);
     synthesizeEnumUnboxingUtilityMethods(executorService);
@@ -1495,7 +1481,7 @@
     deadCodeRemover.run(code, timing);
     assert code.isConsistentSSA();
 
-    if (options.desugarState == DesugarState.ON && enableTryWithResourcesDesugaring()) {
+    if (options.desugarState == DesugarState.ON && options.enableTryWithResourcesDesugaring()) {
       timing.begin("Rewrite Throwable suppresed methods");
       codeRewriter.rewriteThrowableAddAndGetSuppressed(code);
       timing.end();
@@ -1582,7 +1568,7 @@
 
     if (twrCloseResourceRewriter != null) {
       timing.begin("Rewrite TWR close");
-      twrCloseResourceRewriter.rewriteMethodCode(code);
+      twrCloseResourceRewriter.rewriteIR(code);
       timing.end();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
index f6ad5f3..a9bbb90 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
@@ -67,6 +67,13 @@
     registerDesugaredLibraryAPIConverter(method);
   }
 
+  private void registerTwrCloseResourceRewriting(DexMethod method) {
+    if (!needsDesugarging) {
+      needsDesugarging =
+          TwrCloseResourceRewriter.isTwrCloseResourceMethod(method, appView.dexItemFactory());
+    }
+  }
+
   private void registerBackportedMethodRewriting(DexMethod method) {
     if (!needsDesugarging) {
       needsDesugarging = backportedMethodRewriter.needsDesugaring(method);
@@ -98,9 +105,7 @@
 
   @Override
   public void registerInvokeStatic(DexMethod method) {
-    if (!needsDesugarging) {
-      needsDesugarging = TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(method, appView);
-    }
+    registerTwrCloseResourceRewriting(method);
     registerBackportedMethodRewriting(method);
     registerLibraryRetargeting(method, false);
     registerInterfaceMethodRewriting(method);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index d41fd32..bf7a3ea 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -4,178 +4,141 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication.Builder;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
-import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.Sets;
-import java.util.Collections;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
+import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.google.common.base.Suppliers;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.objectweb.asm.Opcodes;
 
-// Try with resources outlining processor. Handles $closeResource methods
-// synthesized by java 9 compiler.
+// Try with resources close-resource desugaring.
 //
-// Works in two phases:
-//   a. during method code processing finds all references to $closeResource(...) synthesized
-//      by java compiler, and replaces them with references to a special utility class method.
-//   b. after all methods are processed and if there was at least one method referencing
-//      $closeResource(...), it synthesizes utility class with appropriate methods.
+// Rewrites $closeResource methods synthesized by java compilers to work on older DEX runtimes.
+// All invocations to $closeResource methods are rewritten to target a compiler generated version
+// that is correct on older DEX runtimes where not all types implement AutoClosable as expected.
 //
 // Note that we don't remove $closeResource(...) synthesized by java compiler, relying on
 // tree shaking to remove them since now they should not be referenced.
-//
+// TODO(b/177401708): D8 does not tree shake so we should remove the now unused method.
 public final class TwrCloseResourceRewriter {
 
-  public static final String UTILITY_CLASS_NAME = "$r8$twr$utility";
-  public static final String UTILITY_CLASS_DESCRIPTOR = "L$r8$twr$utility;";
-
   private final AppView<?> appView;
-  private final IRConverter converter;
+  private final DexProto twrCloseResourceProto;
+  private final List<ProgramMethod> synthesizedMethods = new ArrayList<>();
 
-  private final DexMethod twrCloseResourceMethod;
-
-  private final Set<DexProgramClass> referencingClasses = Sets.newConcurrentHashSet();
-
-  public TwrCloseResourceRewriter(AppView<?> appView, IRConverter converter) {
-    this.appView = appView;
-    this.converter = converter;
-
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    DexType twrUtilityClass = dexItemFactory.createType(UTILITY_CLASS_DESCRIPTOR);
-    DexProto twrCloseResourceProto =
-        dexItemFactory.createProto(
-            dexItemFactory.voidType, dexItemFactory.throwableType, dexItemFactory.objectType);
-    this.twrCloseResourceMethod =
-        dexItemFactory.createMethod(
-            twrUtilityClass, twrCloseResourceProto, dexItemFactory.twrCloseResourceMethodName);
+  public static boolean enableTwrCloseResourceDesugaring(InternalOptions options) {
+    return options.desugarState == DesugarState.ON
+        && options.enableTryWithResourcesDesugaring()
+        && !options.canUseTwrCloseResourceMethod();
   }
 
-  public static boolean isUtilityClassDescriptor(DexType type) {
-    return type.descriptor.toString().equals(UTILITY_CLASS_DESCRIPTOR);
+  public TwrCloseResourceRewriter(AppView<?> appView) {
+    this.appView = appView;
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    twrCloseResourceProto =
+        dexItemFactory.createProto(
+            dexItemFactory.voidType, dexItemFactory.throwableType, dexItemFactory.objectType);
+  }
+
+  public int rewriteCf(ProgramMethod method, Consumer<ProgramMethod> newMethodCallback) {
+    CfCode code = method.getDefinition().getCode().asCfCode();
+    List<CfInstruction> instructions = code.getInstructions();
+    Supplier<List<CfInstruction>> lazyNewInstructions =
+        Suppliers.memoize(() -> new ArrayList<>(instructions));
+    int replaced = 0;
+    int newInstructionDelta = 0;
+    for (int i = 0; i < instructions.size(); i++) {
+      CfInvoke invoke = instructions.get(i).asInvoke();
+      if (invoke == null
+          || invoke.getOpcode() != Opcodes.INVOKESTATIC
+          || !isTwrCloseResourceMethod(invoke.getMethod(), appView.dexItemFactory())) {
+        continue;
+      }
+      // Synthesize a new method.
+      ProgramMethod closeMethod = createSyntheticCloseResourceMethod(method);
+      newMethodCallback.accept(closeMethod);
+      // Rewrite the invoke to the new synthetic.
+      int newInstructionIndex = i + newInstructionDelta;
+      lazyNewInstructions
+          .get()
+          .set(
+              newInstructionIndex,
+              new CfInvoke(Opcodes.INVOKESTATIC, closeMethod.getReference(), false));
+      ++replaced;
+    }
+    if (replaced > 0) {
+      code.setInstructions(lazyNewInstructions.get());
+    }
+    return replaced;
   }
 
   // Rewrites calls to $closeResource() method. Can be invoked concurrently.
-  public void rewriteMethodCode(IRCode code) {
+  public void rewriteIR(IRCode code) {
     InstructionListIterator iterator = code.instructionListIterator();
-    AppInfo appInfo = appView.appInfo();
     while (iterator.hasNext()) {
-      Instruction instruction = iterator.next();
-      if (!instruction.isInvokeStatic()) {
-        continue;
-      }
-      InvokeStatic invoke = instruction.asInvokeStatic();
-      if (!isSynthesizedCloseResourceMethod(invoke.getInvokedMethod(), appView)) {
+      InvokeStatic invoke = iterator.next().asInvokeStatic();
+      if (invoke == null
+          || !isTwrCloseResourceMethod(invoke.getInvokedMethod(), appView.dexItemFactory())) {
         continue;
       }
 
-      // Replace with a call to a synthetic utility with the only
-      // implementation of the method $closeResource.
+      // Replace with a call to a synthetic utility.
       assert invoke.outValue() == null;
       assert invoke.inValues().size() == 2;
-      iterator.replaceCurrentInstruction(
-          new InvokeStatic(twrCloseResourceMethod, null, invoke.inValues()));
-
-      // Mark as a class referencing utility class.
-      referencingClasses.add(appInfo.definitionFor(code.method().getHolderType()).asProgramClass());
+      ProgramMethod closeResourceMethod = createSyntheticCloseResourceMethod(code.context());
+      InvokeStatic newInvoke =
+          new InvokeStatic(closeResourceMethod.getReference(), null, invoke.inValues());
+      iterator.replaceCurrentInstruction(newInvoke);
+      synchronized (synthesizedMethods) {
+        synthesizedMethods.add(closeResourceMethod);
+      }
     }
   }
 
-  public static boolean isSynthesizedCloseResourceMethod(DexMethod method, AppView<?> appView) {
-    DexMethod original = appView.graphLens().getOriginalMethodSignature(method);
-    assert original != null;
-    // We consider all methods of *any* class with expected name and signature
-    // to be synthesized by java 9 compiler for try-with-resources, reasoning:
-    //
-    //  * we need to look to all potential classes because the calls might be
-    //    moved by inlining.
-    //  * theoretically we could check appropriate encoded method for having
-    //    right attributes, but it still does not guarantee much since we also
-    //    need to look into code and doing this seems an overkill
-    //
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    return original.name == dexItemFactory.twrCloseResourceMethodName
-        && original.proto == dexItemFactory.twrCloseResourceMethodProto;
+  public static boolean isTwrCloseResourceMethod(DexMethod method, DexItemFactory factory) {
+    return method.name == factory.twrCloseResourceMethodName
+        && method.proto == factory.twrCloseResourceMethodProto;
   }
 
-  public void synthesizeUtilityClass(
-      Builder<?> builder, ExecutorService executorService, InternalOptions options)
-      throws ExecutionException {
-    if (referencingClasses.isEmpty()) {
-      return;
-    }
+  private ProgramMethod createSyntheticCloseResourceMethod(ProgramMethod method) {
+    return appView
+        .getSyntheticItems()
+        .createMethod(
+            SyntheticKind.TWR_CLOSE_RESOURCE,
+            method,
+            appView.dexItemFactory(),
+            methodBuilder ->
+                methodBuilder
+                    .setAccessFlags(
+                        MethodAccessFlags.fromSharedAccessFlags(
+                            Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC,
+                            false))
+                    .setProto(twrCloseResourceProto)
+                    .setCode(
+                        m ->
+                            BackportedMethods.CloseResourceMethod_closeResourceImpl(
+                                appView.options(), m)));
+  }
 
-    // The only encoded method.
-    CfCode code =
-        BackportedMethods.CloseResourceMethod_closeResourceImpl(
-            options, twrCloseResourceMethod);
-    MethodAccessFlags flags = MethodAccessFlags.fromSharedAccessFlags(
-        Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false);
-    DexEncodedMethod method =
-        new DexEncodedMethod(
-            twrCloseResourceMethod,
-            flags,
-            MethodTypeSignature.noSignature(),
-            DexAnnotationSet.empty(),
-            ParameterAnnotationsList.empty(),
-            code,
-            true);
-
-    // Create utility class.
-    DexProgramClass utilityClass =
-        new DexProgramClass(
-            twrCloseResourceMethod.holder,
-            null,
-            new SynthesizedOrigin("twr utility class", getClass()),
-            ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
-            appView.dexItemFactory().objectType,
-            DexTypeList.empty(),
-            null,
-            null,
-            Collections.emptyList(),
-            null,
-            Collections.emptyList(),
-            ClassSignature.noSignature(),
-            DexAnnotationSet.empty(),
-            DexEncodedField.EMPTY_ARRAY,
-            DexEncodedField.EMPTY_ARRAY,
-            new DexEncodedMethod[] {method},
-            DexEncodedMethod.EMPTY_ARRAY,
-            appView.dexItemFactory().getSkipNameValidationForTesting(),
-            DexProgramClass::checksumFromType,
-            referencingClasses);
-
-    // Process created class and method.
-    AppInfo appInfo = appView.appInfo();
-    MainDexClasses mainDexClasses = appInfo.getMainDexClasses();
-    boolean addToMainDexList = mainDexClasses.containsAnyOf(referencingClasses);
-    appInfo.addSynthesizedClass(utilityClass, addToMainDexList);
-    converter.optimizeSynthesizedClass(utilityClass, executorService);
-    builder.addSynthesizedClass(utilityClass);
+  public void processSynthesizedMethods(IRConverter converter) {
+    synthesizedMethods.forEach(converter::optimizeSynthesizedMethod);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 8f510fb..7e75ee2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -49,7 +49,6 @@
 import com.android.tools.r8.ir.conversion.LensCodeRewriter;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.PostOptimization;
-import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.ir.optimize.inliner.DefaultInliningReasonStrategy;
@@ -130,9 +129,7 @@
     }
 
     if (extraNeverInlineMethods.contains(
-            appView.graphLens().getOriginalMethodSignature(singleTargetReference))
-        || TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(
-            singleTargetReference, appView)) {
+        appView.graphLens().getOriginalMethodSignature(singleTargetReference))) {
       whyAreYouNotInliningReporter.reportExtraNeverInline();
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 73037aa..f1c5405 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -47,7 +47,6 @@
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
-import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.utils.CollectionUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -473,7 +472,6 @@
             || InterfaceMethodRewriter.hasDispatchClassSuffix(type)
             || InterfaceMethodRewriter.isEmulatedLibraryClassType(type)
             || type.toDescriptorString().startsWith("Lj$/$r8$retargetLibraryMember$")
-            || TwrCloseResourceRewriter.isUtilityClassDescriptor(type)
             // TODO(b/150736225): Not sure how to remove these.
             || DesugaredLibraryAPIConverter.isVivifiedType(type)
         : "Failed lookup of non-missing type: " + type;
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 90416fd..37c66c4 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -86,6 +86,7 @@
 import com.android.tools.r8.ir.desugar.LambdaClass;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
+import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.kotlin.KotlinMetadataEnqueuerExtension;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
@@ -359,12 +360,14 @@
 
   private final LambdaRewriter lambdaRewriter;
   private final BackportedMethodRewriter backportRewriter;
+  private final TwrCloseResourceRewriter twrCloseResourceRewriter;
   private final DesugaredLibraryConversionWrapperAnalysis desugaredLibraryWrapperAnalysis;
   private final Map<DexType, Pair<LambdaClass, ProgramMethod>> lambdaClasses =
       new IdentityHashMap<>();
   private final ProgramMethodMap<Map<DexCallSite, LambdaClass>> lambdaCallSites =
       ProgramMethodMap.create();
   private final Map<DexMethod, ProgramMethod> methodsWithBackports = new IdentityHashMap<>();
+  private final Map<DexMethod, ProgramMethod> methodsWithTwrCloseResource = new IdentityHashMap<>();
   private final Set<DexProgramClass> classesWithSerializableLambdas = Sets.newIdentityHashSet();
 
   Enqueuer(
@@ -403,6 +406,10 @@
     lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
     backportRewriter =
         options.desugarState == DesugarState.ON ? new BackportedMethodRewriter(appView) : null;
+    twrCloseResourceRewriter =
+        TwrCloseResourceRewriter.enableTwrCloseResourceDesugaring(options)
+            ? new TwrCloseResourceRewriter(appView)
+            : null;
 
     objectAllocationInfoCollection =
         ObjectAllocationInfoCollectionImpl.builder(mode.isInitialTreeShaking(), graphReporter);
@@ -1234,12 +1241,24 @@
     return false;
   }
 
+  private boolean registerCloseResource(DexMethod invokedMethod, ProgramMethod context) {
+    if (twrCloseResourceRewriter != null
+        && TwrCloseResourceRewriter.isTwrCloseResourceMethod(
+            invokedMethod, appView.dexItemFactory())) {
+      methodsWithTwrCloseResource.putIfAbsent(context.getReference(), context);
+      return true;
+    }
+    return false;
+  }
+
   private void traceInvokeStatic(
       DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
     if (registerBackportInvoke(invokedMethod, context)) {
       return;
     }
-
+    if (registerCloseResource(invokedMethod, context)) {
+      return;
+    }
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     if (dexItemFactory.classMethods.isReflectiveClassLookup(invokedMethod)
         || dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod)) {
@@ -3132,6 +3151,7 @@
     synthesizeLambdas(additions);
     synthesizeLibraryConversionWrappers(additions);
     synthesizeBackports(additions);
+    synthesizeTwrCloseResource(additions);
     if (additions.isEmpty()) {
       return;
     }
@@ -3175,6 +3195,12 @@
     }
   }
 
+  private void synthesizeTwrCloseResource(SyntheticAdditions additions) {
+    for (ProgramMethod method : methodsWithTwrCloseResource.values()) {
+      twrCloseResourceRewriter.rewriteCf(method, additions::addLiveMethod);
+    }
+  }
+
   private void synthesizeLambdas(SyntheticAdditions additions) {
     if (lambdaRewriter == null || lambdaClasses.isEmpty()) {
       assert lambdaCallSites.isEmpty();
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 644a359..27e03ce 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -27,7 +27,8 @@
     BACKPORT("Backport", true),
     STATIC_INTERFACE_CALL("StaticInterfaceCall", true),
     TO_STRING_IF_NOT_NULL("ToStringIfNotNull", true),
-    THROW_CCE_IF_NOT_NULL("ThrowCCEIfNotNull", true);
+    THROW_CCE_IF_NOT_NULL("ThrowCCEIfNotNull", true),
+    TWR_CLOSE_RESOURCE("TwrCloseResource", true);
 
     public final String descriptor;
     public final boolean isSingleSyntheticMethod;
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 c375074..d0e0a52 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1606,6 +1606,16 @@
     return !isDesugaring() || hasMinApi(AndroidApiLevel.K);
   }
 
+  public boolean enableTryWithResourcesDesugaring() {
+    switch (tryWithResourcesDesugaring) {
+      case Off:
+        return false;
+      case Auto:
+        return !canUseSuppressedExceptions();
+    }
+    throw new Unreachable();
+  }
+
   public boolean canUsePrivateInterfaceMethods() {
     return !isDesugaring() || hasMinApi(AndroidApiLevel.N);
   }
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index f5348c3..845a5c5 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
-import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
@@ -98,7 +97,7 @@
           Assert.assertTrue(
               descriptor.endsWith(InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX + ";")
                   || descriptor.endsWith(InterfaceMethodRewriter.DISPATCH_CLASS_NAME_SUFFIX + ";")
-                  || descriptor.equals(TwrCloseResourceRewriter.UTILITY_CLASS_DESCRIPTOR)
+                  || SyntheticItemsTestUtils.isExternalTwrCloseMethod(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 e1d4c9e..bb69054 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1702,6 +1702,10 @@
     return AndroidApiLevel.L;
   }
 
+  public static AndroidApiLevel apiLevelWithTwrCloseResourceSupport() {
+    return AndroidApiLevel.K;
+  }
+
   public static boolean canUseJavaUtilObjects(TestParameters parameters) {
     return parameters.isCfRuntime()
         || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
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
new file mode 100644
index 0000000..dde98e2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
@@ -0,0 +1,157 @@
+// 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.desugar.twr;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.util.List;
+import java.util.jar.JarFile;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class TwrCloseResourceDuplicationTest extends TestBase {
+
+  static final int INPUT_CLASSES = 3;
+
+  static final String EXPECTED =
+      StringUtils.lines(
+          "foo opened 1",
+          "foo post close 1",
+          "foo opened 2",
+          "foo caught from 2: RuntimeException",
+          "foo post close 2",
+          "bar opened 1",
+          "bar post close 1",
+          "bar opened 2",
+          "bar caught from 2: RuntimeException",
+          "bar post close 2");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public TwrCloseResourceDuplicationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private String getZipFile() throws IOException {
+    return ZipUtils.ZipBuilder.builder(temp.newFile("file.zip").toPath())
+        // DEX VMs from 4.4 up-to 9.0 including, will fail if no entry is added.
+        .addBytes("entry", new byte[1])
+        .build()
+        .toString();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), TestClass.class, getZipFile())
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class, getZipFile())
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(
+            inspector -> {
+              // There should be exactly one synthetic class besides the three program classes.
+              int expectedSynthetics =
+                  parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport())
+                      ? 1
+                      : 0;
+              assertEquals(INPUT_CLASSES + expectedSynthetics, inspector.allClasses().size());
+            });
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(Foo.class, Bar.class)
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .run(parameters.getRuntime(), TestClass.class, getZipFile())
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(
+            inspector -> {
+              // R8 will optimize the generated methods for the two cases below where the thrown
+              // exception is known or not, thus the synthetic methods will be 2.
+              int expectedSynthetics =
+                  parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport())
+                      ? 2
+                      : 0;
+              List<FoundClassSubject> foundClassSubjects = inspector.allClasses();
+              assertEquals(INPUT_CLASSES + expectedSynthetics, foundClassSubjects.size());
+            });
+  }
+
+  static class Foo {
+    void foo(String name) {
+      try (JarFile f = new JarFile(name)) {
+        System.out.println("foo opened 1");
+      } catch (Exception e) {
+        System.out.println("foo caught from 1: " + e.getClass().getSimpleName());
+      } finally {
+        System.out.println("foo post close 1");
+      }
+      try (JarFile f = new JarFile(name)) {
+        System.out.println("foo opened 2");
+        throw new RuntimeException();
+      } catch (Exception e) {
+        System.out.println("foo caught from 2: " + e.getClass().getSimpleName());
+      } finally {
+        System.out.println("foo post close 2");
+      }
+    }
+  }
+
+  static class Bar {
+    void bar(String name) {
+      try (JarFile f = new JarFile(name)) {
+        System.out.println("bar opened 1");
+      } catch (Exception e) {
+        System.out.println("bar caught from 1: " + e.getClass().getSimpleName());
+      } finally {
+        System.out.println("bar post close 1");
+      }
+      try (JarFile f = new JarFile(name)) {
+        System.out.println("bar opened 2");
+        throw new RuntimeException();
+      } catch (Exception e) {
+        System.out.println("bar caught from 2: " + e.getClass().getSimpleName());
+      } finally {
+        System.out.println("bar post close 2");
+      }
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new Foo().foo(args[0]);
+      new Bar().bar(args[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 dba85d6..438d3f9 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -63,6 +63,10 @@
         reference, Phase.EXTERNAL, SyntheticKind.STATIC_INTERFACE_CALL);
   }
 
+  public static boolean isExternalTwrCloseMethod(ClassReference reference) {
+    return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.TWR_CLOSE_RESOURCE);
+  }
+
   public static Matcher<String> containsInternalSyntheticReference() {
     return containsString(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
   }