aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar kmb <kmb@google.com>2018-02-05 18:18:15 -0800
committerGravatar Copybara-Service <copybara-piper@google.com>2018-02-05 18:21:02 -0800
commit1324318ea0fe60350c0a5179818fc1c97d4ec854 (patch)
tree95e900cd38111f5b380fb703880b7b3fb3b91c27
parent1eeba9c9f90e81fad1d1a82163c3b8b2f62f761e (diff)
Basic tooling to desugar select core libraries
RELNOTES: None. PiperOrigin-RevId: 184619885
-rw-r--r--src/test/java/com/google/devtools/build/android/desugar/CoreLibrarySupportTest.java232
-rw-r--r--src/test/java/com/google/devtools/build/android/desugar/CorePackageRenamerTest.java90
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryInvocationRewriter.java81
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryRewriter.java6
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java152
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/CorePackageRenamer.java54
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java72
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java3
8 files changed, 681 insertions, 9 deletions
diff --git a/src/test/java/com/google/devtools/build/android/desugar/CoreLibrarySupportTest.java b/src/test/java/com/google/devtools/build/android/desugar/CoreLibrarySupportTest.java
new file mode 100644
index 0000000000..d7fcad463c
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/CoreLibrarySupportTest.java
@@ -0,0 +1,232 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.desugar;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(JUnit4.class)
+public class CoreLibrarySupportTest {
+
+ @Test
+ public void testIsRenamedCoreLibrary() throws Exception {
+ CoreLibrarySupport support =
+ new CoreLibrarySupport(
+ new CoreLibraryRewriter(""), null, ImmutableList.of("java/time/"), ImmutableList.of());
+ assertThat(support.isRenamedCoreLibrary("java/time/X")).isTrue();
+ assertThat(support.isRenamedCoreLibrary("java/time/y/X")).isTrue();
+ assertThat(support.isRenamedCoreLibrary("java/io/X")).isFalse();
+ assertThat(support.isRenamedCoreLibrary("com/google/X")).isFalse();
+ }
+
+ @Test
+ public void testIsRenamedCoreLibrary_prefixedLoader() throws Exception {
+ CoreLibrarySupport support =
+ new CoreLibrarySupport(
+ new CoreLibraryRewriter("__/"),
+ null,
+ ImmutableList.of("java/time/"),
+ ImmutableList.of());
+ assertThat(support.isRenamedCoreLibrary("__/java/time/X")).isTrue();
+ assertThat(support.isRenamedCoreLibrary("__/java/time/y/X")).isTrue();
+ assertThat(support.isRenamedCoreLibrary("__/java/io/X")).isFalse();
+ assertThat(support.isRenamedCoreLibrary("com/google/X")).isFalse();
+ }
+ @Test
+ public void testRenameCoreLibrary() throws Exception {
+ CoreLibrarySupport support =
+ new CoreLibrarySupport(
+ new CoreLibraryRewriter(""), null, ImmutableList.of(), ImmutableList.of());
+ assertThat(support.renameCoreLibrary("java/time/X")).isEqualTo("j$/time/X");
+ assertThat(support.renameCoreLibrary("com/google/X")).isEqualTo("com/google/X");
+ }
+
+ @Test
+ public void testRenameCoreLibrary_prefixedLoader() throws Exception {
+ CoreLibrarySupport support =
+ new CoreLibrarySupport(
+ new CoreLibraryRewriter("__/"), null, ImmutableList.of(), ImmutableList.of());
+ assertThat(support.renameCoreLibrary("__/java/time/X")).isEqualTo("j$/time/X");
+ assertThat(support.renameCoreLibrary("com/google/X")).isEqualTo("com/google/X");
+ }
+
+ @Test
+ public void testIsEmulatedCoreLibraryInvocation() throws Exception {
+ CoreLibrarySupport support =
+ new CoreLibrarySupport(
+ new CoreLibraryRewriter(""),
+ Thread.currentThread().getContextClassLoader(),
+ ImmutableList.of(),
+ ImmutableList.of("java/util/Collection"));
+ assertThat(
+ support.isEmulatedCoreLibraryInvocation(
+ Opcodes.INVOKEINTERFACE,
+ "java/util/Collection",
+ "removeIf",
+ "(Ljava/util/function/Predicate;)Z",
+ true))
+ .isTrue(); // true for default method
+ assertThat(
+ support.isEmulatedCoreLibraryInvocation(
+ Opcodes.INVOKEINTERFACE, "java/util/Collection", "size", "()I", true))
+ .isFalse(); // false for abstract method
+ }
+
+ @Test
+ public void testGetEmulatedCoreLibraryInvocationTarget_defaultMethod() throws Exception {
+ CoreLibrarySupport support =
+ new CoreLibrarySupport(
+ new CoreLibraryRewriter(""),
+ Thread.currentThread().getContextClassLoader(),
+ ImmutableList.of(),
+ ImmutableList.of("java/util/Collection"));
+ assertThat(
+ support.getEmulatedCoreLibraryInvocationTarget(
+ Opcodes.INVOKEINTERFACE,
+ "java/util/Collection",
+ "removeIf",
+ "(Ljava/util/function/Predicate;)Z",
+ true))
+ .isEqualTo(Collection.class);
+ assertThat(
+ support.getEmulatedCoreLibraryInvocationTarget(
+ Opcodes.INVOKEVIRTUAL,
+ "java/util/ArrayList",
+ "removeIf",
+ "(Ljava/util/function/Predicate;)Z",
+ false))
+ .isEqualTo(Collection.class);
+ assertThat(
+ support.getEmulatedCoreLibraryInvocationTarget(
+ Opcodes.INVOKEINTERFACE,
+ "com/google/common/collect/ImmutableList",
+ "removeIf",
+ "(Ljava/util/function/Predicate;)Z",
+ true))
+ .isNull();
+ }
+
+ @Test
+ public void testGetEmulatedCoreLibraryInvocationTarget_abstractMethod() throws Exception {
+ CoreLibrarySupport support =
+ new CoreLibrarySupport(
+ new CoreLibraryRewriter(""),
+ Thread.currentThread().getContextClassLoader(),
+ ImmutableList.of(),
+ ImmutableList.of("java/util/Collection"));
+ assertThat(
+ support.getEmulatedCoreLibraryInvocationTarget(
+ Opcodes.INVOKEINTERFACE,
+ "java/util/Collection",
+ "size",
+ "()I",
+ true))
+ .isNull();
+ assertThat(
+ support.getEmulatedCoreLibraryInvocationTarget(
+ Opcodes.INVOKEVIRTUAL,
+ "java/util/ArrayList",
+ "size",
+ "()I",
+ false))
+ .isNull();
+ }
+
+ @Test
+ public void testGetEmulatedCoreLibraryInvocationTarget_defaultOverride() throws Exception {
+ CoreLibrarySupport support =
+ new CoreLibrarySupport(
+ new CoreLibraryRewriter(""),
+ Thread.currentThread().getContextClassLoader(),
+ ImmutableList.of(),
+ ImmutableList.of("java/util/Map"));
+ assertThat(
+ support.getEmulatedCoreLibraryInvocationTarget(
+ Opcodes.INVOKEINTERFACE,
+ "java/util/Map",
+ "putIfAbsent",
+ "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
+ true))
+ .isEqualTo(Map.class);
+ assertThat(
+ support.getEmulatedCoreLibraryInvocationTarget(
+ Opcodes.INVOKEINTERFACE,
+ "java/util/concurrent/ConcurrentMap",
+ "putIfAbsent",
+ "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
+ true))
+ .isNull(); // putIfAbsent is default in Map but abstract in ConcurrentMap
+ assertThat(
+ support.getEmulatedCoreLibraryInvocationTarget(
+ Opcodes.INVOKEINTERFACE,
+ "java/util/concurrent/ConcurrentMap",
+ "getOrDefault",
+ "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
+ true))
+ .isEqualTo(ConcurrentMap.class); // conversely, getOrDefault is overridden as default method
+ }
+
+ @Test
+ public void testGetEmulatedCoreLibraryInvocationTarget_staticInterfaceMethod() throws Exception {
+ CoreLibrarySupport support =
+ new CoreLibrarySupport(
+ new CoreLibraryRewriter(""),
+ Thread.currentThread().getContextClassLoader(),
+ ImmutableList.of(),
+ ImmutableList.of("java/util/Comparator"));
+ assertThat(
+ support.getEmulatedCoreLibraryInvocationTarget(
+ Opcodes.INVOKESTATIC,
+ "java/util/Comparator",
+ "reverseOrder",
+ "()Ljava/util/Comparator;",
+ true))
+ .isEqualTo(Comparator.class);
+ }
+
+ @Test
+ public void testGetEmulatedCoreLibraryInvocationTarget_ignoreRenamed() throws Exception {
+ CoreLibrarySupport support =
+ new CoreLibrarySupport(
+ new CoreLibraryRewriter(""),
+ Thread.currentThread().getContextClassLoader(),
+ ImmutableList.of("java/util/concurrent/"), // should return null for these
+ ImmutableList.of("java/util/Map"));
+ assertThat(
+ support.getEmulatedCoreLibraryInvocationTarget(
+ Opcodes.INVOKEINTERFACE,
+ "java/util/Map",
+ "getOrDefault",
+ "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
+ true))
+ .isEqualTo(Map.class);
+ assertThat(
+ support.getEmulatedCoreLibraryInvocationTarget(
+ Opcodes.INVOKEINTERFACE,
+ "java/util/concurrent/ConcurrentMap",
+ "getOrDefault",
+ "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
+ true))
+ .isNull();
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/desugar/CorePackageRenamerTest.java b/src/test/java/com/google/devtools/build/android/desugar/CorePackageRenamerTest.java
new file mode 100644
index 0000000000..0626b469fd
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/CorePackageRenamerTest.java
@@ -0,0 +1,90 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.desugar;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(JUnit4.class)
+public class CorePackageRenamerTest {
+
+ @Test
+ public void testSymbolRewrite() throws Exception {
+ MockClassVisitor out = new MockClassVisitor();
+ CorePackageRenamer renamer = new CorePackageRenamer(
+ out,
+ new CoreLibrarySupport(
+ new CoreLibraryRewriter(""), null, ImmutableList.of("java/time/"), ImmutableList.of()));
+ MethodVisitor mv = renamer.visitMethod(0, "test", "()V", null, null);
+
+ mv.visitMethodInsn(
+ Opcodes.INVOKESTATIC, "java/time/Instant", "now", "()Ljava/time/Instant;", false);
+ assertThat(out.mv.owner).isEqualTo("j$/time/Instant");
+ assertThat(out.mv.desc).isEqualTo("()Lj$/time/Instant;");
+
+ mv.visitMethodInsn(
+ Opcodes.INVOKESTATIC, "other/time/Instant", "now", "()Ljava/time/Instant;", false);
+ assertThat(out.mv.owner).isEqualTo("other/time/Instant");
+ assertThat(out.mv.desc).isEqualTo("()Lj$/time/Instant;");
+
+ mv.visitFieldInsn(
+ Opcodes.GETFIELD, "other/time/Instant", "now", "Ljava/time/Instant;");
+ assertThat(out.mv.owner).isEqualTo("other/time/Instant");
+ assertThat(out.mv.desc).isEqualTo("Lj$/time/Instant;");
+ }
+
+ private static class MockClassVisitor extends ClassVisitor {
+
+ final MockMethodVisitor mv = new MockMethodVisitor();
+
+ public MockClassVisitor() {
+ super(Opcodes.ASM6);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String desc, String signature, String[] exceptions) {
+ return mv;
+ }
+ }
+
+ private static class MockMethodVisitor extends MethodVisitor {
+
+ String owner;
+ String desc;
+
+ public MockMethodVisitor() {
+ super(Opcodes.ASM6);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+ this.owner = owner;
+ this.desc = desc;
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ this.owner = owner;
+ this.desc = desc;
+ }
+ }
+}
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
new file mode 100644
index 0000000000..417248b8eb
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryInvocationRewriter.java
@@ -0,0 +1,81 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.desugar;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Rewriter of default and static interface methods defined in some core libraries.
+ *
+ * <p>This is conceptually similar to call site rewriting in {@link InterfaceDesugaring} but here
+ * we're doing it for certain bootclasspath methods and in particular for invokeinterface and
+ * invokevirtual, which are ignored in regular {@link InterfaceDesugaring}.
+ */
+public class CoreLibraryInvocationRewriter extends ClassVisitor {
+
+ private final CoreLibrarySupport support;
+
+ public CoreLibraryInvocationRewriter(ClassVisitor cv, CoreLibrarySupport support) {
+ super(Opcodes.ASM6, cv);
+ this.support = support;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String desc, String signature, String[] exceptions) {
+ MethodVisitor result = super.visitMethod(access, name, desc, signature, exceptions);
+ return result != null ? new CoreLibraryMethodInvocationRewriter(result) : null;
+ }
+
+ private class CoreLibraryMethodInvocationRewriter extends MethodVisitor {
+ public CoreLibraryMethodInvocationRewriter(MethodVisitor mv) {
+ super(Opcodes.ASM6, mv);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+ Class<?> coreInterface =
+ support.getEmulatedCoreLibraryInvocationTarget(opcode, owner, name, desc, itf);
+ if (coreInterface != null) {
+ String coreInterfaceName = coreInterface.getName().replace('.', '/');
+ name =
+ InterfaceDesugaring.normalizeInterfaceMethodName(
+ name, name.startsWith("lambda$"), opcode == Opcodes.INVOKESTATIC);
+ if (opcode == Opcodes.INVOKESTATIC) {
+ checkState(owner.equals(coreInterfaceName));
+ } else {
+ desc =
+ InterfaceDesugaring.companionDefaultMethodDescriptor(
+ opcode == Opcodes.INVOKESPECIAL ? owner : coreInterfaceName, desc);
+ }
+
+ if (opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.INVOKESPECIAL) {
+ checkArgument(itf, "Expected interface to rewrite %s.%s : %s", owner, name, desc);
+ owner = InterfaceDesugaring.getCompanionClassName(owner);
+ } else {
+ // TODO(kmb): Simulate dynamic dispatch instead of calling most general default method
+ owner = InterfaceDesugaring.getCompanionClassName(coreInterfaceName);
+ }
+ opcode = Opcodes.INVOKESTATIC;
+ itf = false;
+ }
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
+ }
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryRewriter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryRewriter.java
index 7f1591b4ba..456fdb531e 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryRewriter.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryRewriter.java
@@ -85,6 +85,10 @@ class CoreLibraryRewriter {
return false;
}
+ public String getPrefix() {
+ return prefix;
+ }
+
/** Removes prefix from class names */
public String unprefix(String typeName) {
if (prefix.isEmpty() || !typeName.startsWith(prefix)) {
@@ -159,7 +163,7 @@ class CoreLibraryRewriter {
if (!prefix.isEmpty()) {
this.cv =
new ClassRemapper(
- this.cv,
+ this.writer,
new Remapper() {
@Override
public String map(String typeName) {
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
new file mode 100644
index 0000000000..56e5f18b30
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java
@@ -0,0 +1,152 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.desugar;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Method;
+import java.util.LinkedHashSet;
+import java.util.Objects;
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/**
+ * Helper that keeps track of which core library classes and methods we want to rewrite.
+ */
+class CoreLibrarySupport {
+
+ private final CoreLibraryRewriter rewriter;
+ private final ClassLoader targetLoader;
+ /** Internal name prefixes that we want to move to a custom package. */
+ private final ImmutableList<String> renamedPrefixes;
+ /** Internal names of interfaces whose default and static interface methods we'll emulate. */
+ private final ImmutableList<Class<?>> emulatedInterfaces;
+
+ public CoreLibrarySupport(CoreLibraryRewriter rewriter, ClassLoader targetLoader,
+ ImmutableList<String> renamedPrefixes, ImmutableList<String> emulatedInterfaces)
+ throws ClassNotFoundException {
+ this.rewriter = rewriter;
+ this.targetLoader = targetLoader;
+ checkArgument(
+ renamedPrefixes.stream().allMatch(prefix -> prefix.startsWith("java/")), renamedPrefixes);
+ this.renamedPrefixes = renamedPrefixes;
+ ImmutableList.Builder<Class<?>> classBuilder = ImmutableList.builder();
+ for (String itf : emulatedInterfaces) {
+ checkArgument(itf.startsWith("java/util/"), itf);
+ Class<?> clazz = targetLoader.loadClass((rewriter.getPrefix() + itf).replace('/', '.'));
+ checkArgument(clazz.isInterface(), itf);
+ classBuilder.add(clazz);
+ }
+ this.emulatedInterfaces = classBuilder.build();
+ }
+
+ public boolean isRenamedCoreLibrary(String internalName) {
+ String unprefixedName = rewriter.unprefix(internalName);
+ return renamedPrefixes.stream().anyMatch(prefix -> unprefixedName.startsWith(prefix));
+ }
+
+ public String renameCoreLibrary(String internalName) {
+ internalName = rewriter.unprefix(internalName);
+ return (internalName.startsWith("java/"))
+ ? "j$/" + internalName.substring(/* cut away "java/" prefix */ 5)
+ : internalName;
+ }
+
+ public boolean isEmulatedCoreLibraryInvocation(
+ int opcode, String owner, String name, String desc, boolean itf) {
+ return getEmulatedCoreLibraryInvocationTarget(opcode, owner, name, desc, itf) != null;
+ }
+
+ @Nullable
+ public Class<?> getEmulatedCoreLibraryInvocationTarget(
+ int opcode, String owner, String name, String desc, boolean itf) {
+ if (owner.contains("$$Lambda$") || owner.endsWith("$$CC")) {
+ return null; // regular desugaring handles invocations on generated classes, no emulation
+ }
+ Class<?> clazz = getEmulatedCoreClassOrInterface(owner);
+ if (clazz == null) {
+ return null;
+ }
+
+ if (itf && opcode == Opcodes.INVOKESTATIC) {
+ return clazz; // static interface method
+ }
+
+ Method callee = findInterfaceMethod(clazz, name, desc);
+ if (callee != null && callee.isDefault()) {
+ return callee.getDeclaringClass();
+ }
+ return null;
+ }
+
+ private Class<?> getEmulatedCoreClassOrInterface(String internalName) {
+ {
+ String unprefixedOwner = rewriter.unprefix(internalName);
+ if (!unprefixedOwner.startsWith("java/util/") || isRenamedCoreLibrary(unprefixedOwner)) {
+ return null;
+ }
+ }
+
+ Class<?> clazz;
+ try {
+ clazz = targetLoader.loadClass(internalName.replace('/', '.'));
+ } catch (ClassNotFoundException e) {
+ throw (NoClassDefFoundError) new NoClassDefFoundError().initCause(e);
+ }
+
+ if (emulatedInterfaces.stream().anyMatch(itf -> itf.isAssignableFrom(clazz))) {
+ return clazz;
+ }
+ return null;
+ }
+
+ private static Method findInterfaceMethod(Class<?> clazz, String name, String desc) {
+ return collectImplementedInterfaces(clazz, new LinkedHashSet<>())
+ .stream()
+ // search more subtypes before supertypes
+ .sorted(DefaultMethodClassFixer.InterfaceComparator.INSTANCE)
+ .map(itf -> findMethod(itf, name, desc))
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElse((Method) null);
+ }
+
+
+ private static Method findMethod(Class<?> clazz, String name, String desc) {
+ for (Method m : clazz.getMethods()) {
+ if (m.getName().equals(name) && Type.getMethodDescriptor(m).equals(desc)) {
+ return m;
+ }
+ }
+ return null;
+ }
+
+ private static Set<Class<?>> collectImplementedInterfaces(Class<?> clazz, Set<Class<?>> dest) {
+ if (clazz.isInterface()) {
+ if (!dest.add(clazz)) {
+ return dest;
+ }
+ } else if (clazz.getSuperclass() != null) {
+ collectImplementedInterfaces(clazz.getSuperclass(), dest);
+ }
+
+ for (Class<?> itf : clazz.getInterfaces()) {
+ collectImplementedInterfaces(itf, dest);
+ }
+ return dest;
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/CorePackageRenamer.java b/src/tools/android/java/com/google/devtools/build/android/desugar/CorePackageRenamer.java
new file mode 100644
index 0000000000..3d58ef6d7e
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/CorePackageRenamer.java
@@ -0,0 +1,54 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.desugar;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.commons.ClassRemapper;
+import org.objectweb.asm.commons.Remapper;
+
+/**
+ * A visitor that renames some type names and only when the owner is also renamed.
+ */
+class CorePackageRenamer extends ClassRemapper {
+
+ public CorePackageRenamer(ClassVisitor cv, CoreLibrarySupport support) {
+ super(cv, new CorePackageRemapper(support));
+ }
+
+ private static final class CorePackageRemapper extends Remapper {
+ private final CoreLibrarySupport support;
+
+ private CorePackageRemapper(CoreLibrarySupport support) {
+ this.support = support;
+ }
+
+ public boolean isRenamed(String owner) {
+ return support.isRenamedCoreLibrary(owner);
+ }
+
+ @Override
+ public String map(String typeName) {
+ return isRenamed(typeName) ? support.renameCoreLibrary(typeName) : typeName;
+ }
+
+ @Override
+ public Object mapValue(Object value) {
+ if (value instanceof Handle && !isRenamed(((Handle) value).getOwner())) {
+ return value;
+ }
+ return super.mapValue(value);
+ }
+ }
+}
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 5d3df4a406..c86b4068ac 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
@@ -248,6 +248,28 @@ class Desugar {
)
public boolean coreLibrary;
+ /** Type prefixes that we'll move to a custom package. */
+ @Option(
+ name = "rewrite_core_library_prefix",
+ defaultValue = "", // ignored
+ allowMultiple = true,
+ documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "Assume the given java.* prefixes are desugared."
+ )
+ public List<String> rewriteCoreLibraryPrefixes;
+
+ /** Interfaces whose default and static interface methods we'll emulate. */
+ @Option(
+ name = "emulate_core_library_interface",
+ defaultValue = "", // ignored
+ allowMultiple = true,
+ documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "Assume the given java.* interfaces are emulated."
+ )
+ public List<String> emulateCoreLibraryInterfaces;
+
/** Set to work around b/62623509 with JaCoCo versions prior to 0.7.9. */
// TODO(kmb): Remove when Android Studio doesn't need it anymore (see b/37116789)
@Option(
@@ -265,7 +287,7 @@ class Desugar {
private final DesugarOptions options;
private final CoreLibraryRewriter rewriter;
private final LambdaClassMaker lambdas;
- private final GeneratedClassStore store;
+ private final GeneratedClassStore store = new GeneratedClassStore();
private final Set<String> visitedExceptionTypes = new HashSet<>();
/** The counter to record the times of try-with-resources desugaring is invoked. */
private final AtomicInteger numOfTryWithResourcesInvoked = new AtomicInteger();
@@ -282,7 +304,6 @@ class Desugar {
this.options = options;
this.rewriter = new CoreLibraryRewriter(options.coreLibrary ? "__desugar__/" : "");
this.lambdas = new LambdaClassMaker(dumpDirectory);
- this.store = new GeneratedClassStore();
this.outputJava7 = options.minSdkVersion < 24;
this.allowDefaultMethods =
options.desugarInterfaceMethodBodiesIfNeeded || options.minSdkVersion >= 24;
@@ -358,6 +379,16 @@ class Desugar {
ImmutableSet.Builder<String> interfaceLambdaMethodCollector = ImmutableSet.builder();
ClassVsInterface interfaceCache = new ClassVsInterface(classpathReader);
+ CoreLibrarySupport coreLibrarySupport =
+ options.rewriteCoreLibraryPrefixes.isEmpty()
+ && options.emulateCoreLibraryInterfaces.isEmpty()
+ ? null
+ : new CoreLibrarySupport(
+ rewriter,
+ loader,
+ ImmutableList.copyOf(options.rewriteCoreLibraryPrefixes),
+ ImmutableList.copyOf(options.emulateCoreLibraryInterfaces));
+
desugarClassesInInput(
inputFiles,
outputFileProvider,
@@ -365,6 +396,7 @@ class Desugar {
classpathReader,
depsCollector,
bootclasspathReader,
+ coreLibrarySupport,
interfaceCache,
interfaceLambdaMethodCollector);
@@ -374,11 +406,12 @@ class Desugar {
classpathReader,
depsCollector,
bootclasspathReader,
+ coreLibrarySupport,
interfaceCache,
interfaceLambdaMethodCollector.build(),
bridgeMethodReader);
- desugarAndWriteGeneratedClasses(outputFileProvider, bootclasspathReader);
+ desugarAndWriteGeneratedClasses(outputFileProvider, bootclasspathReader, coreLibrarySupport);
copyThrowableExtensionClass(outputFileProvider);
byte[] depsInfo = depsCollector.toByteArray();
@@ -445,6 +478,7 @@ class Desugar {
@Nullable ClassReaderFactory classpathReader,
DependencyCollector depsCollector,
ClassReaderFactory bootclasspathReader,
+ @Nullable CoreLibrarySupport coreLibrarySupport,
ClassVsInterface interfaceCache,
ImmutableSet.Builder<String> interfaceLambdaMethodCollector)
throws IOException {
@@ -466,6 +500,7 @@ class Desugar {
classpathReader,
depsCollector,
bootclasspathReader,
+ coreLibrarySupport,
interfaceCache,
interfaceLambdaMethodCollector,
writer,
@@ -494,6 +529,7 @@ class Desugar {
@Nullable ClassReaderFactory classpathReader,
DependencyCollector depsCollector,
ClassReaderFactory bootclasspathReader,
+ @Nullable CoreLibrarySupport coreLibrarySupport,
ClassVsInterface interfaceCache,
ImmutableSet<String> interfaceLambdaMethods,
@Nullable ClassReaderFactory bridgeMethodReader)
@@ -525,6 +561,7 @@ class Desugar {
classpathReader,
depsCollector,
bootclasspathReader,
+ coreLibrarySupport,
interfaceCache,
interfaceLambdaMethods,
bridgeMethodReader,
@@ -540,7 +577,9 @@ class Desugar {
}
private void desugarAndWriteGeneratedClasses(
- OutputFileProvider outputFileProvider, ClassReaderFactory bootclasspathReader)
+ OutputFileProvider outputFileProvider,
+ ClassReaderFactory bootclasspathReader,
+ @Nullable CoreLibrarySupport coreLibrarySupport)
throws IOException {
// Write out any classes we generated along the way
ImmutableMap<String, ClassNode> generatedClasses = store.drain();
@@ -552,8 +591,13 @@ class Desugar {
UnprefixingClassWriter writer = rewriter.writer(ClassWriter.COMPUTE_MAXS);
// checkState above implies that we want Java 7 .class files, so send through that visitor.
// Don't need a ClassReaderFactory b/c static interface methods should've been moved.
- ClassVisitor visitor =
- new Java7Compatibility(writer, (ClassReaderFactory) null, bootclasspathReader);
+ ClassVisitor visitor = writer;
+ if (coreLibrarySupport != null) {
+ visitor = new CorePackageRenamer(visitor, coreLibrarySupport);
+ visitor = new CoreLibraryInvocationRewriter(visitor, coreLibrarySupport);
+ }
+
+ visitor = new Java7Compatibility(visitor, (ClassReaderFactory) null, bootclasspathReader);
generated.getValue().accept(visitor);
String filename = rewriter.unprefix(generated.getKey()) + ".class";
outputFileProvider.write(filename, writer.toByteArray());
@@ -569,6 +613,7 @@ class Desugar {
@Nullable ClassReaderFactory classpathReader,
DependencyCollector depsCollector,
ClassReaderFactory bootclasspathReader,
+ @Nullable CoreLibrarySupport coreLibrarySupport,
ClassVsInterface interfaceCache,
ImmutableSet<String> interfaceLambdaMethods,
@Nullable ClassReaderFactory bridgeMethodReader,
@@ -576,6 +621,12 @@ class Desugar {
UnprefixingClassWriter writer,
ClassReader input) {
ClassVisitor visitor = checkNotNull(writer);
+
+ if (coreLibrarySupport != null) {
+ visitor = new CorePackageRenamer(visitor, coreLibrarySupport);
+ visitor = new CoreLibraryInvocationRewriter(visitor, coreLibrarySupport);
+ }
+
if (!allowTryWithResources) {
CloseResourceMethodScanner closeResourceMethodScanner = new CloseResourceMethodScanner();
input.accept(closeResourceMethodScanner, ClassReader.SKIP_DEBUG);
@@ -612,6 +663,7 @@ class Desugar {
options.legacyJacocoFix);
}
}
+
visitor =
new LambdaClassFixer(
visitor,
@@ -639,11 +691,18 @@ class Desugar {
@Nullable ClassReaderFactory classpathReader,
DependencyCollector depsCollector,
ClassReaderFactory bootclasspathReader,
+ @Nullable CoreLibrarySupport coreLibrarySupport,
ClassVsInterface interfaceCache,
ImmutableSet.Builder<String> interfaceLambdaMethodCollector,
UnprefixingClassWriter writer,
ClassReader input) {
ClassVisitor visitor = checkNotNull(writer);
+
+ if (coreLibrarySupport != null) {
+ visitor = new CorePackageRenamer(visitor, coreLibrarySupport);
+ visitor = new CoreLibraryInvocationRewriter(visitor, coreLibrarySupport);
+ }
+
if (!allowTryWithResources) {
CloseResourceMethodScanner closeResourceMethodScanner = new CloseResourceMethodScanner();
input.accept(closeResourceMethodScanner, ClassReader.SKIP_DEBUG);
@@ -678,6 +737,7 @@ class Desugar {
options.legacyJacocoFix);
}
}
+
// LambdaDesugaring is relatively expensive, so check first whether we need it. Additionally,
// we need to collect lambda methods referenced by invokedynamic instructions up-front anyway.
// TODO(kmb): Scan constant pool instead of visiting the class to find bootstrap methods etc.
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 b8b3ead330..3524fae710 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
@@ -265,8 +265,7 @@ class InterfaceDesugaring extends ClassVisitor {
return "<clinit>".equals(methodName);
}
- private static String normalizeInterfaceMethodName(
- String name, boolean isLambda, boolean isStatic) {
+ static String normalizeInterfaceMethodName(String name, boolean isLambda, boolean isStatic) {
if (isLambda) {
// Rename lambda method to reflect the new owner. Not doing so confuses LambdaDesugaring
// if it's run over this class again. LambdaDesugaring has already renamed the method from