aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java
diff options
context:
space:
mode:
authorGravatar Googler <noreply@google.com>2017-06-20 07:41:10 -0400
committerGravatar Kristina Chodorow <kchodorow@google.com>2017-06-20 14:36:06 -0400
commit2d008e824e4c5c39f00f21cfa879eed61d5b500a (patch)
tree762e250be38b5b2a2191cd911b8466037e7a6824 /src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java
parent44833517b549af9a844e58a2c6978111bda06720 (diff)
Automated rollback of commit 317e0de2113f0361c9dbf98653ceda4efea3f3e2.
*** Original change description *** Access interface constants to explicitly trigger the execution of interface initializers. The problem is that when we desugar default methods, the static intializer of interface will not be executed. The JVM spec says that if an interface has default methods, then when it is loaded, it will also be initialized. After we desugar such an interface, its default methods are removed, and when we load the interface, the <clinit> will not be executed. This CL checks whether an interface has default me... *** ROLLBACK_OF=159496992 NO_SQ=PURE_ROLLBACK RELNOTES: None PiperOrigin-RevId: 159545752
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java209
1 files changed, 3 insertions, 206 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 68f9756735..2563c7a9ab 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,7 +20,6 @@ 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;
@@ -28,11 +27,6 @@ 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
@@ -49,8 +43,6 @@ 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,
@@ -90,88 +82,10 @@ 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) {
@@ -179,11 +93,6 @@ 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);
}
@@ -256,55 +165,6 @@ 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.
*/
@@ -338,13 +198,10 @@ 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);
+ access,
+ Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC | Opcodes.ACC_BRIDGE | Opcodes.ACC_PRIVATE)
+ && !instanceMethods.contains(name + ":" + desc);
}
/**
@@ -522,66 +379,6 @@ 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;