aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build/android/desugar
diff options
context:
space:
mode:
authorGravatar kmb <kmb@google.com>2018-03-01 12:56:13 -0800
committerGravatar Copybara-Service <copybara-piper@google.com>2018-03-01 12:58:30 -0800
commit4b6c0ec4b54e258763ce22e1a7f529d293aff026 (patch)
treef4f50ae482c377720ed35feea1a4b33f2b8d60eb /src/tools/android/java/com/google/devtools/build/android/desugar
parente2cc8868c53d88f12552139f548ab6e685403a73 (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')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryInvocationRewriter.java3
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java129
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java3
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/EmulatedInterfaceRewriter.java56
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java7
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);
}