diff options
author | kmb <kmb@google.com> | 2018-03-02 14:41:23 -0800 |
---|---|---|
committer | Copybara-Service <copybara-piper@google.com> | 2018-03-02 14:43:19 -0800 |
commit | babbfdc6cb98a23fe0dadf02d7dc407504e9cac5 (patch) | |
tree | cdfb21000b6d902ccf760abe472f357e29e4a35a /src/tools/android/java | |
parent | bcd4536665f4bec34a0bb3efff4154fe52b1a4d7 (diff) |
emulate dynamic dispatch of emulated default interface methods
RELNOTES: None.
PiperOrigin-RevId: 187671513
Diffstat (limited to 'src/tools/android/java')
3 files changed, 156 insertions, 74 deletions
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 e22c596c8e..b90222bd69 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 @@ -16,10 +16,16 @@ package com.google.devtools.build.android.desugar; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; +import com.google.auto.value.AutoValue; import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Multimap; +import com.google.errorprone.annotations.Immutable; import java.lang.reflect.Method; +import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; @@ -39,6 +45,7 @@ import org.objectweb.asm.Type; class CoreLibrarySupport { private static final Object[] EMPTY_FRAME = new Object[0]; + private static final String[] EMPTY_LIST = new String[0]; private final CoreLibraryRewriter rewriter; private final ClassLoader targetLoader; @@ -49,21 +56,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<>(); + /** For the collection of definitions of emulated default methods (deterministic iteration). */ + private final Multimap<String, EmulatedMethod> emulatedDefaultMethods = + LinkedHashMultimap.create(); public CoreLibrarySupport( CoreLibraryRewriter rewriter, ClassLoader targetLoader, - GeneratedClassStore store, List<String> renamedPrefixes, List<String> emulatedInterfaces, List<String> memberMoves, List<String> excludeFromEmulation) { this.rewriter = rewriter; this.targetLoader = targetLoader; - this.store = store; checkArgument( renamedPrefixes.stream().allMatch(prefix -> prefix.startsWith("java/")), renamedPrefixes); this.renamedPrefixes = ImmutableSet.copyOf(renamedPrefixes); @@ -146,59 +152,8 @@ class CoreLibrarySupport { 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(); + emulatedDefaultMethods.put( + name + ":" + desc, EmulatedMethod.create(access, emulated, name, desc, exceptions)); } /** @@ -303,6 +258,130 @@ class CoreLibrarySupport { return null; } + public void makeDispatchHelpers(GeneratedClassStore store) { + HashMap<Class<?>, ClassVisitor> dispatchHelpers = new HashMap<>(); + for (Collection<EmulatedMethod> group : emulatedDefaultMethods.asMap().values()) { + checkState(!group.isEmpty()); + Class<?> root = group + .stream() + .map(EmulatedMethod::owner) + .max(DefaultMethodClassFixer.InterfaceComparator.INSTANCE) + .get(); + checkState(group.stream().map(m -> m.owner()).allMatch(o -> root.isAssignableFrom(o)), + "Not a single unique method: %s", group); + + for (EmulatedMethod methodDefinition : group) { + Class<?> owner = methodDefinition.owner(); + ClassVisitor dispatchHelper = dispatchHelpers.computeIfAbsent(owner, clazz -> { + String className = clazz.getName().replace('.', '/') + "$$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", + EMPTY_LIST); + return result; + }); + + // Types to check for before calling methodDefinition's companion, sub- before super-types + ImmutableList<Class<?>> typechecks = + group + .stream() + .map(EmulatedMethod::owner) + .filter(o -> o != owner && owner.isAssignableFrom(o)) + .distinct() // should already be but just in case + .sorted(DefaultMethodClassFixer.InterfaceComparator.INSTANCE) + .collect(ImmutableList.toImmutableList()); + makeDispatchHelperMethod(dispatchHelper, methodDefinition, typechecks); + } + } + } + + private void makeDispatchHelperMethod( + ClassVisitor helper, EmulatedMethod method, ImmutableList<Class<?>> typechecks) { + String owner = method.owner().getName().replace('.', '/'); + Type methodType = Type.getMethodType(method.descriptor()); + String companionDesc = + InterfaceDesugaring.companionDefaultMethodDescriptor(owner, method.descriptor()); + MethodVisitor dispatchMethod = + helper.visitMethod( + method.access() | Opcodes.ACC_STATIC, + method.name(), + companionDesc, + /*signature=*/ null, // signature is invalid due to extra "receiver" argument + method.exceptions().toArray(EMPTY_LIST)); + + + 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 fallthrough = new Label(); + String emulationInterface = renameCoreLibrary(owner); + dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0); // load "receiver" + dispatchMethod.visitTypeInsn(Opcodes.INSTANCEOF, emulationInterface); + dispatchMethod.visitJumpInsn(Opcodes.IFEQ, fallthrough); + dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0); // load "receiver" + dispatchMethod.visitTypeInsn(Opcodes.CHECKCAST, emulationInterface); + + visitLoadArgs(dispatchMethod, methodType, 1 /* receiver already loaded above */); + dispatchMethod.visitMethodInsn( + Opcodes.INVOKEINTERFACE, + emulationInterface, + method.name(), + method.descriptor(), + /*itf=*/ true); + dispatchMethod.visitInsn(methodType.getReturnType().getOpcode(Opcodes.IRETURN)); + + dispatchMethod.visitLabel(fallthrough); + // Trivial frame for the branch target: same empty stack as before + dispatchMethod.visitFrame(Opcodes.F_SAME, 0, EMPTY_FRAME, 0, EMPTY_FRAME); + } + + // Next, check for emulated subtypes and call their companion methods + for (Class<?> tested : typechecks) { + checkState(tested.isInterface(), "Dispatch emulation not supported for classes: %s", tested); + Label fallthrough = new Label(); + String emulatedInterface = tested.getName().replace('.', '/'); + dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0); // load "receiver" + dispatchMethod.visitTypeInsn(Opcodes.INSTANCEOF, emulatedInterface); + dispatchMethod.visitJumpInsn(Opcodes.IFEQ, fallthrough); + dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0); // load "receiver" + dispatchMethod.visitTypeInsn(Opcodes.CHECKCAST, emulatedInterface); // make verifier happy + + visitLoadArgs(dispatchMethod, methodType, 1 /* receiver already loaded above */); + dispatchMethod.visitMethodInsn( + Opcodes.INVOKESTATIC, + InterfaceDesugaring.getCompanionClassName(emulatedInterface), + method.name(), + InterfaceDesugaring.companionDefaultMethodDescriptor( + emulatedInterface, method.descriptor()), + /*itf=*/ false); + dispatchMethod.visitInsn(methodType.getReturnType().getOpcode(Opcodes.IRETURN)); + + dispatchMethod.visitLabel(fallthrough); + // 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 + dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0); // load "receiver" + visitLoadArgs(dispatchMethod, methodType, 1 /* receiver already loaded above */); + dispatchMethod.visitMethodInsn( + Opcodes.INVOKESTATIC, + InterfaceDesugaring.getCompanionClassName(owner), + method.name(), + companionDesc, + /*itf=*/ false); + dispatchMethod.visitInsn(methodType.getReturnType().getOpcode(Opcodes.IRETURN)); + + dispatchMethod.visitMaxs(0, 0); + dispatchMethod.visitEnd(); + } + private boolean isExcluded(Method method) { String unprefixedOwner = rewriter.unprefix(method.getDeclaringClass().getName().replace('.', '/')); @@ -317,22 +396,6 @@ 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() @@ -383,4 +446,20 @@ class CoreLibrarySupport { private static boolean looksGenerated(String owner) { return owner.contains("$$Lambda$") || owner.endsWith("$$CC") || owner.endsWith("$$Dispatch"); } + + @AutoValue + @Immutable + abstract static class EmulatedMethod { + public static EmulatedMethod create( + int access, Class<?> owner, String name, String desc, @Nullable String[] exceptions) { + return new AutoValue_CoreLibrarySupport_EmulatedMethod(access, owner, name, desc, + exceptions != null ? ImmutableList.copyOf(exceptions) : ImmutableList.of()); + } + + abstract int access(); + abstract Class<?> owner(); + abstract String name(); + abstract String descriptor(); + abstract ImmutableList<String> exceptions(); + } } 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 6143940391..292e14204d 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 @@ -649,6 +649,7 @@ public class DefaultMethodClassFixer extends ClassVisitor { /** Comparator for interfaces that compares by whether interfaces extend one another. */ enum InterfaceComparator implements Comparator<Class<?>> { + /** Orders subtypes before supertypes and breaks ties lexicographically. */ INSTANCE; @Override 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 8b8635bc2a..506a380c12 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 @@ -417,7 +417,6 @@ class Desugar { ? new CoreLibrarySupport( rewriter, loader, - store, options.rewriteCoreLibraryPrefixes, options.emulateCoreLibraryInterfaces, options.retargetCoreLibraryMembers, @@ -627,6 +626,9 @@ class Desugar { @Nullable CoreLibrarySupport coreLibrarySupport) throws IOException { // Write out any classes we generated along the way + if (coreLibrarySupport != null) { + coreLibrarySupport.makeDispatchHelpers(store); + } ImmutableMap<String, ClassNode> generatedClasses = store.drain(); checkState( generatedClasses.isEmpty() || (allowDefaultMethods && outputJava7), |