summaryrefslogtreecommitdiff
path: root/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java')
-rw-r--r--java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java129
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");
+ }
}