From 17d008dc0cc602c46135f4a4f6f55a6d93431d77 Mon Sep 17 00:00:00 2001 From: kmb Date: Thu, 8 Feb 2018 18:11:29 -0800 Subject: Stub default methods as needed for core library desugaring RELNOTES: None PiperOrigin-RevId: 185082719 GitOrigin-RevId: aa79fd483daff0db9be274c33de109257f8a6804 Change-Id: I90cad779653c93f9917f69fe06daad2bbf919f65 --- .../android/desugar/DefaultMethodClassFixer.java | 130 ++++++++++++++------- 1 file changed, 91 insertions(+), 39 deletions(-) (limited to 'java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java') diff --git a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java index 1aaf0b6..2eda141 100644 --- a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java +++ b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java @@ -23,6 +23,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.TreeSet; +import javax.annotation.Nullable; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; @@ -44,6 +45,7 @@ public class DefaultMethodClassFixer extends ClassVisitor { private final ClassReaderFactory bootclasspath; private final ClassLoader targetLoader; private final DependencyCollector depsCollector; + @Nullable private final CoreLibrarySupport coreLibrarySupport; private final HashSet instanceMethods = new HashSet<>(); private boolean isInterface; @@ -57,10 +59,12 @@ public class DefaultMethodClassFixer extends ClassVisitor { ClassVisitor dest, ClassReaderFactory classpath, DependencyCollector depsCollector, + @Nullable CoreLibrarySupport coreLibrarySupport, ClassReaderFactory bootclasspath, ClassLoader targetLoader) { super(Opcodes.ASM6, dest); this.classpath = classpath; + this.coreLibrarySupport = coreLibrarySupport; this.bootclasspath = bootclasspath; this.targetLoader = targetLoader; this.depsCollector = depsCollector; @@ -88,7 +92,9 @@ public class DefaultMethodClassFixer extends ClassVisitor { @Override public void visitEnd() { - if (!isInterface && defaultMethodsDefined(directInterfaces)) { + if (!isInterface + && (mayNeedInterfaceStubsForEmulatedSuperclass() + || defaultMethodsDefined(directInterfaces))) { // 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(); @@ -190,6 +196,12 @@ public class DefaultMethodClassFixer extends ClassVisitor { return super.visitMethod(access, name, desc, signature, exceptions); } + private boolean mayNeedInterfaceStubsForEmulatedSuperclass() { + return coreLibrarySupport != null + && !coreLibrarySupport.isEmulatedCoreClassOrInterface(internalName) + && coreLibrarySupport.isEmulatedCoreClassOrInterface(superName); + } + private void stubMissingDefaultAndBridgeMethods() { TreeSet> allInterfaces = new TreeSet<>(InterfaceComparator.INSTANCE); for (String direct : directInterfaces) { @@ -203,19 +215,51 @@ public class DefaultMethodClassFixer extends ClassVisitor { } Class superclass = loadFromInternal(superName); + boolean mayNeedStubsForSuperclass = mayNeedInterfaceStubsForEmulatedSuperclass(); + if (mayNeedStubsForSuperclass) { + // Collect interfaces inherited from emulated superclasses as well, to handle things like + // extending AbstractList without explicitly implementing List. + for (Class clazz = superclass; clazz != null; clazz = clazz.getSuperclass()) { + for (Class itf : superclass.getInterfaces()) { + collectInterfaces(itf, allInterfaces); + } + } + } for (Class interfaceToVisit : allInterfaces) { // if J extends I, J is allowed to redefine I's default methods. The comparator we used // above makes sure we visit J before I in that case so we can use J's definition. - if (superclass != null && interfaceToVisit.isAssignableFrom(superclass)) { - // superclass already implements this interface, so we must skip it. The superclass will - // be similarly rewritten or comes from the bootclasspath; either way we don't need to and - // shouldn't stub default methods for this interface. + if (!mayNeedStubsForSuperclass && interfaceToVisit.isAssignableFrom(superclass)) { + // superclass is also rewritten and already implements this interface, so we _must_ skip it. continue; } stubMissingDefaultAndBridgeMethods(interfaceToVisit.getName().replace('.', '/')); } } + private void stubMissingDefaultAndBridgeMethods(String implemented) { + ClassReader bytecode; + boolean isBootclasspath; + if (bootclasspath.isKnown(implemented)) { + if (coreLibrarySupport != null + && (coreLibrarySupport.isRenamedCoreLibrary(implemented) + || coreLibrarySupport.isEmulatedCoreClassOrInterface(implemented))) { + bytecode = checkNotNull(bootclasspath.readIfKnown(implemented), implemented); + isBootclasspath = true; + } else { + // Default methods from interfaces on the bootclasspath that we're not renaming or emulating + // are assumed available at runtime, so just ignore them. + return; + } + } else { + bytecode = + checkNotNull( + classpath.readIfKnown(implemented), + "Couldn't find interface %s implemented by %s", implemented, internalName); + isBootclasspath = false; + } + bytecode.accept(new DefaultMethodStubber(isBootclasspath), ClassReader.SKIP_DEBUG); + } + private Class loadFromInternal(String internalName) { try { return targetLoader.loadClass(internalName.replace('/', '.')); @@ -313,26 +357,38 @@ public class DefaultMethodClassFixer extends ClassVisitor { */ private boolean defaultMethodsDefined(ImmutableList interfaces) { for (String implemented : interfaces) { + ClassReader bytecode; if (bootclasspath.isKnown(implemented)) { - continue; - } - ClassReader bytecode = classpath.readIfKnown(implemented); - if (bytecode == null) { - // Interface isn't on the classpath, which indicates incomplete classpaths. Record missing - // dependency so we can check it later. If we don't check then we may get runtime failures - // or wrong behavior from default methods that should've been stubbed in. - // TODO(kmb): Print a warning so people can start fixing their deps? - depsCollector.missingImplementedInterface(internalName, implemented); + if (coreLibrarySupport != null + && coreLibrarySupport.isEmulatedCoreClassOrInterface(implemented)) { + return true; // need to stub in emulated interface methods such as Collection.stream() + } else if (coreLibrarySupport != null + && coreLibrarySupport.isRenamedCoreLibrary(implemented)) { + // Check default methods of renamed interfaces + bytecode = checkNotNull(bootclasspath.readIfKnown(implemented), implemented); + } else { + continue; + } } else { - // 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; + bytecode = classpath.readIfKnown(implemented); + if (bytecode == null) { + // Interface isn't on the classpath, which indicates incomplete classpaths. Record missing + // dependency so we can check it later. If we don't check then we may get runtime + // failures or wrong behavior from default methods that should've been stubbed in. + // TODO(kmb): Print a warning so people can start fixing their deps? + depsCollector.missingImplementedInterface(internalName, implemented); + continue; } } + + // 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; + } } return false; } @@ -365,30 +421,18 @@ public class DefaultMethodClassFixer extends ClassVisitor { && !instanceMethods.contains(name + ":" + desc); } - private void stubMissingDefaultAndBridgeMethods(String implemented) { - if (bootclasspath.isKnown(implemented)) { - // Default methods on the bootclasspath will be available at runtime, so just ignore them. - return; - } - ClassReader bytecode = - checkNotNull( - classpath.readIfKnown(implemented), - "Couldn't find interface %s implemented by %s", - implemented, - internalName); - bytecode.accept(new DefaultMethodStubber(), ClassReader.SKIP_DEBUG); - } - /** * Visitor for interfaces that produces delegates in the class visited by the outer {@link * DefaultMethodClassFixer} for every default method encountered. */ private class DefaultMethodStubber extends ClassVisitor { + private final boolean isBootclasspathInterface; private String stubbedInterfaceName; - public DefaultMethodStubber() { + public DefaultMethodStubber(boolean isBootclasspathInterface) { super(Opcodes.ASM6); + this.isBootclasspathInterface = isBootclasspathInterface; } @Override @@ -413,8 +457,11 @@ public class DefaultMethodClassFixer extends ClassVisitor { // definitions conflict, but see stubMissingDefaultMethods() for how we deal with default // methods redefined in interfaces extending another. recordIfInstanceMethod(access, name, desc); - depsCollector.assumeCompanionClass( - internalName, InterfaceDesugaring.getCompanionClassName(stubbedInterfaceName)); + if (!isBootclasspathInterface) { + // Don't record these dependencies, as we can't check them + depsCollector.assumeCompanionClass( + internalName, InterfaceDesugaring.getCompanionClassName(stubbedInterfaceName)); + } // 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 @@ -444,6 +491,10 @@ public class DefaultMethodClassFixer extends ClassVisitor { return null; } else if (shouldStubAsBridgeDefaultMethod(access, name, desc)) { recordIfInstanceMethod(access, name, desc); + // If we're visiting a bootclasspath interface then we most likely don't have the code. + // That means we can't just copy the method bodies as we're trying to do below. + checkState(!isBootclasspathInterface, + "TODO stub bridge methods for core interfaces if ever needed"); // For bridges we just copy their bodies instead of going through the companion class. // Meanwhile, we also need to desugar the copied method bodies, so that any calls to // interface methods are correctly handled. @@ -454,7 +505,7 @@ public class DefaultMethodClassFixer extends ClassVisitor { depsCollector, internalName); } else { - return null; // we don't care about the actual code in these methods + return null; // not a default or bridge method or the class already defines this method. } } } @@ -529,6 +580,7 @@ public class DefaultMethodClassFixer extends ClassVisitor { @Override public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions) { + // TODO(kmb): what if this method only exists on some devices, e.g., ArrayList.spliterator? recordIfInstanceMethod(access, name, desc); return null; } -- cgit v1.2.3 From 25d5064e7f8cea5babb248b709348077a4f376bd Mon Sep 17 00:00:00 2001 From: kmb Date: Thu, 15 Feb 2018 10:43:41 -0800 Subject: Resolve the owner of interface.super calls to inherited default methods for android desugaring RELNOTES: None. PiperOrigin-RevId: 185863194 GitOrigin-RevId: c8e8749adc7b98c272b2421569dc97a88d487771 Change-Id: I063c2caa4b38fff2f9111f9fc09c317a5b097834 --- .../google/devtools/build/android/desugar/DefaultMethodClassFixer.java | 1 + 1 file changed, 1 insertion(+) (limited to 'java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java') diff --git a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java index 2eda141..52964b7 100644 --- a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java +++ b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java @@ -502,6 +502,7 @@ public class DefaultMethodClassFixer extends ClassVisitor { DefaultMethodClassFixer.this.visitMethod(access, name, desc, (String) null, exceptions), stubbedInterfaceName, bootclasspath, + targetLoader, depsCollector, internalName); } else { -- cgit v1.2.3 From 669a724b8244e89d40ffd2ea0390d05c078857a3 Mon Sep 17 00:00:00 2001 From: kmb Date: Tue, 20 Feb 2018 20:16:39 -0800 Subject: Apply interface invocation desugaring to renamed core libraries. Fix invokespecial invocations for core interfaces. RELNOTES: None. PiperOrigin-RevId: 186404206 GitOrigin-RevId: f4d2dad976907abea8a727a8360c2e4e087b893f Change-Id: Ic6ddd94802f83596c35999db68ad3b28bdc93c73 --- .../google/devtools/build/android/desugar/DefaultMethodClassFixer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java') diff --git a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java index 52964b7..6143940 100644 --- a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java +++ b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java @@ -494,7 +494,7 @@ public class DefaultMethodClassFixer extends ClassVisitor { // If we're visiting a bootclasspath interface then we most likely don't have the code. // That means we can't just copy the method bodies as we're trying to do below. checkState(!isBootclasspathInterface, - "TODO stub bridge methods for core interfaces if ever needed"); + "TODO stub core interface %s bridge methods in %s", stubbedInterfaceName, internalName); // For bridges we just copy their bodies instead of going through the companion class. // Meanwhile, we also need to desugar the copied method bodies, so that any calls to // interface methods are correctly handled. -- cgit v1.2.3 From 1c433fd1116c4ca655503e7cffa13679c31f0b99 Mon Sep 17 00:00:00 2001 From: kmb Date: Fri, 2 Mar 2018 14:41:23 -0800 Subject: emulate dynamic dispatch of emulated default interface methods RELNOTES: None. PiperOrigin-RevId: 187671513 GitOrigin-RevId: babbfdc6cb98a23fe0dadf02d7dc407504e9cac5 Change-Id: Ie23b521a82464d07f625cefad8418c502f0978f0 --- .../google/devtools/build/android/desugar/DefaultMethodClassFixer.java | 1 + 1 file changed, 1 insertion(+) (limited to 'java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java') diff --git a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java index 6143940..292e142 100644 --- a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java +++ b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java @@ -649,6 +649,7 @@ public class DefaultMethodClassFixer extends ClassVisitor { /** Comparator for interfaces that compares by whether interfaces extend one another. */ enum InterfaceComparator implements Comparator> { + /** Orders subtypes before supertypes and breaks ties lexicographically. */ INSTANCE; @Override -- cgit v1.2.3 From 2042d8d9fe7d52111447ac07e45c161d48cb17d7 Mon Sep 17 00:00:00 2001 From: kmb Date: Mon, 12 Mar 2018 12:20:48 -0700 Subject: Support custom implementations of emulated core interface methods RELNOTES: None. PiperOrigin-RevId: 188760099 GitOrigin-RevId: bff3472e4013c053e452fad7948ad68c5cbd5692 Change-Id: I6fe0153afa5bb57d27da9ca43f2a6796c8907e95 --- .../devtools/build/android/desugar/DefaultMethodClassFixer.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java') diff --git a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java index 292e142..853ed09 100644 --- a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java +++ b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java @@ -203,7 +203,7 @@ public class DefaultMethodClassFixer extends ClassVisitor { } private void stubMissingDefaultAndBridgeMethods() { - TreeSet> allInterfaces = new TreeSet<>(InterfaceComparator.INSTANCE); + TreeSet> allInterfaces = new TreeSet<>(SubtypeComparator.INSTANCE); for (String direct : directInterfaces) { // Loading ensures all transitively implemented interfaces can be loaded, which is necessary // to produce correct default method stubs in all cases. We could do without classloading but @@ -647,18 +647,17 @@ public class DefaultMethodClassFixer extends ClassVisitor { } } - /** Comparator for interfaces that compares by whether interfaces extend one another. */ - enum InterfaceComparator implements Comparator> { + /** Comparator for classes and interfaces that compares by whether subtyping relationship. */ + enum SubtypeComparator implements Comparator> { /** Orders subtypes before supertypes and breaks ties lexicographically. */ INSTANCE; @Override public int compare(Class o1, Class o2) { - checkArgument(o1.isInterface()); - checkArgument(o2.isInterface()); if (o1 == o2) { return 0; } + // order subtypes before supertypes if (o1.isAssignableFrom(o2)) { // o1 is supertype of o2 return 1; // we want o1 to come after o2 } -- cgit v1.2.3 From 44b53edaa9c5611c75e08da2fdc5d91c7b0ac6ae Mon Sep 17 00:00:00 2001 From: kmb Date: Mon, 12 Mar 2018 21:37:51 -0700 Subject: Make KeepScanner tool search classpath for nearest definition of each member reference, instead of potentially referring to a subtype. Refactor desugar's class loading machinery and related code into a separate package for easier reuse in this tool. RELNOTES: None. PiperOrigin-RevId: 188825305 GitOrigin-RevId: 2cbeb24a9c41c6b14ecbb26e2e198fbaf79aea64 Change-Id: Ie2969cb1e1c86aa68c5a6dc0be6b42b09dfaee70 --- .../google/devtools/build/android/desugar/DefaultMethodClassFixer.java | 1 + 1 file changed, 1 insertion(+) (limited to 'java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java') diff --git a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java index 853ed09..960cfeb 100644 --- a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java +++ b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import com.google.common.collect.ImmutableList; +import com.google.devtools.build.android.desugar.io.BitFlags; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; -- cgit v1.2.3 From faa0690d2152c090607a8db136ba9104bc22bb4e Mon Sep 17 00:00:00 2001 From: kmb Date: Fri, 16 Mar 2018 18:52:15 -0700 Subject: Reflect core library moves in super calls, even in default method stubs. Always generate default method stubs for emulated methods. RELNOTES: None. PiperOrigin-RevId: 189423933 GitOrigin-RevId: 44a26afb091f2d23d68bcad53e45a319b299867a Change-Id: I8eaecb5a1a29051a14d0529005a56a225b2f4d8b --- .../android/desugar/DefaultMethodClassFixer.java | 59 ++++++++++++++++++---- 1 file changed, 49 insertions(+), 10 deletions(-) (limited to 'java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java') diff --git a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java index 960cfeb..f0fc6d1 100644 --- a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java +++ b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java @@ -233,11 +233,13 @@ public class DefaultMethodClassFixer extends ClassVisitor { // superclass is also rewritten and already implements this interface, so we _must_ skip it. continue; } - stubMissingDefaultAndBridgeMethods(interfaceToVisit.getName().replace('.', '/')); + stubMissingDefaultAndBridgeMethods( + interfaceToVisit.getName().replace('.', '/'), mayNeedStubsForSuperclass); } } - private void stubMissingDefaultAndBridgeMethods(String implemented) { + private void stubMissingDefaultAndBridgeMethods( + String implemented, boolean mayNeedStubsForSuperclass) { ClassReader bytecode; boolean isBootclasspath; if (bootclasspath.isKnown(implemented)) { @@ -258,7 +260,9 @@ public class DefaultMethodClassFixer extends ClassVisitor { "Couldn't find interface %s implemented by %s", implemented, internalName); isBootclasspath = false; } - bytecode.accept(new DefaultMethodStubber(isBootclasspath), ClassReader.SKIP_DEBUG); + bytecode.accept( + new DefaultMethodStubber(isBootclasspath, mayNeedStubsForSuperclass), + ClassReader.SKIP_DEBUG); } private Class loadFromInternal(String internalName) { @@ -281,7 +285,8 @@ public class DefaultMethodClassFixer extends ClassVisitor { } private void recordInheritedMethods() { - InstanceMethodRecorder recorder = new InstanceMethodRecorder(); + InstanceMethodRecorder recorder = + new InstanceMethodRecorder(mayNeedInterfaceStubsForEmulatedSuperclass()); String internalName = superName; while (internalName != null) { ClassReader bytecode = bootclasspath.readIfKnown(internalName); @@ -429,11 +434,15 @@ public class DefaultMethodClassFixer extends ClassVisitor { private class DefaultMethodStubber extends ClassVisitor { private final boolean isBootclasspathInterface; + private final boolean mayNeedStubsForSuperclass; + private String stubbedInterfaceName; - public DefaultMethodStubber(boolean isBootclasspathInterface) { + public DefaultMethodStubber( + boolean isBootclasspathInterface, boolean mayNeedStubsForSuperclass) { super(Opcodes.ASM6); this.isBootclasspathInterface = isBootclasspathInterface; + this.mayNeedStubsForSuperclass = mayNeedStubsForSuperclass; } @Override @@ -472,6 +481,21 @@ public class DefaultMethodClassFixer extends ClassVisitor { MethodVisitor stubMethod = DefaultMethodClassFixer.this.visitMethod(access, name, desc, (String) null, exceptions); + String receiverName = stubbedInterfaceName; + String owner = InterfaceDesugaring.getCompanionClassName(stubbedInterfaceName); + if (mayNeedStubsForSuperclass) { + // Reflect what CoreLibraryInvocationRewriter would do if it encountered a super-call to a + // moved implementation of an emulated method. Equivalent to emitting the invokespecial + // super call here and relying on CoreLibraryInvocationRewriter for the rest + Class emulatedImplementation = + coreLibrarySupport.getCoreInterfaceRewritingTarget( + Opcodes.INVOKESPECIAL, superName, name, desc, /*itf=*/ false); + if (emulatedImplementation != null && !emulatedImplementation.isInterface()) { + receiverName = emulatedImplementation.getName().replace('.', '/'); + owner = checkNotNull(coreLibrarySupport.getMoveTarget(receiverName, name)); + } + } + int slot = 0; stubMethod.visitVarInsn(Opcodes.ALOAD, slot++); // load the receiver Type neededType = Type.getMethodType(desc); @@ -481,10 +505,10 @@ public class DefaultMethodClassFixer extends ClassVisitor { } stubMethod.visitMethodInsn( Opcodes.INVOKESTATIC, - InterfaceDesugaring.getCompanionClassName(stubbedInterfaceName), + owner, name, - InterfaceDesugaring.companionDefaultMethodDescriptor(stubbedInterfaceName, desc), - /*itf*/ false); + InterfaceDesugaring.companionDefaultMethodDescriptor(receiverName, desc), + /*itf=*/ false); stubMethod.visitInsn(neededType.getReturnType().getOpcode(Opcodes.IRETURN)); stubMethod.visitMaxs(0, 0); // rely on class writer to compute these @@ -563,8 +587,13 @@ public class DefaultMethodClassFixer extends ClassVisitor { private class InstanceMethodRecorder extends ClassVisitor { - public InstanceMethodRecorder() { + private final boolean ignoreEmulatedMethods; + + private String className; + + public InstanceMethodRecorder(boolean ignoreEmulatedMethods) { super(Opcodes.ASM6); + this.ignoreEmulatedMethods = ignoreEmulatedMethods; } @Override @@ -576,13 +605,23 @@ public class DefaultMethodClassFixer extends ClassVisitor { String superName, String[] interfaces) { checkArgument(BitFlags.noneSet(access, Opcodes.ACC_INTERFACE)); + className = name; // updated every time we start visiting another superclass super.visit(version, access, name, signature, superName, interfaces); } @Override public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions) { - // TODO(kmb): what if this method only exists on some devices, e.g., ArrayList.spliterator? + if (ignoreEmulatedMethods + && BitFlags.noneSet(access, Opcodes.ACC_STATIC) // short-circuit + && coreLibrarySupport.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEVIRTUAL, className, name, desc, /*itf=*/ false) + != null) { + // *don't* record emulated core library method implementations in immediate subclasses of + // emulated core library clasess so that they can be stubbed (since the inherited + // implementation may be missing at runtime). + return null; + } recordIfInstanceMethod(access, name, desc); return null; } -- cgit v1.2.3 From 6227f0cb24cbe58be9847a356e97205a5ba17f61 Mon Sep 17 00:00:00 2001 From: kmb Date: Mon, 26 Mar 2018 18:38:07 -0700 Subject: stub simple core library bridge methods that only differ in return type RELNOTES: None. PiperOrigin-RevId: 190559240 GitOrigin-RevId: 327c74df7c3b4820a0620bf9696c3f88bffebda3 Change-Id: I0f9a4718ff0e8714e3133ecf0ef528bb7a039bba --- .../android/desugar/DefaultMethodClassFixer.java | 85 ++++++++++++++++++---- 1 file changed, 72 insertions(+), 13 deletions(-) (limited to 'java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java') diff --git a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java index f0fc6d1..1de48bf 100644 --- a/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java +++ b/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java @@ -19,6 +19,8 @@ import static com.google.common.base.Preconditions.checkState; import com.google.common.collect.ImmutableList; import com.google.devtools.build.android.desugar.io.BitFlags; +import java.lang.reflect.Method; +import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; @@ -513,27 +515,84 @@ public class DefaultMethodClassFixer extends ClassVisitor { stubMethod.visitMaxs(0, 0); // rely on class writer to compute these stubMethod.visitEnd(); - return null; + return null; // don't visit the visited interface's default method } else if (shouldStubAsBridgeDefaultMethod(access, name, desc)) { recordIfInstanceMethod(access, name, desc); + MethodVisitor stubMethod = + DefaultMethodClassFixer.this.visitMethod(access, name, desc, (String) null, exceptions); // If we're visiting a bootclasspath interface then we most likely don't have the code. // That means we can't just copy the method bodies as we're trying to do below. - checkState(!isBootclasspathInterface, - "TODO stub core interface %s bridge methods in %s", stubbedInterfaceName, internalName); - // For bridges we just copy their bodies instead of going through the companion class. - // Meanwhile, we also need to desugar the copied method bodies, so that any calls to - // interface methods are correctly handled. - return new InterfaceDesugaring.InterfaceInvocationRewriter( - DefaultMethodClassFixer.this.visitMethod(access, name, desc, (String) null, exceptions), - stubbedInterfaceName, - bootclasspath, - targetLoader, - depsCollector, - internalName); + if (isBootclasspathInterface) { + // Synthesize a "bridge" method that calls the true implementation + Method bridged = findBridgedMethod(name, desc); + checkState(bridged != null, + "TODO: Can't stub core interface bridge method %s.%s %s in %s", + stubbedInterfaceName, name, desc, internalName); + + int slot = 0; + stubMethod.visitVarInsn(Opcodes.ALOAD, slot++); // load the receiver + Type neededType = Type.getType(bridged); + for (Type arg : neededType.getArgumentTypes()) { + // TODO(b/73586397): insert downcasts if necessary + stubMethod.visitVarInsn(arg.getOpcode(Opcodes.ILOAD), slot); + slot += arg.getSize(); + } + // Just call the bridged method directly on the visited class using invokevirtual + stubMethod.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + internalName, + name, + neededType.getDescriptor(), + /*itf=*/ false); + stubMethod.visitInsn(neededType.getReturnType().getOpcode(Opcodes.IRETURN)); + + stubMethod.visitMaxs(0, 0); // rely on class writer to compute these + stubMethod.visitEnd(); + return null; // don't visit the visited interface's bridge method + } else { + // For bridges we just copy their bodies instead of going through the companion class. + // Meanwhile, we also need to desugar the copied method bodies, so that any calls to + // interface methods are correctly handled. + return new InterfaceDesugaring.InterfaceInvocationRewriter( + stubMethod, + stubbedInterfaceName, + bootclasspath, + targetLoader, + depsCollector, + internalName); + } } else { return null; // not a default or bridge method or the class already defines this method. } } + + /** + * Returns a non-bridge interface method with given name that a method with the given descriptor + * can bridge to, if any such method can be found. + */ + @Nullable + private Method findBridgedMethod(String name, String desc) { + Type[] paramTypes = Type.getArgumentTypes(desc); + Class itf = loadFromInternal(stubbedInterfaceName); + checkArgument(itf.isInterface(), "Should be an interface: %s", stubbedInterfaceName); + Method result = null; + for (Method m : itf.getDeclaredMethods()) { + if (m.isBridge()) { + continue; + } + if (!m.getName().equals(name)) { + continue; + } + // For now, only support specialized return types (which don't require casts) + // TODO(b/73586397): Make this work for other kinds of bridges in core library interfaces + if (Arrays.equals(paramTypes, Type.getArgumentTypes(m))) { + checkState(result == null, + "Found multiple bridge target %s and %s for descriptor %s", result, m, desc); + return result = m; + } + } + return result; + } } /** -- cgit v1.2.3