Add a closeResource(Throwable throwable, Object resource) in the runtime
library. Javac9 generates a helper method $closeResource(Throwable,
AutoCloseable) sometimes for try-with-resources. Now we rewrite the call
to call our version to avoid the dependency on AutoCloseable.

RELNOTES: None
PiperOrigin-RevId: 167025276
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java
index 2cdcae1..f7a3a7d 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java
@@ -14,6 +14,8 @@
 package com.google.devtools.build.android.desugar;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.objectweb.asm.Opcodes.ACC_STATIC;
+import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
 import static org.objectweb.asm.Opcodes.ASM5;
 import static org.objectweb.asm.Opcodes.INVOKESTATIC;
 import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
@@ -88,6 +90,10 @@
           .put("(Ljava/io/PrintWriter;)V", "(Ljava/lang/Throwable;Ljava/io/PrintWriter;)V")
           .build();
 
+  static final String CLOSE_RESOURCE_METHOD_NAME = "$closeResource";
+  static final String CLOSE_RESOURCE_METHOD_DESC =
+      "(Ljava/lang/Throwable;Ljava/lang/AutoCloseable;)V";
+
   private final ClassLoader classLoader;
   private final Set<String> visitedExceptionTypes;
   private final AtomicInteger numOfTryWithResourcesInvoked;
@@ -129,22 +135,38 @@
       // collect exception types.
       Collections.addAll(visitedExceptionTypes, exceptions);
     }
+    if (isSyntheticCloseResourceMethod(access, name, desc)) {
+      return null; // Discard this method.
+    }
+
     MethodVisitor visitor = super.cv.visitMethod(access, name, desc, signature, exceptions);
     return visitor == null || shouldCurrentClassBeIgnored
         ? visitor
-        : new TryWithResourceVisitor(internalName + "." + name + desc, visitor, classLoader);
+        : new TryWithResourceVisitor(internalName, name + desc, visitor, classLoader);
+  }
+
+  private boolean isSyntheticCloseResourceMethod(int access, String name, String desc) {
+    return BitFlags.isSet(access, ACC_SYNTHETIC | ACC_STATIC)
+        && CLOSE_RESOURCE_METHOD_NAME.equals(name)
+        && CLOSE_RESOURCE_METHOD_DESC.equals(desc);
   }
 
   private class TryWithResourceVisitor extends MethodVisitor {
 
     private final ClassLoader classLoader;
     /** For debugging purpose. Enrich exception information. */
+    private final String internalName;
+
     private final String methodSignature;
 
     public TryWithResourceVisitor(
-        String methodSignature, MethodVisitor methodVisitor, ClassLoader classLoader) {
+        String internalName,
+        String methodSignature,
+        MethodVisitor methodVisitor,
+        ClassLoader classLoader) {
       super(ASM5, methodVisitor);
       this.classLoader = classLoader;
+      this.internalName = internalName;
       this.methodSignature = methodSignature;
     }
 
@@ -158,6 +180,17 @@
 
     @Override
     public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+      if (isCallToSyntheticCloseResource(opcode, owner, name, desc)) {
+        // Rewrite the call to the runtime library.
+        super.visitMethodInsn(
+            opcode,
+            THROWABLE_EXTENSION_INTERNAL_NAME,
+            "closeResource",
+            "(Ljava/lang/Throwable;Ljava/lang/Object;)V",
+            itf);
+        return;
+      }
+
       if (!isMethodCallTargeted(opcode, owner, name, desc)) {
         super.visitMethodInsn(opcode, owner, name, desc, itf);
         return;
@@ -168,6 +201,23 @@
           INVOKESTATIC, THROWABLE_EXTENSION_INTERNAL_NAME, name, METHOD_DESC_MAP.get(desc), false);
     }
 
+    private boolean isCallToSyntheticCloseResource(
+        int opcode, String owner, String name, String desc) {
+      if (opcode != INVOKESTATIC) {
+        return false;
+      }
+      if (!internalName.equals(owner)) {
+        return false;
+      }
+      if (!CLOSE_RESOURCE_METHOD_NAME.equals(name)) {
+        return false;
+      }
+      if (!CLOSE_RESOURCE_METHOD_DESC.equals(desc)) {
+        return false;
+      }
+      return true;
+    }
+
     private boolean isMethodCallTargeted(int opcode, String owner, String name, String desc) {
       if (opcode != INVOKEVIRTUAL) {
         return false;
@@ -184,7 +234,8 @@
         return throwableClass.isAssignableFrom(klass);
       } catch (ClassNotFoundException e) {
         throw new AssertionError(
-            "Failed to load class when desugaring method " + methodSignature, e);
+            "Failed to load class when desugaring method " + internalName + "." + methodSignature,
+            e);
       }
     }
   }
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/ThrowableExtension.java b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/ThrowableExtension.java
index 365884b..070363a 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/ThrowableExtension.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/ThrowableExtension.java
@@ -13,12 +13,15 @@
 // limitations under the License.
 package com.google.devtools.build.android.desugar.runtime;
 
+import java.io.Closeable;
 import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.lang.ref.Reference;
 import java.lang.ref.ReferenceQueue;
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.util.List;
 import java.util.Vector;
 import java.util.concurrent.ConcurrentHashMap;
@@ -43,10 +46,14 @@
   public static final String SYSTEM_PROPERTY_TWR_DISABLE_MIMIC =
       "com.google.devtools.build.android.desugar.runtime.twr_disable_mimic";
 
+  // Visible for testing.
+  static final int API_LEVEL;
+
   static {
     AbstractDesugaringStrategy strategy;
+    Integer apiLevel = null;
     try {
-      Integer apiLevel = readApiLevelFromBuildVersion();
+      apiLevel = readApiLevelFromBuildVersion();
       if (apiLevel != null && apiLevel.intValue() >= 19) {
         strategy = new ReuseDesugaringStrategy();
       } else if (useMimicStrategy()) {
@@ -66,6 +73,7 @@
       strategy = new NullDesugaringStrategy();
     }
     STRATEGY = strategy;
+    API_LEVEL = apiLevel == null ? 1 : apiLevel.intValue();
   }
 
   public static AbstractDesugaringStrategy getStrategy() {
@@ -92,6 +100,44 @@
     STRATEGY.printStackTrace(receiver, stream);
   }
 
+  public static void closeResource(Throwable throwable, Object resource) throws Throwable {
+    if (resource == null) {
+      return;
+    }
+    try {
+      if (API_LEVEL >= 19) {
+        ((AutoCloseable) resource).close();
+      } else {
+        if (resource instanceof Closeable) {
+          ((Closeable) resource).close();
+        } else {
+          try {
+            Method method = resource.getClass().getMethod("close");
+            method.invoke(resource);
+          } catch (NoSuchMethodException | SecurityException e) {
+            throw new AssertionError(resource.getClass() + " does not have a close() method.", e);
+          } catch (IllegalAccessException
+              | IllegalArgumentException
+              | ExceptionInInitializerError e) {
+            throw new AssertionError("Fail to call close() on " + resource.getClass(), e);
+          } catch (InvocationTargetException e) {
+            // Exception occurs during the invocation to the close method. The cause is the real
+            // exception.
+            Throwable cause = e.getCause();
+            throw cause;
+          }
+        }
+      }
+    } catch (Throwable e) {
+      if (throwable != null) {
+        addSuppressed(throwable, e);
+        throw throwable;
+      } else {
+        throw e;
+      }
+    }
+  }
+
   private static boolean useMimicStrategy() {
     return !Boolean.getBoolean(SYSTEM_PROPERTY_TWR_DISABLE_MIMIC);
   }