diff options
author | kmb <kmb@google.com> | 2018-03-01 12:56:13 -0800 |
---|---|---|
committer | Copybara-Service <copybara-piper@google.com> | 2018-03-01 12:58:30 -0800 |
commit | 4b6c0ec4b54e258763ce22e1a7f529d293aff026 (patch) | |
tree | f4f50ae482c377720ed35feea1a4b33f2b8d60eb /src/tools/android/java/com/google/devtools/build/android/desugar | |
parent | e2cc8868c53d88f12552139f548ab6e685403a73 (diff) |
send invocations to emulated interfaces through dispatch helper.
fix logic for implementing emulated interfaces.
RELNOTES: None.
PiperOrigin-RevId: 187520298
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build/android/desugar')
5 files changed, 176 insertions, 22 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryInvocationRewriter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryInvocationRewriter.java index fb6221998f..0e0610f48d 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryInvocationRewriter.java +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryInvocationRewriter.java @@ -79,8 +79,7 @@ public class CoreLibraryInvocationRewriter extends ClassVisitor { checkArgument(itf, "Expected interface to rewrite %s.%s : %s", owner, name, desc); owner = InterfaceDesugaring.getCompanionClassName(coreInterfaceName); } else { - // TODO(kmb): Simulate dynamic dispatch instead of calling most general default method - owner = InterfaceDesugaring.getCompanionClassName(coreInterfaceName); + owner = coreInterfaceName + "$$Dispatch"; } opcode = Opcodes.INVOKESTATIC; diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java b/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java index 90e6bc0884..c73874e977 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java @@ -20,12 +20,16 @@ import com.google.common.base.Splitter; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.lang.reflect.Method; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Set; import javax.annotation.Nullable; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; @@ -34,6 +38,8 @@ import org.objectweb.asm.Type; */ class CoreLibrarySupport { + private static final Object[] EMPTY_FRAME = new Object[0]; + private final CoreLibraryRewriter rewriter; private final ClassLoader targetLoader; /** Internal name prefixes that we want to move to a custom package. */ @@ -42,15 +48,20 @@ class CoreLibrarySupport { private final ImmutableSet<Class<?>> emulatedInterfaces; /** Map from {@code owner#name} core library members to their new owners. */ private final ImmutableMap<String, String> memberMoves; + private final GeneratedClassStore store; + + private final HashMap<String, ClassVisitor> dispatchHelpers = new HashMap<>(); public CoreLibrarySupport( CoreLibraryRewriter rewriter, ClassLoader targetLoader, + GeneratedClassStore store, List<String> renamedPrefixes, List<String> emulatedInterfaces, List<String> memberMoves) { this.rewriter = rewriter; this.targetLoader = targetLoader; + this.store = store; checkArgument( renamedPrefixes.stream().allMatch(prefix -> prefix.startsWith("java/")), renamedPrefixes); this.renamedPrefixes = ImmutableSet.copyOf(renamedPrefixes); @@ -88,8 +99,7 @@ class CoreLibrarySupport { } // Rename any classes desugar might generate under java/ (for emulated interfaces) as well as // configured prefixes - return unprefixedName.contains("$$Lambda$") - || unprefixedName.endsWith("$$CC") + return looksGenerated(unprefixedName) || renamedPrefixes.stream().anyMatch(prefix -> unprefixedName.startsWith(prefix)); } @@ -113,6 +123,78 @@ class CoreLibrarySupport { return getEmulatedCoreClassOrInterface(internalName) != null; } + /** Includes the given method definition in any applicable core interface emulation logic. */ + public void registerIfEmulatedCoreInterface( + int access, + String owner, + String name, + String desc, + String[] exceptions) { + Class<?> emulated = getEmulatedCoreClassOrInterface(owner); + if (emulated == null) { + return; + } + checkArgument(emulated.isInterface(), "Shouldn't be called for a class: %s.%s", owner, name); + checkArgument( + BitFlags.noneSet( + access, + Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE | Opcodes.ACC_STATIC | Opcodes.ACC_BRIDGE), + "Should only be called for default methods: %s.%s", owner, name); + + ClassVisitor helper = dispatchHelper(owner); + String companionDesc = InterfaceDesugaring.companionDefaultMethodDescriptor(owner, desc); + MethodVisitor dispatchMethod = + helper.visitMethod( + access | Opcodes.ACC_STATIC, + name, + companionDesc, + /*signature=*/ null, // signature is invalid due to extra "receiver" argument + exceptions); + + dispatchMethod.visitCode(); + { + // See if the receiver might come with its own implementation of the method, and call it. + // We do this by testing for the interface type created by EmulatedInterfaceRewriter + Label callCompanion = new Label(); + String emulationInterface = renameCoreLibrary(owner); + dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0); // load "receiver" + dispatchMethod.visitTypeInsn(Opcodes.INSTANCEOF, emulationInterface); + dispatchMethod.visitJumpInsn(Opcodes.IFEQ, callCompanion); + dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0); // load "receiver" + dispatchMethod.visitTypeInsn(Opcodes.CHECKCAST, emulationInterface); + + Type neededType = Type.getMethodType(desc); + visitLoadArgs(dispatchMethod, neededType, 1 /* receiver already loaded above*/); + dispatchMethod.visitMethodInsn( + Opcodes.INVOKEINTERFACE, + emulationInterface, + name, + desc, + /*itf=*/ true); + dispatchMethod.visitInsn(neededType.getReturnType().getOpcode(Opcodes.IRETURN)); + + dispatchMethod.visitLabel(callCompanion); + // Trivial frame for the branch target: same empty stack as before + dispatchMethod.visitFrame(Opcodes.F_SAME, 0, EMPTY_FRAME, 0, EMPTY_FRAME); + } + + // Call static type's default implementation in companion class + Type neededType = Type.getMethodType(companionDesc); + visitLoadArgs(dispatchMethod, neededType, 0); + // TODO(b/70681189): Also test emulated subtypes and call their implementations before falling + // back on static type's default implementation + dispatchMethod.visitMethodInsn( + Opcodes.INVOKESTATIC, + InterfaceDesugaring.getCompanionClassName(owner), + name, + companionDesc, + /*itf=*/ false); + dispatchMethod.visitInsn(neededType.getReturnType().getOpcode(Opcodes.IRETURN)); + + dispatchMethod.visitMaxs(0, 0); + dispatchMethod.visitEnd(); + } + /** * If the given invocation needs to go through a companion class of an emulated or renamed * core interface, this methods returns that interface. This is a helper method for @@ -124,7 +206,7 @@ class CoreLibrarySupport { @Nullable public Class<?> getCoreInterfaceRewritingTarget( int opcode, String owner, String name, String desc, boolean itf) { - if (owner.contains("$$Lambda$") || owner.endsWith("$$CC")) { + if (looksGenerated(owner)) { // Regular desugaring handles generated classes, no emulation is needed return null; } @@ -188,8 +270,13 @@ class CoreLibrarySupport { return null; } - private Class<?> getEmulatedCoreClassOrInterface(String internalName) { - if (internalName.contains("$$Lambda$") || internalName.endsWith("$$CC")) { + /** + * Returns the given class if it's a core library class or interface with emulated default + * methods. This is equivalent to calling {@link #isEmulatedCoreClassOrInterface} and then + * just loading the class (using the target class loader). + */ + public Class<?> getEmulatedCoreClassOrInterface(String internalName) { + if (looksGenerated(internalName)) { // Regular desugaring handles generated classes, no emulation is needed return null; } @@ -215,6 +302,22 @@ class CoreLibrarySupport { } } + private ClassVisitor dispatchHelper(String internalName) { + return dispatchHelpers.computeIfAbsent(internalName, className -> { + className += "$$Dispatch"; + ClassVisitor result = store.add(className); + result.visit( + Opcodes.V1_7, + // Must be public so dispatch methods can be called from anywhere + Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PUBLIC, + className, + /*signature=*/ null, + "java/lang/Object", + new String[0]); + return result; + }); + } + private static Method findInterfaceMethod(Class<?> clazz, String name, String desc) { return collectImplementedInterfaces(clazz, new LinkedHashSet<>()) .stream() @@ -249,4 +352,20 @@ class CoreLibrarySupport { } return dest; } + + /** + * Emits instructions to load a method's parameters as arguments of a method call assumed to have + * compatible descriptor, starting at the given local variable slot. + */ + private static void visitLoadArgs(MethodVisitor dispatchMethod, Type neededType, int slot) { + for (Type arg : neededType.getArgumentTypes()) { + dispatchMethod.visitVarInsn(arg.getOpcode(Opcodes.ILOAD), slot); + slot += arg.getSize(); + } + } + + /** Checks whether the given class is (likely) generated by desugar itself. */ + private static boolean looksGenerated(String owner) { + return owner.contains("$$Lambda$") || owner.endsWith("$$CC") || owner.endsWith("$$Dispatch"); + } } 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 0fad8c2e46..dd1992a6f8 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 @@ -406,6 +406,7 @@ class Desugar { ? new CoreLibrarySupport( rewriter, loader, + store, options.rewriteCoreLibraryPrefixes, options.emulateCoreLibraryInterfaces, options.retargetCoreLibraryMembers) @@ -719,6 +720,7 @@ class Desugar { visitor, interfaceCache, depsCollector, + coreLibrarySupport, bootclasspathReader, loader, store, @@ -800,6 +802,7 @@ class Desugar { visitor, interfaceCache, depsCollector, + coreLibrarySupport, bootclasspathReader, loader, store, diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/EmulatedInterfaceRewriter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/EmulatedInterfaceRewriter.java index d3e786d3f3..f066f2a74c 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/EmulatedInterfaceRewriter.java +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/EmulatedInterfaceRewriter.java @@ -13,18 +13,21 @@ // limitations under the License. package com.google.devtools.build.android.desugar; -import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedHashSet; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; /** * Visitor that renames emulated interfaces and marks classes that extend emulated interfaces to * also implement the renamed interfaces. {@link DefaultMethodClassFixer} makes sure the requisite - * methods are present. Doing this helps with dynamic dispatch on emulated interfaces. + * methods are present in all classes implementing the renamed interface. Doing this helps with + * dynamic dispatch on emulated interfaces. */ public class EmulatedInterfaceRewriter extends ClassVisitor { + private static final String[] EMPTY_ARRAY = new String[0]; + private final CoreLibrarySupport support; public EmulatedInterfaceRewriter(ClassVisitor dest, CoreLibrarySupport support) { @@ -40,24 +43,47 @@ public class EmulatedInterfaceRewriter extends ClassVisitor { String signature, String superName, String[] interfaces) { - boolean isEmulated = support.isEmulatedCoreClassOrInterface(name); - if (interfaces != null && interfaces.length > 0 && !isEmulated) { - // Make classes implementing emulated interfaces also implement the renamed interfaces we - // create below. - ArrayList<String> newInterfaces = new ArrayList<>(interfaces.length + 2); - Collections.addAll(newInterfaces, interfaces); - for (String itf : interfaces) { - if (support.isEmulatedCoreClassOrInterface(itf)) { - newInterfaces.add(support.renameCoreLibrary(itf)); + boolean emulated = support.isEmulatedCoreClassOrInterface(name); + { + // 1. see if we should implement any additional interfaces. + // Use LinkedHashSet to dedupe but maintain deterministic order + LinkedHashSet<String> newInterfaces = new LinkedHashSet<>(); + if (interfaces != null && interfaces.length > 0) { + // Make classes implementing emulated interfaces also implement the renamed interfaces we + // create below. This includes making the renamed interfaces extends each other as needed. + Collections.addAll(newInterfaces, interfaces); + for (String itf : interfaces) { + if (support.isEmulatedCoreClassOrInterface(itf)) { + newInterfaces.add(support.renameCoreLibrary(itf)); + } + } + } + if (!emulated) { + // For an immediate subclass of an emulated class, also fill in any interfaces implemented + // by superclasses, similar to the additional default method stubbing performed in + // DefaultMethodClassFixer in this situation. + Class<?> superclass = support.getEmulatedCoreClassOrInterface(superName); + while (superclass != null) { + for (Class<?> implemented : superclass.getInterfaces()) { + String itf = implemented.getName().replace('.', '/'); + if (support.isEmulatedCoreClassOrInterface(itf)) { + newInterfaces.add(support.renameCoreLibrary(itf)); + } + } + superclass = superclass.getSuperclass(); } } - if (interfaces.length != newInterfaces.size()) { - interfaces = newInterfaces.toArray(interfaces); - signature = null; // additional interfaces invalidate signature + // Update implemented interfaces and signature if we did anything above + if (interfaces == null + ? !newInterfaces.isEmpty() + : interfaces.length != newInterfaces.size()) { + interfaces = newInterfaces.toArray(EMPTY_ARRAY); + signature = null; // additional interfaces invalidate any signature } } - if (BitFlags.isInterface(access) && isEmulated) { + // 2. see if we need to rename this interface itself + if (BitFlags.isInterface(access) && emulated) { name = support.renameCoreLibrary(name); } super.visit(version, access, name, signature, superName, interfaces); 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 f17f114884..0a10df1f43 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 @@ -47,6 +47,7 @@ class InterfaceDesugaring extends ClassVisitor { private final ClassVsInterface interfaceCache; private final DependencyCollector depsCollector; + private final CoreLibrarySupport coreLibrarySupport; private final ClassReaderFactory bootclasspath; private final ClassLoader targetLoader; private final GeneratedClassStore store; @@ -63,6 +64,7 @@ class InterfaceDesugaring extends ClassVisitor { ClassVisitor dest, ClassVsInterface interfaceCache, DependencyCollector depsCollector, + @Nullable CoreLibrarySupport coreLibrarySupport, ClassReaderFactory bootclasspath, ClassLoader targetLoader, GeneratedClassStore store, @@ -70,6 +72,7 @@ class InterfaceDesugaring extends ClassVisitor { super(Opcodes.ASM6, dest); this.interfaceCache = interfaceCache; this.depsCollector = depsCollector; + this.coreLibrarySupport = coreLibrarySupport; this.bootclasspath = bootclasspath; this.targetLoader = targetLoader; this.store = store; @@ -214,6 +217,10 @@ class InterfaceDesugaring extends ClassVisitor { internalName, desc); ++numberOfDefaultMethods; + if (coreLibrarySupport != null) { + coreLibrarySupport.registerIfEmulatedCoreInterface( + access, internalName, name, desc, exceptions); + } abstractDest = super.visitMethod(access | Opcodes.ACC_ABSTRACT, name, desc, signature, exceptions); } |