diff options
2 files changed, 101 insertions, 4 deletions
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 2cdcae1564..f7a3a7d156 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 @@ public class TryWithResourcesRewriter extends ClassVisitor { .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 @@ public class TryWithResourcesRewriter extends ClassVisitor { // 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 @@ public class TryWithResourcesRewriter extends ClassVisitor { @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 @@ public class TryWithResourcesRewriter extends ClassVisitor { 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 @@ public class TryWithResourcesRewriter extends ClassVisitor { 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 365884b690..070363a0b8 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 final class ThrowableExtension { 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 @@ public final class ThrowableExtension { strategy = new NullDesugaringStrategy(); } STRATEGY = strategy; + API_LEVEL = apiLevel == null ? 1 : apiLevel.intValue(); } public static AbstractDesugaringStrategy getStrategy() { @@ -92,6 +100,44 @@ public final class ThrowableExtension { 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); } |