diff options
author | kmb <kmb@google.com> | 2017-04-26 23:28:11 +0200 |
---|---|---|
committer | Vladimir Moskva <vladmos@google.com> | 2017-04-27 11:18:11 +0200 |
commit | 611ea1cad400879c64af196032222defd47fb82b (patch) | |
tree | fe5fce6bc5f0346213ca560ef0fd445e293115a1 /src/tools/android/java/com/google | |
parent | d28c5ba9eda6ae0acc2f242c76c90b0acdd6d3c5 (diff) |
Desugar default and static interface methods by default.
RELNOTES: none
PiperOrigin-RevId: 154344780
Diffstat (limited to 'src/tools/android/java/com/google')
-rw-r--r-- | src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java | 83 | ||||
-rw-r--r-- | src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java | 2 |
2 files changed, 80 insertions, 5 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 a9e86a1575..8ad5dc285b 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 @@ -66,7 +66,7 @@ public class DefaultMethodClassFixer extends ClassVisitor { @Override public void visitEnd() { - if (!isInterface && !interfaces.isEmpty()) { + if (!isInterface && defaultMethodsDefined(interfaces)) { // Inherited methods take precedence over default methods, so visit all superclasses and // figure out what methods they declare before stubbing in any missing default methods. recordInheritedMethods(); @@ -79,7 +79,7 @@ public class DefaultMethodClassFixer extends ClassVisitor { public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions) { // Keep track of instance methods implemented in this class for later. - if (!isInterface && !interfaces.isEmpty()) { + if (!isInterface) { recordIfInstanceMethod(access, name, desc); } return super.visitMethod(access, name, desc, signature, exceptions); @@ -91,7 +91,8 @@ public class DefaultMethodClassFixer extends ClassVisitor { while (internalName != null) { ClassReader bytecode = bootclasspath.readIfKnown(internalName); if (bytecode == null) { - bytecode = checkNotNull(classpath.readIfKnown(internalName), "Not found: %s", internalName); + bytecode = checkNotNull(classpath.readIfKnown(internalName), + "Superclass not found: %s", internalName); } bytecode.accept(recorder, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG); internalName = bytecode.getSuperName(); @@ -106,6 +107,31 @@ public class DefaultMethodClassFixer extends ClassVisitor { } } + /** + * 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. + */ + private boolean defaultMethodsDefined(ImmutableList<String> interfaces) { + for (String implemented : interfaces) { + ClassReader bytecode = classpath.readIfKnown(implemented); + if (bytecode != null && !bootclasspath.isKnown(implemented)) { + // Class in classpath and bootclasspath is a bad idea but in any event, assume the + // bootclasspath will take precedence like in a classloader. + // We can skip code attributes as we just need to find default methods to stub. + DefaultMethodFinder finder = new DefaultMethodFinder(); + bytecode.accept(finder, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG); + if (finder.foundDefaultMethods()) { + return true; + } + } + // Else interface isn't on the classpath, which indicates incomplete classpaths. For now + // we'll just assume the missing interfaces don't declare default methods but if they do + // we'll end up with concrete classes that don't implement an abstract method, which can + // cause runtime failures. The classpath needs to be fixed in this case. + } + return false; + } + private void stubMissingDefaultMethods(ImmutableList<String> interfaces) { for (String implemented : interfaces) { if (!seenInterfaces.add(implemented)) { @@ -158,7 +184,7 @@ public class DefaultMethodClassFixer extends ClassVisitor { public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions) { if (BitFlags.noneSet(access, Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC | Opcodes.ACC_BRIDGE) - && instanceMethods.add(name + ":" + desc)) { + && !instanceMethods.contains(name + ":" + desc)) { // Add this method to the class we're desugaring and stub in a body to call the default // implementation in the interface's companion class. ijar omits these methods when setting // ACC_SYNTHETIC modifier, so don't. Don't do this for bridge methods, which we handle @@ -190,6 +216,55 @@ public class DefaultMethodClassFixer extends ClassVisitor { } } + /** + * Visitor for interfaces that recursively searches interfaces for default method declarations. + */ + public class DefaultMethodFinder extends ClassVisitor { + + @SuppressWarnings("hiding") private ImmutableList<String> interfaces; + private boolean found; + + public DefaultMethodFinder() { + super(Opcodes.ASM5); + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + checkArgument(BitFlags.isSet(access, Opcodes.ACC_INTERFACE)); + checkState(this.interfaces == null); + this.interfaces = ImmutableList.copyOf(interfaces); + } + + public boolean foundDefaultMethods() { + return found; + } + + @Override + public void visitEnd() { + if (!found) { + found = defaultMethodsDefined(this.interfaces); + } + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + if (BitFlags.noneSet(access, Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC | Opcodes.ACC_BRIDGE) + && !instanceMethods.contains(name + ":" + desc)) { + // Found a default method we're not ignoring (instanceMethods at this point contains methods + // the top-level visited class implements itself). + found = true; + } + return null; // we don't care about the actual code in these methods + } + } + private class InstanceMethodRecorder extends ClassVisitor { public InstanceMethodRecorder() { 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 5e24b3560f..8f4c68a6f8 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 @@ -150,7 +150,7 @@ class Desugar { @Option( name = "desugar_interface_method_bodies_if_needed", - defaultValue = "false", + defaultValue = "true", category = "misc", help = "Rewrites default and static methods in interfaces if --min_sdk_version < 24. This " + "only works correctly if subclasses of rewritten interfaces as well as uses of static " |