diff options
Diffstat (limited to 'java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java')
-rw-r--r-- | java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java | 129 |
1 files changed, 124 insertions, 5 deletions
diff --git a/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java b/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java index 90e6bc0..c73874e 100644 --- a/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java +++ b/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"); + } } |