aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java209
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java7
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java79
3 files changed, 286 insertions, 9 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java b/src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java
index 2563c7a9ab..68f9756735 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java
@@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.ImmutableList;
import java.util.Comparator;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
import org.objectweb.asm.ClassReader;
@@ -27,6 +28,11 @@ import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.InsnNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
/**
* Fixer of classes that extend interfaces with default methods to declare any missing methods
@@ -43,6 +49,8 @@ public class DefaultMethodClassFixer extends ClassVisitor {
private String internalName;
private ImmutableList<String> directInterfaces;
private String superName;
+ /** This method node caches <clinit>, and flushes out in {@code visitEnd()}; */
+ private MethodNode clInitMethodNode;
public DefaultMethodClassFixer(
ClassVisitor dest,
@@ -82,10 +90,88 @@ public class DefaultMethodClassFixer extends ClassVisitor {
// figure out what methods they declare before stubbing in any missing default methods.
recordInheritedMethods();
stubMissingDefaultAndBridgeMethods();
+ // Check whether there are interfaces with default methods and <clinit>. If yes, the following
+ // method call will return a list of interface fields to access in the <clinit> to trigger
+ // the initialization of these interfaces.
+ ImmutableList<String> companionsToTriggerInterfaceClinit =
+ computeOrderedCompanionsToTriggerInterfaceClinit(directInterfaces);
+ if (!companionsToTriggerInterfaceClinit.isEmpty()) {
+ if (clInitMethodNode == null) {
+ clInitMethodNode = new MethodNode(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
+ }
+ desugarClinitToTriggerInterfaceInitializers(companionsToTriggerInterfaceClinit);
+ }
+ }
+ if (clInitMethodNode != null && super.cv != null) { // Write <clinit> to the chained visitor.
+ clInitMethodNode.accept(super.cv);
}
super.visitEnd();
}
+ private boolean isClinitAlreadyDesugared(
+ ImmutableList<String> companionsToAccessToTriggerInterfaceClinit) {
+ InsnList instructions = clInitMethodNode.instructions;
+ if (instructions.size() <= companionsToAccessToTriggerInterfaceClinit.size()) {
+ // The <clinit> must end with RETURN, so if the instruction count is less than or equal to
+ // the companion class count, this <clinit> has not been desugared.
+ return false;
+ }
+ Iterator<AbstractInsnNode> iterator = instructions.iterator();
+ for (String companion : companionsToAccessToTriggerInterfaceClinit) {
+ if (!iterator.hasNext()) {
+ return false;
+ }
+ AbstractInsnNode first = iterator.next();
+ if (!(first instanceof MethodInsnNode)) {
+ return false;
+ }
+ MethodInsnNode methodInsnNode = (MethodInsnNode) first;
+ if (methodInsnNode.getOpcode() != Opcodes.INVOKESTATIC
+ || !methodInsnNode.owner.equals(companion)
+ || !methodInsnNode.name.equals(
+ InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME)) {
+ return false;
+ }
+ checkState(
+ methodInsnNode.desc.equals(
+ InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC),
+ "Inconsistent method desc: %s vs %s",
+ methodInsnNode.desc,
+ InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC);
+
+ if (!iterator.hasNext()) {
+ return false;
+ }
+ AbstractInsnNode second = iterator.next();
+ if (second.getOpcode() != Opcodes.POP) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void desugarClinitToTriggerInterfaceInitializers(
+ ImmutableList<String> companionsToTriggerInterfaceClinit) {
+ if (isClinitAlreadyDesugared(companionsToTriggerInterfaceClinit)) {
+ return;
+ }
+ InsnList desugarInsts = new InsnList();
+ for (String companionClass : companionsToTriggerInterfaceClinit) {
+ desugarInsts.add(
+ new MethodInsnNode(
+ Opcodes.INVOKESTATIC,
+ companionClass,
+ InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME,
+ InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC,
+ false));
+ }
+ if (clInitMethodNode.instructions.size() == 0) {
+ clInitMethodNode.instructions.insert(new InsnNode(Opcodes.RETURN));
+ }
+ clInitMethodNode.instructions.insertBefore(
+ clInitMethodNode.instructions.getFirst(), desugarInsts);
+ }
+
@Override
public MethodVisitor visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
@@ -93,6 +179,11 @@ public class DefaultMethodClassFixer extends ClassVisitor {
if (!isInterface) {
recordIfInstanceMethod(access, name, desc);
}
+ if ("<clinit>".equals(name)) {
+ checkState(clInitMethodNode == null, "This class fixer has been used. ");
+ clInitMethodNode = new MethodNode(access, name, desc, signature, exceptions);
+ return clInitMethodNode;
+ }
return super.visitMethod(access, name, desc, signature, exceptions);
}
@@ -165,6 +256,55 @@ public class DefaultMethodClassFixer extends ClassVisitor {
}
/**
+ * Starting from the given interfaces, this method scans the interface hierarchy, finds the
+ * interfaces that have default methods and <clinit>, and returns the companion class names of
+ * these interfaces.
+ *
+ * <p>Note that the returned companion classes are ordered in the order of the interface
+ * initialization, which is consistent with the JVM behavior. For example, "class A implements I1,
+ * I2", the returned list would be [I1$$CC, I2$$CC], not [I2$$CC, I1$$CC].
+ */
+ private ImmutableList<String> computeOrderedCompanionsToTriggerInterfaceClinit(
+ ImmutableList<String> interfaces) {
+ ImmutableList.Builder<String> companionCollector = ImmutableList.builder();
+ HashSet<String> visitedInterfaces = new HashSet<>();
+ for (String anInterface : interfaces) {
+ computeOrderedCompanionsToTriggerInterfaceClinit(
+ anInterface, visitedInterfaces, companionCollector);
+ }
+ return companionCollector.build();
+ }
+
+ private void computeOrderedCompanionsToTriggerInterfaceClinit(
+ String anInterface,
+ HashSet<String> visitedInterfaces,
+ ImmutableList.Builder<String> companionCollector) {
+ if (!visitedInterfaces.add(anInterface)) {
+ return;
+ }
+ ClassReader bytecode = classpath.readIfKnown(anInterface);
+ if (bytecode == null || bootclasspath.isKnown(anInterface)) {
+ return;
+ }
+ String[] parentInterfaces = bytecode.getInterfaces();
+ if (parentInterfaces != null && parentInterfaces.length > 0) {
+ for (String parentInterface : parentInterfaces) {
+ computeOrderedCompanionsToTriggerInterfaceClinit(
+ parentInterface, visitedInterfaces, companionCollector);
+ }
+ }
+ InterfaceInitializationNecessityDetector necessityDetector =
+ new InterfaceInitializationNecessityDetector(bytecode.getClassName());
+ bytecode.accept(necessityDetector, ClassReader.SKIP_DEBUG);
+ if (necessityDetector.needsToInitialize()) {
+ // If we need to initialize this interface, we initialize its companion class, and its
+ // companion class will initialize the interface then. This desigin decision is made to avoid
+ // access issue, e.g., package-private interfaces.
+ companionCollector.add(InterfaceDesugaring.getCompanionClassName(anInterface));
+ }
+ }
+
+ /**
* Recursively searches the given interfaces for default methods not implemented by this class
* directly. If this method returns true we need to think about stubbing missing default methods.
*/
@@ -198,10 +338,13 @@ public class DefaultMethodClassFixer extends ClassVisitor {
// Note that an exception is that, if a bridge method is for a default interface method, javac
// will NOT generate the bridge method in the implementing class. So we need extra logic to
// handle these bridge methods.
+ return isNonBridgeDefaultMethod(access) && !instanceMethods.contains(name + ":" + desc);
+ }
+
+ private static boolean isNonBridgeDefaultMethod(int access) {
return BitFlags.noneSet(
- access,
- Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC | Opcodes.ACC_BRIDGE | Opcodes.ACC_PRIVATE)
- && !instanceMethods.contains(name + ":" + desc);
+ access,
+ Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC | Opcodes.ACC_BRIDGE | Opcodes.ACC_PRIVATE);
}
/**
@@ -379,6 +522,66 @@ public class DefaultMethodClassFixer extends ClassVisitor {
}
}
+ /**
+ * Detector to determine whether an interface needs to be initialized when it is loaded.
+ *
+ * <p>If the interface has a default method, and its <clinit> initializes any of its fields, then
+ * this interface needs to be initialized.
+ */
+ private static class InterfaceInitializationNecessityDetector extends ClassVisitor {
+
+ private final String internalName;
+ private boolean hasFieldInitializedInClinit;
+ private boolean hasDefaultMethods;
+
+ public InterfaceInitializationNecessityDetector(String internalName) {
+ super(Opcodes.ASM5);
+ this.internalName = internalName;
+ }
+
+ public boolean needsToInitialize() {
+ return hasDefaultMethods && hasFieldInitializedInClinit;
+ }
+
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ super.visit(version, access, name, signature, superName, interfaces);
+ checkState(
+ internalName.equals(name),
+ "Inconsistent internal names: expected=%s, real=%s",
+ internalName,
+ name);
+ checkArgument(
+ BitFlags.isSet(access, Opcodes.ACC_INTERFACE),
+ "This class visitor is only used for interfaces.");
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String desc, String signature, String[] exceptions) {
+ if (!hasDefaultMethods) {
+ hasDefaultMethods = isNonBridgeDefaultMethod(access);
+ }
+ if ("<clinit>".equals(name)) {
+ return new MethodVisitor(Opcodes.ASM5) {
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ if (opcode == Opcodes.PUTSTATIC && internalName.equals(owner)) {
+ hasFieldInitializedInClinit = true;
+ }
+ }
+ };
+ }
+ return null; // Do not care about the code.
+ }
+ }
+
/** Comparator for interfaces that compares by whether interfaces extend one another. */
enum InterfaceComparator implements Comparator<Class<?>> {
INSTANCE;
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
index bbba22e149..74dfa176a1 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
@@ -20,6 +20,7 @@ import static com.google.devtools.build.android.desugar.LambdaClassMaker.LAMBDA_
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import com.google.auto.value.AutoValue;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -625,7 +626,8 @@ class Desugar {
return ioPairListbuilder.build();
}
- private static class ThrowingClassLoader extends ClassLoader {
+ @VisibleForTesting
+ static class ThrowingClassLoader extends ClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.startsWith("java.")) {
@@ -700,7 +702,8 @@ class Desugar {
* closer.
*/
@SuppressWarnings("MustBeClosedChecker")
- private static ImmutableList<InputFileProvider> toRegisteredInputFileProvider(
+ @VisibleForTesting
+ static ImmutableList<InputFileProvider> toRegisteredInputFileProvider(
Closer closer, List<Path> paths) throws IOException {
ImmutableList.Builder<InputFileProvider> builder = new ImmutableList.Builder<>();
for (Path path : paths) {
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java b/src/tools/android/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java
index adcd1e04af..c32ca9ab98 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java
@@ -14,6 +14,7 @@
package com.google.devtools.build.android.desugar;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import javax.annotation.Nullable;
@@ -37,6 +38,9 @@ import org.objectweb.asm.TypePath;
*/
class InterfaceDesugaring extends ClassVisitor {
+ static final String COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME = "$$triggerInterfaceInit";
+ static final String COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC = "()V";
+
static final String COMPANION_SUFFIX = "$$CC";
static final String INTERFACE_STATIC_COMPANION_METHOD_SUFFIX = "$$STATIC$$";
@@ -47,6 +51,7 @@ class InterfaceDesugaring extends ClassVisitor {
private int bytecodeVersion;
private int accessFlags;
@Nullable private ClassVisitor companion;
+ @Nullable private FieldInfo interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit;
public InterfaceDesugaring(
ClassVisitor dest, ClassReaderFactory bootclasspath, GeneratedClassStore store) {
@@ -73,18 +78,50 @@ class InterfaceDesugaring extends ClassVisitor {
@Override
public void visitEnd() {
if (companion != null) {
+ // Emit a method to access the fields of the interfaces that need initialization.
+ emitInterfaceFieldAccessInCompanionMethodToTriggerInterfaceClinit();
companion.visitEnd();
}
super.visitEnd();
}
+ private void emitInterfaceFieldAccessInCompanionMethodToTriggerInterfaceClinit() {
+ if (companion == null
+ || interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit == null) {
+ return;
+ }
+
+ // Create a method to access the interface fields
+ MethodVisitor visitor =
+ checkNotNull(
+ companion.visitMethod(
+ Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC,
+ COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME,
+ COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC,
+ null,
+ null),
+ "Cannot get a method visitor to write out %s to the companion class.",
+ COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME);
+ // Visit the interface field to triger <clinit> of the interface.
+ visitor.visitFieldInsn(
+ Opcodes.GETSTATIC,
+ interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit.owner(),
+ interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit.name(),
+ interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit.desc());
+ visitor.visitInsn(Opcodes.POP);
+ visitor.visitInsn(Opcodes.RETURN);
+ }
+
@Override
public MethodVisitor visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor result;
- if (BitFlags.isSet(accessFlags, Opcodes.ACC_INTERFACE)
- && BitFlags.noneSet(access, Opcodes.ACC_ABSTRACT | Opcodes.ACC_BRIDGE)
- && !"<clinit>".equals(name)) {
+ if (isStaticInitializer(name)) {
+ result =
+ new InterfaceFieldWriteCollector(
+ super.visitMethod(access, name, desc, signature, exceptions));
+ } else if (BitFlags.isSet(accessFlags, Opcodes.ACC_INTERFACE)
+ && BitFlags.noneSet(access, Opcodes.ACC_ABSTRACT | Opcodes.ACC_BRIDGE)) {
checkArgument(BitFlags.noneSet(access, Opcodes.ACC_NATIVE), "Forbidden per JLS ch 9.4");
boolean isLambdaBody =
@@ -140,6 +177,10 @@ class InterfaceDesugaring extends ClassVisitor {
: null;
}
+ private static boolean isStaticInitializer(String methodName) {
+ return "<clinit>".equals(methodName);
+ }
+
private static String normalizeInterfaceMethodName(
String name, boolean isLambda, boolean isStatic) {
String suffix;
@@ -156,6 +197,10 @@ class InterfaceDesugaring extends ClassVisitor {
return name + suffix;
}
+ static String getCompanionClassName(String interfaceName) {
+ return interfaceName + COMPANION_SUFFIX;
+ }
+
/**
* Returns the descriptor of a static method for an instance method with the given receiver and
* description, simply by pre-pending the given descriptor's parameter list with the given
@@ -172,7 +217,7 @@ class InterfaceDesugaring extends ClassVisitor {
private ClassVisitor companion() {
if (companion == null) {
checkState(BitFlags.isSet(accessFlags, Opcodes.ACC_INTERFACE));
- String companionName = internalName + COMPANION_SUFFIX;
+ String companionName = getCompanionClassName(internalName);
companion = store.add(companionName);
companion.visit(
@@ -188,6 +233,32 @@ class InterfaceDesugaring extends ClassVisitor {
}
/**
+ * Interface field scanner to get the field of the current interface that is written in the
+ * initializer.
+ */
+ private class InterfaceFieldWriteCollector extends MethodVisitor {
+
+ public InterfaceFieldWriteCollector(MethodVisitor mv) {
+ super(Opcodes.ASM5, mv);
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ if (interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit == null
+ && opcode == Opcodes.PUTSTATIC) {
+ checkState(
+ owner.equals(internalName),
+ "Expect only the fields in this interface to be initialized. owner=%s, expected=%s",
+ owner,
+ internalName);
+ interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit =
+ FieldInfo.create(owner, name, desc);
+ }
+ super.visitFieldInsn(opcode, owner, name, desc);
+ }
+ }
+
+ /**
* Rewriter for calls to static interface methods and super calls to default methods, unless
* they're part of the bootclasspath, as well as all lambda body methods. Keeps calls to interface
* methods declared in the bootclasspath as-is (but note that these would presumably fail on