summaryrefslogtreecommitdiff
path: root/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java')
-rw-r--r--java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java240
1 files changed, 207 insertions, 33 deletions
diff --git a/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java b/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java
index f7a3a7d..17580c4 100644
--- a/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java
+++ b/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java
@@ -14,23 +14,32 @@
package com.google.devtools.build.android.desugar;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
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.INVOKEINTERFACE;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.android.desugar.BytecodeTypeInference.InferredType;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
+import javax.annotation.Nullable;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.commons.ClassRemapper;
+import org.objectweb.asm.commons.Remapper;
+import org.objectweb.asm.tree.MethodNode;
/**
* Desugar try-with-resources. This class visitor intercepts calls to the following methods, and
@@ -97,22 +106,34 @@ public class TryWithResourcesRewriter extends ClassVisitor {
private final ClassLoader classLoader;
private final Set<String> visitedExceptionTypes;
private final AtomicInteger numOfTryWithResourcesInvoked;
+ /** Stores the internal class names of resources that need to be closed. */
+ private final LinkedHashSet<String> resourceTypeInternalNames = new LinkedHashSet<>();
+
+ private final boolean hasCloseResourceMethod;
+
private String internalName;
/**
* Indicate whether the current class being desugared should be ignored. If the current class is
* one of the runtime extension classes, then it should be ignored.
*/
private boolean shouldCurrentClassBeIgnored;
+ /**
+ * A method node for $closeResource(Throwable, AutoCloseable). At then end, we specialize this
+ * method node.
+ */
+ @Nullable private MethodNode closeResourceMethod;
public TryWithResourcesRewriter(
ClassVisitor classVisitor,
ClassLoader classLoader,
Set<String> visitedExceptionTypes,
- AtomicInteger numOfTryWithResourcesInvoked) {
+ AtomicInteger numOfTryWithResourcesInvoked,
+ boolean hasCloseResourceMethod) {
super(ASM5, classVisitor);
this.classLoader = classLoader;
this.visitedExceptionTypes = visitedExceptionTypes;
this.numOfTryWithResourcesInvoked = numOfTryWithResourcesInvoked;
+ this.hasCloseResourceMethod = hasCloseResourceMethod;
}
@Override
@@ -126,6 +147,33 @@ public class TryWithResourcesRewriter extends ClassVisitor {
super.visit(version, access, name, signature, superName, interfaces);
internalName = name;
shouldCurrentClassBeIgnored = THROWABLE_EXT_CLASS_INTERNAL_NAMES.contains(name);
+ Preconditions.checkState(
+ !shouldCurrentClassBeIgnored || !hasCloseResourceMethod,
+ "The current class which will be ignored "
+ + "contains $closeResource(Throwable, AutoCloseable).");
+ }
+
+ @Override
+ public void visitEnd() {
+ if (!resourceTypeInternalNames.isEmpty()) {
+ checkNotNull(closeResourceMethod);
+ for (String resourceInternalName : resourceTypeInternalNames) {
+ boolean isInterface = isInterface(resourceInternalName.replace('/', '.'));
+ // We use "this" to desugar the body of the close resource method.
+ closeResourceMethod.accept(
+ new CloseResourceMethodSpecializer(cv, resourceInternalName, isInterface));
+ }
+ } else {
+ checkState(
+ closeResourceMethod == null,
+ "The field resourceTypeInternalNames is empty. "
+ + "But the class has the $closeResource method.");
+ checkState(
+ !hasCloseResourceMethod,
+ "The class %s has close resource method, but resourceTypeInternalNames is empty.",
+ internalName);
+ }
+ super.visitEnd();
}
@Override
@@ -136,21 +184,70 @@ public class TryWithResourcesRewriter extends ClassVisitor {
Collections.addAll(visitedExceptionTypes, exceptions);
}
if (isSyntheticCloseResourceMethod(access, name, desc)) {
- return null; // Discard this method.
+ checkState(closeResourceMethod == null, "The TWR rewriter has been used.");
+ closeResourceMethod = new MethodNode(ASM5, access, name, desc, signature, exceptions);
+ // Run the TWR desugar pass over the $closeResource(Throwable, AutoCloseable) first, for
+ // example, to rewrite calls to AutoCloseable.close()..
+ TryWithResourceVisitor twrVisitor =
+ new TryWithResourceVisitor(
+ internalName, name + desc, closeResourceMethod, classLoader, null);
+ return twrVisitor;
}
MethodVisitor visitor = super.cv.visitMethod(access, name, desc, signature, exceptions);
- return visitor == null || shouldCurrentClassBeIgnored
- ? visitor
- : new TryWithResourceVisitor(internalName, name + desc, visitor, classLoader);
+ if (visitor == null || shouldCurrentClassBeIgnored) {
+ return visitor;
+ }
+
+ BytecodeTypeInference inference = null;
+ if (hasCloseResourceMethod) {
+ /*
+ * BytecodeTypeInference will run after the TryWithResourceVisitor, because when we are
+ * processing a bytecode instruction, we need to know the types in the operand stack, which
+ * are inferred after the previous instruction.
+ */
+ inference = new BytecodeTypeInference(access, internalName, name, desc);
+ inference.setDelegateMethodVisitor(visitor);
+ visitor = inference;
+ }
+
+ TryWithResourceVisitor twrVisitor =
+ new TryWithResourceVisitor(internalName, name + desc, visitor, classLoader, inference);
+ return twrVisitor;
}
- private boolean isSyntheticCloseResourceMethod(int access, String name, String desc) {
+ public static 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 boolean isInterface(String className) {
+ try {
+ Class<?> klass = classLoader.loadClass(className);
+ return klass.isInterface();
+ } catch (ClassNotFoundException e) {
+ throw new AssertionError("Failed to load class when desugaring class " + internalName);
+ }
+ }
+
+ public static boolean isCallToSyntheticCloseResource(
+ String currentClassInternalName, int opcode, String owner, String name, String desc) {
+ if (opcode != INVOKESTATIC) {
+ return false;
+ }
+ if (!currentClassInternalName.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 class TryWithResourceVisitor extends MethodVisitor {
private final ClassLoader classLoader;
@@ -158,16 +255,19 @@ public class TryWithResourcesRewriter extends ClassVisitor {
private final String internalName;
private final String methodSignature;
+ @Nullable private final BytecodeTypeInference typeInference;
public TryWithResourceVisitor(
String internalName,
String methodSignature,
MethodVisitor methodVisitor,
- ClassLoader classLoader) {
+ ClassLoader classLoader,
+ @Nullable BytecodeTypeInference typeInference) {
super(ASM5, methodVisitor);
this.classLoader = classLoader;
this.internalName = internalName;
this.methodSignature = methodSignature;
+ this.typeInference = typeInference;
}
@Override
@@ -180,13 +280,39 @@ 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.
+ if (isCallToSyntheticCloseResource(internalName, opcode, owner, name, desc)) {
+ checkNotNull(
+ typeInference,
+ "This method %s.%s has a call to $closeResource(Throwable, AutoCloseable) method, "
+ + "but the type inference is null.",
+ internalName,
+ methodSignature);
+ {
+ // Check the exception type.
+ InferredType exceptionClass = typeInference.getTypeOfOperandFromTop(1);
+ if (!exceptionClass.isNull()) {
+ String exceptionClassInternalName = exceptionClass.getInternalNameOrThrow();
+ checkState(
+ isAssignableFrom(
+ "java.lang.Throwable", exceptionClassInternalName.replace('/', '.')),
+ "The exception type should be a subclass of java.lang.Throwable.");
+ }
+ }
+
+ String resourceClassInternalName =
+ typeInference.getTypeOfOperandFromTop(0).getInternalNameOrThrow();
+ checkState(
+ isAssignableFrom(
+ "java.lang.AutoCloseable", resourceClassInternalName.replace('/', '.')),
+ "The resource type should be a subclass of java.lang.AutoCloseable: %s",
+ resourceClassInternalName);
+
+ resourceTypeInternalNames.add(resourceClassInternalName);
super.visitMethodInsn(
opcode,
- THROWABLE_EXTENSION_INTERNAL_NAME,
- "closeResource",
- "(Ljava/lang/Throwable;Ljava/lang/Object;)V",
+ owner,
+ "$closeResource",
+ "(Ljava/lang/Throwable;L" + resourceClassInternalName + ";)V",
itf);
return;
}
@@ -201,23 +327,6 @@ 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;
@@ -228,15 +337,80 @@ public class TryWithResourcesRewriter extends ClassVisitor {
if (visitedExceptionTypes.contains(owner)) {
return true; // The owner is an exception that has been visited before.
}
+ return isAssignableFrom("java.lang.Throwable", owner.replace('/', '.'));
+ }
+
+ private boolean isAssignableFrom(String baseClassName, String subClassName) {
try {
- Class<?> throwableClass = classLoader.loadClass("java.lang.Throwable");
- Class<?> klass = classLoader.loadClass(owner.replace('/', '.'));
- return throwableClass.isAssignableFrom(klass);
+ Class<?> baseClass = classLoader.loadClass(baseClassName);
+ Class<?> subClass = classLoader.loadClass(subClassName);
+ return baseClass.isAssignableFrom(subClass);
} catch (ClassNotFoundException e) {
throw new AssertionError(
- "Failed to load class when desugaring method " + internalName + "." + methodSignature,
+ "Failed to load class when desugaring method "
+ + internalName
+ + "."
+ + methodSignature
+ + " when checking the assignable relation for class "
+ + baseClassName
+ + " and "
+ + subClassName,
e);
}
}
}
+
+ /**
+ * A class to specialize the method $closeResource(Throwable, AutoCloseable), which does
+ *
+ * <ul>
+ * <li>Rename AutoCloseable to the given concrete resource type.
+ * <li>Adjust the invoke instruction that calls AutoCloseable.close()
+ * </ul>
+ */
+ private static class CloseResourceMethodSpecializer extends ClassRemapper {
+
+ private final boolean isResourceAnInterface;
+ private final String targetResourceInternalName;
+
+ public CloseResourceMethodSpecializer(
+ ClassVisitor cv, String targetResourceInternalName, boolean isResourceAnInterface) {
+ super(
+ cv,
+ new Remapper() {
+ @Override
+ public String map(String typeName) {
+ if (typeName.equals("java/lang/AutoCloseable")) {
+ return targetResourceInternalName;
+ } else {
+ return typeName;
+ }
+ }
+ });
+ this.targetResourceInternalName = targetResourceInternalName;
+ this.isResourceAnInterface = isResourceAnInterface;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String desc, String signature, String[] exceptions) {
+ MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
+ return new MethodVisitor(ASM5, mv) {
+ @Override
+ public void visitMethodInsn(
+ int opcode, String owner, String name, String desc, boolean itf) {
+ if (opcode == INVOKEINTERFACE
+ && owner.endsWith("java/lang/AutoCloseable")
+ && name.equals("close")
+ && desc.equals("()V")
+ && itf) {
+ opcode = isResourceAnInterface ? INVOKEINTERFACE : INVOKEVIRTUAL;
+ owner = targetResourceInternalName;
+ itf = isResourceAnInterface;
+ }
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
+ }
+ };
+ }
+ }
}