Shadowed Android API Desuagar: Add Automated Support for Android APIs built-in Core Types
PiperOrigin-RevId: 303881017
diff --git a/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedAndroidApiAdapterTest.java b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedAndroidApiAdapterTest.java
new file mode 100644
index 0000000..9b14ee3
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedAndroidApiAdapterTest.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2020 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.corelibadapter;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.android.desugar.testing.junit.DesugarTestHelpers.getRuntimePathsFromJvmFlag;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.android.desugar.testing.junit.AsmNode;
+import com.google.devtools.build.android.desugar.testing.junit.DesugarRule;
+import com.google.devtools.build.android.desugar.testing.junit.DesugarRunner;
+import com.google.devtools.build.android.desugar.testing.junit.FromParameterValueSource;
+import com.google.devtools.build.android.desugar.testing.junit.JdkSuppress;
+import com.google.devtools.build.android.desugar.testing.junit.JdkVersion;
+import com.google.devtools.build.android.desugar.testing.junit.ParameterValueSource;
+import com.google.devtools.build.android.desugar.testing.junit.RuntimeMethodHandle;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.util.Arrays;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+
+/** Tests for accessing private constructors from another class within a nest. */
+@RunWith(DesugarRunner.class)
+@JdkSuppress(minJdkVersion = JdkVersion.V11)
+public class ShadowedAndroidApiAdapterTest {
+
+ @Rule
+ public final DesugarRule desugarRule =
+ DesugarRule.builder(this, MethodHandles.lookup())
+ .addSourceInputsFromJvmFlag("input_srcs")
+ .addClasspathEntries(getRuntimePathsFromJvmFlag("desugar_jdk_jar"))
+ .addClasspathEntries(getRuntimePathsFromJvmFlag("desugar_runtime_libs"))
+ .addClasspathEntries(getRuntimePathsFromJvmFlag("jdk_jar"))
+ .addJavacOptions("-source 11", "-target 11")
+ .addCommandOptions("core_library", "true")
+ .addCommandOptions("desugar_supported_core_libs", "true")
+ .enableIterativeTransformation(2)
+ .addCommandOptions("rewrite_core_library_prefix", "javadesugar/testing/")
+ .build();
+
+ @Test
+ public void inputClassFileMajorVersions_preDesugar(
+ @AsmNode(className = "com.app.testing.CuboidCalculator", round = 0) ClassNode before) {
+ assertThat(before.version).isEqualTo(JdkVersion.V11);
+ }
+
+ @Test
+ public void inputClassFileMajorVersions_postDesugar(
+ @AsmNode(className = "com.app.testing.CuboidCalculator", round = 1) ClassNode before) {
+ assertThat(before.version).isEqualTo(JdkVersion.V1_7);
+ }
+
+ @Test
+ public void executeUserApp_invokeConstructorPreDesugar(
+ @RuntimeMethodHandle(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeConstructor",
+ round = 0)
+ MethodHandle method)
+ throws Throwable {
+ long result = (long) method.invoke(2L, 3L, 4L);
+ assertThat(result).isEqualTo(24L);
+ }
+
+ @Test
+ public void executeUserApp_invokeConstructorDesugarOnce(
+ @RuntimeMethodHandle(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeConstructor",
+ round = 1)
+ MethodHandle method)
+ throws Throwable {
+ long result = (long) method.invoke(2L, 3L, 4L);
+ assertThat(result).isEqualTo(24L);
+ }
+
+ @Test
+ public void executeUserApp_invokeConstructorDesugarTwice(
+ @RuntimeMethodHandle(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeConstructor",
+ round = 2)
+ MethodHandle method)
+ throws Throwable {
+ long result = (long) method.invoke(2L, 3L, 4L);
+ assertThat(result).isEqualTo(24L);
+ }
+
+ @Test
+ public void executeUserApp_invokeDerivedClassConstructorWithEmbeddedInstancePreDesugar(
+ @RuntimeMethodHandle(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeDerivedClassConstructorWithEmbeddedInstance",
+ round = 0)
+ MethodHandle method)
+ throws Throwable {
+ long result = (long) method.invoke(2L, 3L, 4L);
+ assertThat(result).isEqualTo(24L);
+ }
+
+ @Test
+ public void executeUserApp_invokeDerivedClassConstructorWithEmbeddedInstanceDesugarOnce(
+ @RuntimeMethodHandle(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeDerivedClassConstructorWithEmbeddedInstance",
+ round = 1)
+ MethodHandle method)
+ throws Throwable {
+ long result = (long) method.invoke(2L, 3L, 4L);
+ assertThat(result).isEqualTo(24L);
+ }
+
+ @Test
+ public void executeUserApp_invokeDerivedClassConstructorWithEmbeddedInstanceDesugarTwice(
+ @RuntimeMethodHandle(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeDerivedClassConstructorWithEmbeddedInstance",
+ round = 2)
+ MethodHandle method)
+ throws Throwable {
+ long result = (long) method.invoke(2L, 3L, 4L);
+ assertThat(result).isEqualTo(24L);
+ }
+
+ @Test
+ @ParameterValueSource({"2", "3", "4", "64"})
+ @ParameterValueSource({"20", "30", "40", "24000"})
+ public void executeUserApp_invokeDerivedClassConstructorWithDimensionsPreDesugar(
+ @RuntimeMethodHandle(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeDerivedClassConstructorWithDimensions",
+ round = 0)
+ MethodHandle method,
+ @FromParameterValueSource long width,
+ @FromParameterValueSource long length,
+ @FromParameterValueSource long height,
+ @FromParameterValueSource long expectedVolume)
+ throws Throwable {
+ long result = (long) method.invoke(width, length, height);
+ assertThat(result).isEqualTo(expectedVolume);
+ }
+
+ @Test
+ @ParameterValueSource({"2", "3", "4", "64"})
+ @ParameterValueSource({"20", "30", "40", "24000"})
+ public void executeUserApp_invokeDerivedClassConstructorWithDimensionsDesugarOnce(
+ @RuntimeMethodHandle(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeDerivedClassConstructorWithDimensions",
+ round = 1)
+ MethodHandle method,
+ @FromParameterValueSource long width,
+ @FromParameterValueSource long length,
+ @FromParameterValueSource long height,
+ @FromParameterValueSource long expectedVolume)
+ throws Throwable {
+ long result = (long) method.invoke(width, length, height);
+ assertThat(result).isEqualTo(expectedVolume);
+ }
+
+ @Test
+ @ParameterValueSource({"2", "3", "4", "64"})
+ @ParameterValueSource({"20", "30", "40", "24000"})
+ public void executeUserApp_invokeDerivedClassConstructorWithDimensionsDesugarTwice(
+ @RuntimeMethodHandle(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeDerivedClassConstructorWithDimensions",
+ round = 2)
+ MethodHandle method,
+ @FromParameterValueSource long width,
+ @FromParameterValueSource long length,
+ @FromParameterValueSource long height,
+ @FromParameterValueSource long expectedVolume)
+ throws Throwable {
+ long result = (long) method.invoke(width, length, height);
+ assertThat(result).isEqualTo(expectedVolume);
+ }
+
+ @Test
+ public void executeUserApp_invokeStaticMethodPreDesugar(
+ @RuntimeMethodHandle(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeStaticMethod",
+ round = 0)
+ MethodHandle method)
+ throws Throwable {
+ long result = (long) method.invoke(2L, 3L, 4L, 10L, 10L, 10L);
+ assertThat(result).isEqualTo(24000L);
+ }
+
+ @Test
+ public void executeUserApp_invokeStaticMethodDesugarOnce(
+ @RuntimeMethodHandle(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeStaticMethod",
+ round = 1)
+ MethodHandle method)
+ throws Throwable {
+ long result = (long) method.invoke(2L, 3L, 4L, 10L, 10L, 10L);
+ assertThat(result).isEqualTo(24000L);
+ }
+
+ @Test
+ public void executeUserApp_invokeStaticMethodDesugarTwice(
+ @RuntimeMethodHandle(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeStaticMethod",
+ round = 2)
+ MethodHandle method)
+ throws Throwable {
+ long result = (long) method.invoke(2L, 3L, 4L, 10L, 10L, 10L);
+ assertThat(result).isEqualTo(24000L);
+ }
+
+ @Test
+ public void executeUserApp_invokeInstanceMethodPreDesugar(
+ @RuntimeMethodHandle(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeInstanceMethod",
+ round = 0)
+ MethodHandle method)
+ throws Throwable {
+ long result = (long) method.invoke(2L, 3L, 4L, 10L, 10L, 10L);
+ assertThat(result).isEqualTo(24000L);
+ }
+
+ @Test
+ public void executeUserApp_invokeInstanceMethodDesugarOnce(
+ @RuntimeMethodHandle(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeInstanceMethod",
+ round = 1)
+ MethodHandle method)
+ throws Throwable {
+ long result = (long) method.invoke(2L, 3L, 4L, 10L, 10L, 10L);
+ assertThat(result).isEqualTo(24000L);
+ }
+
+ @Test
+ public void executeUserApp_invokeInstanceMethodDesugarTwice(
+ @RuntimeMethodHandle(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeInstanceMethod",
+ round = 2)
+ MethodHandle method)
+ throws Throwable {
+ long result = (long) method.invoke(2L, 3L, 4L, 10L, 10L, 10L);
+ assertThat(result).isEqualTo(24000L);
+ }
+
+ @Test
+ public void executeUserApp_invokeConstructorDesugarOnceInlineConversion(
+ @AsmNode(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeConstructor",
+ round = 1)
+ MethodNode method) {
+ assertThat(
+ findMethodInvocations(
+ method, "desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter", "from"))
+ .isEmpty();
+ assertThat(
+ findMethodInvocations(
+ method, "desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter", "to"))
+ .isNotEmpty();
+ }
+
+ @Test
+ public void executeUserApp_invokeStaticMethodDesugarConversionVerification(
+ @AsmNode(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeStaticMethod",
+ round = 1)
+ MethodNode method) {
+ assertThat(
+ findMethodInvocations(
+ method,
+ "desugar/runtime/typeadapter/android/testing/CuboidInflaterAdapter",
+ "inflateStatic"))
+ .isNotEmpty();
+ assertThat(
+ findMethodInvocations(
+ method, "desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter", "from"))
+ .isEmpty();
+ assertThat(
+ findMethodInvocations(
+ method, "desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter", "to"))
+ .isEmpty();
+ }
+
+ @Test
+ public void executeUserApp_invokeInstanceMethodDesugarConversionVerification(
+ @AsmNode(
+ className = "com.app.testing.CuboidCalculator",
+ memberName = "invokeInstanceMethod",
+ round = 1)
+ MethodNode method) {
+ assertThat(
+ findMethodInvocations(
+ method,
+ "desugar/runtime/typeadapter/android/testing/CuboidInflaterAdapter",
+ "inflateInstance"))
+ .isNotEmpty();
+ assertThat(
+ findMethodInvocations(
+ method, "desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter", "from"))
+ .isEmpty();
+ assertThat(
+ findMethodInvocations(
+ method, "desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter", "to"))
+ .isEmpty();
+ }
+
+ private static ImmutableList<MethodInsnNode> findMethodInvocations(
+ MethodNode enclosingMethod, String invokedMethodOwner, String invokedMethodName) {
+ AbstractInsnNode[] instructions = enclosingMethod.instructions.toArray();
+ return Arrays.stream(instructions)
+ .filter(node -> node.getType() == AbstractInsnNode.METHOD_INSN)
+ .map(node -> (MethodInsnNode) node)
+ .filter(
+ node -> invokedMethodOwner.equals(node.owner) && invokedMethodName.equals(node.name))
+ .collect(toImmutableList());
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_android_sdk_libs/android/testing/CuboidInflater.java b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_android_sdk_libs/android/testing/CuboidInflater.java
new file mode 100644
index 0000000..e1078b6
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_android_sdk_libs/android/testing/CuboidInflater.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2020 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 android.testing;
+
+import javadesugar.testing.Cuboid;
+
+/**
+ * A simulated Android platform SDK class for desugar testing. The class should not be desugared.
+ */
+public class CuboidInflater {
+
+ private final Cuboid cuboid;
+
+ public CuboidInflater(Cuboid cuboid) {
+ this.cuboid = cuboid;
+ }
+
+ public static Cuboid inflateStatic(
+ Cuboid cuboid, long widthMultiplier, long lengthMultiplier, long heightMultiplier) {
+ return new Cuboid(
+ widthMultiplier * cuboid.getWidth(),
+ lengthMultiplier * cuboid.getLength(),
+ heightMultiplier * cuboid.getHeight());
+ }
+
+ public Cuboid inflateInstance(
+ long widthMultiplier, long lengthMultiplier, long heightMultiplier) {
+ return new Cuboid()
+ .setDimensions(
+ widthMultiplier * cuboid.getWidth(),
+ lengthMultiplier * cuboid.getLength(),
+ heightMultiplier * cuboid.getHeight());
+ }
+
+ public long getVolume() {
+ return cuboid.getVolume();
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_desugar_jdk_libs/javadesugar/testing/Cuboid.java b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_desugar_jdk_libs/javadesugar/testing/Cuboid.java
new file mode 100644
index 0000000..6fb49be
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_desugar_jdk_libs/javadesugar/testing/Cuboid.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2020 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 javadesugar.testing;
+
+/** A simulated built-in JDK class for desugar testing. */
+public final class Cuboid {
+
+ private long width;
+ private long length;
+ private long height;
+
+ public Cuboid(long width, long length, long height) {
+ this.width = width;
+ this.length = length;
+ this.height = height;
+ }
+
+ public Cuboid() {
+ this(0, 0, 0);
+ }
+
+ public Cuboid(Cuboid cuboid) {
+ this(cuboid.width, cuboid.length, cuboid.height);
+ }
+
+ public Cuboid setDimensions(long width, long length, long height) {
+ this.width = width;
+ this.length = length;
+ this.height = height;
+ return this;
+ }
+
+ public long getWidth() {
+ return width;
+ }
+
+ public long getLength() {
+ return length;
+ }
+
+ public long getHeight() {
+ return height;
+ }
+
+ public long getVolume() {
+ return width * length * height;
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_desugar_runtime_libs/desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter.java b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_desugar_runtime_libs/desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter.java
new file mode 100644
index 0000000..7fa3d08
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_desugar_runtime_libs/desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2020 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 desugar.runtime.typeadapter.javadesugar.testing;
+
+/**
+ * Simulated a manually-configured type converter between platform built-in types and
+ * desugar-mirrored types.
+ */
+public abstract class CuboidConverter {
+
+ public static jd$.testing.Cuboid from(javadesugar.testing.Cuboid cuboid) {
+ return new jd$.testing.Cuboid()
+ .setDimensions(cuboid.getWidth(), cuboid.getLength(), cuboid.getHeight());
+ }
+
+ public static javadesugar.testing.Cuboid to(jd$.testing.Cuboid cuboid) {
+ return new javadesugar.testing.Cuboid()
+ .setDimensions(cuboid.getWidth(), cuboid.getLength(), cuboid.getHeight());
+ }
+
+ private CuboidConverter() {}
+}
diff --git a/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_user_app/com/app/testing/CuboidCalculator.java b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_user_app/com/app/testing/CuboidCalculator.java
new file mode 100644
index 0000000..7df0b04
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_user_app/com/app/testing/CuboidCalculator.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2020 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.app.testing;
+
+import android.testing.CuboidInflater;
+import javadesugar.testing.Cuboid;
+
+/** Simulates application-level code that's subject to desugar. */
+public final class CuboidCalculator {
+
+ public static long invokeConstructor(long width, long length, long height) {
+ Cuboid cuboid = new Cuboid(width, length, height);
+
+ // Simulated Android API.
+ CuboidInflater inflater = new CuboidInflater(cuboid);
+
+ return inflater.getVolume();
+ }
+
+ public static long invokeDerivedClassConstructorWithEmbeddedInstance(
+ long width, long length, long height) {
+ Cuboid cuboid = new Cuboid(width, length, height);
+
+ // Derived
+ MyCuboidInflater inflater = new MyCuboidInflater(cuboid);
+
+ return inflater.getVolume();
+ }
+
+ public static long invokeDerivedClassConstructorWithDimensions(
+ long width, long length, long height) {
+
+ // Derived
+ MyCuboidInflater inflater = new MyCuboidInflater(width, length, height);
+
+ return inflater.getVolume();
+ }
+
+ public static long invokeStaticMethod(
+ long width,
+ long length,
+ long height,
+ long widthMultiplier,
+ long lengthMultiplier,
+ long heightMultiplier) {
+ Cuboid cuboid = new Cuboid(width, length, height);
+
+ // Simulated Android API.
+ cuboid =
+ CuboidInflater.inflateStatic(cuboid, widthMultiplier, lengthMultiplier, heightMultiplier);
+
+ return cuboid.getVolume();
+ }
+
+ public static long invokeInstanceMethod(
+ long width,
+ long length,
+ long height,
+ long widthMultiplier,
+ long lengthMultiplier,
+ long heightMultiplier) {
+ CuboidInflater cuboidInflater = createCuboidInflater(width, length, height);
+
+ // Simulated Android API.
+ Cuboid cuboid =
+ cuboidInflater.inflateInstance(widthMultiplier, lengthMultiplier, heightMultiplier);
+
+ return cuboid.getVolume();
+ }
+
+ private static CuboidInflater createCuboidInflater(long width, long length, long height) {
+ return new CuboidInflater(new Cuboid(width, length, height));
+ }
+
+ private CuboidCalculator() {}
+}
diff --git a/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_user_app/com/app/testing/MyCuboidInflater.java b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_user_app/com/app/testing/MyCuboidInflater.java
new file mode 100644
index 0000000..d91a691
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_user_app/com/app/testing/MyCuboidInflater.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2020 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.app.testing;
+
+import android.testing.CuboidInflater;
+import javadesugar.testing.Cuboid;
+
+/**
+ * Simulates an Android SDK-derived class in user application for testing constructor desugaring.
+ */
+public class MyCuboidInflater extends CuboidInflater {
+
+ public MyCuboidInflater(Object cuboid) {
+ // Simulates the invocation of Android SDK constructor API.
+ super(cuboid instanceof Cuboid ? (Cuboid) cuboid : new Cuboid(2, 4, 8));
+ }
+
+ public MyCuboidInflater(long w, long l, long h) {
+ // Simulates the invocation of Android SDK constructor API.
+ super(w * l * h < 64 ? new Cuboid(2, 4, 8) : new Cuboid(w, l, h));
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD
index a078e03..28650c0 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD
@@ -41,6 +41,7 @@
"//src/main/java/com/google/devtools/common/options",
"//src/main/protobuf:worker_protocol_java_proto",
"//src/tools/android/java/com/google/devtools/build/android:android_builder_lib",
+ "//src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter",
"//src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn",
"//src/tools/android/java/com/google/devtools/build/android/desugar/io",
"//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel",
@@ -74,6 +75,7 @@
"//src/tools/android/java/com/google/devtools/build/android/desugar/nest:srcs",
"//src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn:srcs",
"//src/tools/android/java/com/google/devtools/build/android/desugar/typeannotation:srcs",
+ "//src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter:srcs",
"//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:srcs",
"//src/tools/android/java/com/google/devtools/build/android/desugar/strconcat:srcs",
"//src/tools/android/java/com/google/devtools/build/android/desugar/scan:srcs",
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 30d7a6b..50d5c1b 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
@@ -20,6 +20,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedSet;
import com.google.devtools.build.android.desugar.io.BitFlags;
+import com.google.devtools.build.android.desugar.langmodel.ClassName;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
@@ -351,7 +352,8 @@
return false;
}
String implemented = internalName(itf);
- if (implemented.startsWith("java/") || implemented.startsWith("__desugar__/java/")) {
+ if (implemented.startsWith("java/")
+ || implemented.startsWith(ClassName.IN_PROCESS_LABEL + "java/")) {
return coreLibrarySupport != null
&& (coreLibrarySupport.isRenamedCoreLibrary(implemented)
|| coreLibrarySupport.isEmulatedCoreClassOrInterface(implemented));
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 8dce30e..21aa352 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
@@ -31,6 +31,9 @@
import com.google.common.io.Resources;
import com.google.devtools.build.android.Converters.ExistingPathConverter;
import com.google.devtools.build.android.Converters.PathConverter;
+import com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiAdaptersGenerator;
+import com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiInvocationSite;
+import com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiInvocationSite.ImmutableLabelRemover;
import com.google.devtools.build.android.desugar.covariantreturn.NioBufferRefConverter;
import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter;
import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter.UnprefixingClassWriter;
@@ -41,6 +44,9 @@
import com.google.devtools.build.android.desugar.io.OutputFileProvider;
import com.google.devtools.build.android.desugar.io.ThrowingClassLoader;
import com.google.devtools.build.android.desugar.langmodel.ClassMemberUseCounter;
+import com.google.devtools.build.android.desugar.langmodel.ClassName;
+import com.google.devtools.build.android.desugar.langmodel.InvocationSiteTransformationRecord;
+import com.google.devtools.build.android.desugar.langmodel.InvocationSiteTransformationRecord.InvocationSiteTransformationRecordBuilder;
import com.google.devtools.build.android.desugar.nest.NestAnalyzer;
import com.google.devtools.build.android.desugar.nest.NestDesugaring;
import com.google.devtools.build.android.desugar.nest.NestDigest;
@@ -55,6 +61,7 @@
import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsParser;
import com.google.devtools.common.options.ShellQuotedParamsFilePreProcessor;
+import java.io.ByteArrayInputStream;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
@@ -418,7 +425,7 @@
private Desugar(DesugarOptions options, Path dumpDirectory) {
this.options = options;
- this.rewriter = new CoreLibraryRewriter(options.coreLibrary ? "__desugar__/" : "");
+ this.rewriter = new CoreLibraryRewriter(options.coreLibrary ? ClassName.IN_PROCESS_LABEL : "");
this.lambdas = new LambdaClassMaker(dumpDirectory);
this.outputJava7 = options.minSdkVersion < 24;
this.allowDefaultMethods =
@@ -509,6 +516,9 @@
options.preserveCoreLibraryOverrides)
: null;
+ InvocationSiteTransformationRecordBuilder callSiteTransCollector =
+ InvocationSiteTransformationRecord.builder();
+
desugarClassesInInput(
inputFiles,
outputFileProvider,
@@ -518,7 +528,8 @@
bootclasspathReader,
coreLibrarySupport,
interfaceCache,
- interfaceLambdaMethodCollector);
+ interfaceLambdaMethodCollector,
+ callSiteTransCollector);
desugarAndWriteDumpedLambdaClassesToOutput(
outputFileProvider,
@@ -529,7 +540,8 @@
coreLibrarySupport,
interfaceCache,
interfaceLambdaMethodCollector.build(),
- bridgeMethodReader);
+ bridgeMethodReader,
+ callSiteTransCollector);
desugarAndWriteGeneratedClasses(
outputFileProvider,
@@ -537,10 +549,20 @@
classpathReader,
depsCollector,
bootclasspathReader,
- coreLibrarySupport);
+ coreLibrarySupport,
+ callSiteTransCollector);
copyRuntimeClasses(outputFileProvider, coreLibrarySupport);
+ InvocationSiteTransformationRecord callSiteTransRecord = callSiteTransCollector.build();
+ ImmutableList<FileContentProvider<ByteArrayInputStream>> coreLibAdapters =
+ ShadowedApiAdaptersGenerator.generateAdapterClasses(callSiteTransRecord);
+
+ for (FileContentProvider<ByteArrayInputStream> fileContent : coreLibAdapters) {
+ outputFileProvider.write(
+ fileContent.getBinaryPathName(), ByteStreams.toByteArray(fileContent.get()));
+ }
+
byte[] depsInfo = depsCollector.toByteArray();
if (depsInfo != null) {
outputFileProvider.write(OutputFileProvider.DESUGAR_DEPS_FILENAME, depsInfo);
@@ -652,7 +674,8 @@
ClassReaderFactory bootclasspathReader,
@Nullable CoreLibrarySupport coreLibrarySupport,
ClassVsInterface interfaceCache,
- ImmutableSet.Builder<String> interfaceLambdaMethodCollector)
+ ImmutableSet.Builder<String> interfaceLambdaMethodCollector,
+ InvocationSiteTransformationRecordBuilder callSiteRecord)
throws IOException {
ImmutableList<FileContentProvider<? extends InputStream>> inputFileContents =
@@ -682,7 +705,8 @@
// copy classes from Desugar's runtime library, which we build so they need no desugaring.
// The runtime library typically uses constructs we'd otherwise desugar, so it's easier
// to just skip it should it appear as a regular input (for idempotency).
- if (inputFilename.endsWith(".class") && !inputFilename.startsWith(RUNTIME_LIB_PACKAGE)) {
+ if (inputFilename.endsWith(".class")
+ && ClassName.fromClassFileName(inputFilename).isDesugarEligible(options.coreLibrary)) {
ClassReader reader = rewriter.reader(content);
UnprefixingClassWriter writer = rewriter.writer(ClassWriter.COMPUTE_MAXS);
ClassVisitor visitor =
@@ -696,7 +720,8 @@
interfaceLambdaMethodCollector,
writer,
reader,
- nestDigest);
+ nestDigest,
+ callSiteRecord);
if (writer == visitor) {
// Just copy the input if there are no rewritings
outputFileProvider.write(inputFilename, reader.b);
@@ -747,7 +772,8 @@
@Nullable CoreLibrarySupport coreLibrarySupport,
ClassVsInterface interfaceCache,
ImmutableSet<String> interfaceLambdaMethods,
- @Nullable ClassReaderFactory bridgeMethodReader)
+ @Nullable ClassReaderFactory bridgeMethodReader,
+ InvocationSiteTransformationRecordBuilder callSiteTransCollector)
throws IOException {
checkState(
!allowDefaultMethods || interfaceLambdaMethods.isEmpty(),
@@ -784,7 +810,8 @@
bridgeMethodReader,
lambdaClass.getValue(),
writer,
- reader);
+ reader,
+ callSiteTransCollector);
reader.accept(visitor, 0);
checkState(
(options.coreLibrary && coreLibrarySupport != null)
@@ -802,7 +829,8 @@
@Nullable ClassReaderFactory classpathReader,
DependencyCollector depsCollector,
ClassReaderFactory bootclasspathReader,
- @Nullable CoreLibrarySupport coreLibrarySupport)
+ @Nullable CoreLibrarySupport coreLibrarySupport,
+ InvocationSiteTransformationRecordBuilder callSiteTransCollector)
throws IOException {
// Write out any classes we generated along the way
if (coreLibrarySupport != null) {
@@ -822,6 +850,7 @@
visitor = new EmulatedInterfaceRewriter(visitor, coreLibrarySupport);
visitor = new CorePackageRenamer(visitor, coreLibrarySupport);
visitor = new CoreLibraryInvocationRewriter(visitor, coreLibrarySupport);
+ visitor = new ShadowedApiInvocationSite(visitor, callSiteTransCollector);
}
if (!allowTryWithResources) {
@@ -887,13 +916,15 @@
@Nullable ClassReaderFactory bridgeMethodReader,
LambdaInfo lambdaClass,
UnprefixingClassWriter writer,
- ClassReader input) {
+ ClassReader input,
+ InvocationSiteTransformationRecordBuilder callSiteRecord) {
ClassVisitor visitor = checkNotNull(writer);
if (coreLibrarySupport != null) {
visitor = new EmulatedInterfaceRewriter(visitor, coreLibrarySupport);
visitor = new CorePackageRenamer(visitor, coreLibrarySupport);
visitor = new CoreLibraryInvocationRewriter(visitor, coreLibrarySupport);
+ visitor = new ShadowedApiInvocationSite(visitor, callSiteRecord);
}
if (!allowTryWithResources) {
@@ -980,13 +1011,17 @@
ImmutableSet.Builder<String> interfaceLambdaMethodCollector,
UnprefixingClassWriter writer,
ClassReader input,
- NestDigest nestDigest) {
+ NestDigest nestDigest,
+ InvocationSiteTransformationRecordBuilder callSiteRecord) {
ClassVisitor visitor = checkNotNull(writer);
+ visitor = new ImmutableLabelRemover(visitor);
+
if (coreLibrarySupport != null) {
visitor = new EmulatedInterfaceRewriter(visitor, coreLibrarySupport);
visitor = new CorePackageRenamer(visitor, coreLibrarySupport);
visitor = new CoreLibraryInvocationRewriter(visitor, coreLibrarySupport);
+ visitor = new ShadowedApiInvocationSite(visitor, callSiteRecord);
}
if (!allowTryWithResources) {
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/BUILD
new file mode 100644
index 0000000..abb0127
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/BUILD
@@ -0,0 +1,29 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+package(
+ default_visibility = [
+ "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__",
+ "//src/tools/android/java/com/google/devtools/build/android/desugar:__subpackages__",
+ ],
+)
+
+java_library(
+ name = "corelibadapter",
+ srcs = glob(["*.java"]),
+ deps = [
+ "//src/tools/android/java/com/google/devtools/build/android/desugar/io",
+ "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel",
+ "//third_party:asm",
+ "//third_party:asm-commons",
+ "//third_party:asm-tree",
+ "//third_party:auto_value",
+ "//third_party:guava",
+ "//third_party:jsr305",
+ ],
+)
+
+filegroup(
+ name = "srcs",
+ srcs = glob(["**"]),
+ visibility = ["//src/tools/android/java/com/google/devtools/build/android/desugar:__pkg__"],
+)
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdapterHelper.java b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdapterHelper.java
new file mode 100644
index 0000000..4b31d10
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdapterHelper.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2020 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.corelibadapter;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.devtools.build.android.desugar.langmodel.ClassName;
+import com.google.devtools.build.android.desugar.langmodel.MemberUseKind;
+import com.google.devtools.build.android.desugar.langmodel.MethodInvocationSite;
+import com.google.devtools.build.android.desugar.langmodel.MethodKey;
+import java.util.Optional;
+import java.util.stream.Stream;
+import org.objectweb.asm.Type;
+
+/**
+ * Static utilities that serve conversions between desugar-shadowed platform types and their
+ * desugared-mirrored counterparts.
+ */
+class ShadowedApiAdapterHelper {
+
+ private ShadowedApiAdapterHelper() {}
+
+ /**
+ * Returns {@code true} if the desugar tool transforms given invocation site in an inline
+ * strategy, i.e. inserting the parameter type conversion instructions before the give invocation
+ * site.
+ *
+ * @param verbatimInvocationSite The invocation site parsed directly from the desugar input jar.
+ * No in-process label, such as "__desugar__/", is attached to this invocation site.
+ */
+ static boolean shouldUseInlineTypeConversion(MethodInvocationSite verbatimInvocationSite) {
+ return verbatimInvocationSite.isConstructorInvocation()
+ && verbatimInvocationSite.owner().isInPackageEligibleForTypeAdapter()
+ && Stream.concat(
+ Stream.of(verbatimInvocationSite.returnTypeName()),
+ verbatimInvocationSite.argumentTypeNames().stream())
+ .anyMatch(ClassName::isDesugarShadowedType);
+ }
+
+ /**
+ * Returns {@code true} if the desugar tool transforms given invocation site in an adapter
+ * strategy, that is to replace the original invocation with its corresponding adapter method.
+ *
+ * @param verbatimInvocationSite The invocation site parsed directly from the desugar input jar.
+ * No in-process label, such as "__desugar__/", is attached to this invocation site.
+ */
+ static boolean shouldUseApiTypeAdapter(MethodInvocationSite verbatimInvocationSite) {
+ return !verbatimInvocationSite.isConstructorInvocation()
+ && verbatimInvocationSite.owner().isInPackageEligibleForTypeAdapter()
+ && Stream.concat(
+ Stream.of(verbatimInvocationSite.returnTypeName()),
+ verbatimInvocationSite.argumentTypeNames().stream())
+ .anyMatch(ClassName::isDesugarShadowedType);
+ }
+
+ /**
+ * Returns an optional {@link MethodInvocationSite}, present if the given {@link ClassName} is
+ * eligible for transforming a desugar-mirrored type to a desugar-shadowed platform type.
+ */
+ static Optional<MethodInvocationSite> anyMirroredToBuiltinTypeConversion(ClassName className) {
+ return className.isDesugarMirroredType()
+ ? Optional.of(
+ MethodInvocationSite.builder()
+ .setInvocationKind(MemberUseKind.INVOKESTATIC)
+ .setMethod(
+ MethodKey.create(
+ className.mirroredToShadowed().typeConverterOwner(),
+ "to",
+ Type.getMethodDescriptor(
+ className.mirroredToShadowed().toAsmObjectType(),
+ className.toAsmObjectType())))
+ .setIsInterface(false)
+ .build())
+ : Optional.empty();
+ }
+
+ /**
+ * Returns an {@link MethodInvocationSite} that serves transforming a {@code
+ * shadowedTypeName}-represented type to its desugar-mirrored counterpart.
+ */
+ static MethodInvocationSite shadowedToMirroredTypeConversionSite(ClassName shadowedTypeName) {
+ checkArgument(
+ shadowedTypeName.isDesugarShadowedType(),
+ "Expected desugar-shadowed type: Actual (%s)",
+ shadowedTypeName);
+ return MethodInvocationSite.builder()
+ .setInvocationKind(MemberUseKind.INVOKESTATIC)
+ .setMethod(
+ MethodKey.create(
+ shadowedTypeName.typeConverterOwner(),
+ "from",
+ Type.getMethodDescriptor(
+ shadowedTypeName.shadowedToMirrored().toAsmObjectType(),
+ shadowedTypeName.toAsmObjectType())))
+ .setIsInterface(false)
+ .build();
+ }
+
+ /**
+ * Returns an {@link MethodInvocationSite} that serves transforming a {@code
+ * mirroredTypeName}-represented type to its desugar-shadowed counterpart.
+ */
+ static MethodInvocationSite mirroredToShadowedTypeConversionSite(ClassName mirroredTypeName) {
+ checkArgument(
+ mirroredTypeName.isDesugarMirroredType(),
+ "Expected mirrored type: Actual (%s)",
+ mirroredTypeName);
+ return MethodInvocationSite.builder()
+ .setInvocationKind(MemberUseKind.INVOKESTATIC)
+ .setMethod(
+ MethodKey.create(
+ mirroredTypeName.mirroredToShadowed().typeConverterOwner(),
+ "to",
+ Type.getMethodDescriptor(
+ mirroredTypeName.mirroredToShadowed().toAsmObjectType(),
+ mirroredTypeName.toAsmObjectType())))
+ .setIsInterface(false)
+ .build();
+ }
+
+ /**
+ * Returns an {@link MethodInvocationSite} that serves as an adapter between desugar-mirrored
+ * invocations and desugar-shadowed invocations.
+ */
+ static MethodInvocationSite getAdapterInvocationSite(MethodInvocationSite methodInvocationSite) {
+ return MethodInvocationSite.builder()
+ .setInvocationKind(MemberUseKind.INVOKESTATIC)
+ .setMethod(
+ methodInvocationSite
+ .method()
+ .toAdapterMethodForArgsAndReturnTypes(methodInvocationSite.isStaticInvocation()))
+ .setIsInterface(false)
+ .build();
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdaptersGenerator.java b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdaptersGenerator.java
new file mode 100644
index 0000000..732b732
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdaptersGenerator.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2020 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.corelibadapter;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
+import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ACC_STATIC;
+import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.android.desugar.io.FileContentProvider;
+import com.google.devtools.build.android.desugar.langmodel.ClassName;
+import com.google.devtools.build.android.desugar.langmodel.InvocationSiteTransformationRecord;
+import com.google.devtools.build.android.desugar.langmodel.MethodDeclInfo;
+import com.google.devtools.build.android.desugar.langmodel.MethodInvocationSite;
+import com.google.devtools.build.android.desugar.langmodel.MethodKey;
+import java.io.ByteArrayInputStream;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/**
+ * Generates type adapter classes with methods that bridge the interactions between
+ * desugared-mirrored types and their desugar-shadowed built-in types, and delivers the generated
+ * classes to runtime library.
+ */
+public final class ShadowedApiAdaptersGenerator {
+
+ private static final int TYPE_ADAPTER_CLASS_ACCESS = ACC_PUBLIC | ACC_ABSTRACT | ACC_SYNTHETIC;
+ private static final int TYPE_CONVERSION_METHOD_ACCESS = ACC_PUBLIC | ACC_STATIC;
+
+ /** A pre-collected record that tracks the adapter method requests from invocation sites. */
+ private final InvocationSiteTransformationRecord invocationAdapterSites;
+
+ /** A record with evolving map values that track adapter classes to be generated. */
+ private final ImmutableMap<ClassName, ClassWriter> typeAdapters;
+
+ /** The public API that provides the file content of generate adapter classes. */
+ public static ImmutableList<FileContentProvider<ByteArrayInputStream>> generateAdapterClasses(
+ InvocationSiteTransformationRecord callSiteTransformations) {
+ return emitClassWriters(callSiteTransformations)
+ .emitAdapterMethods()
+ .closeClassWriters()
+ .provideFileContents();
+ }
+
+ private ShadowedApiAdaptersGenerator(
+ InvocationSiteTransformationRecord invocationAdapterSites,
+ ImmutableMap<ClassName, ClassWriter> typeAdapters) {
+ this.invocationAdapterSites = invocationAdapterSites;
+ this.typeAdapters = typeAdapters;
+ }
+
+ private static ShadowedApiAdaptersGenerator emitClassWriters(
+ InvocationSiteTransformationRecord callSiteTransformations) {
+ return new ShadowedApiAdaptersGenerator(
+ callSiteTransformations,
+ callSiteTransformations.record().stream()
+ .map(ShadowedApiAdapterHelper::getAdapterInvocationSite)
+ .map(MethodInvocationSite::owner)
+ .distinct()
+ .collect(
+ toImmutableMap(
+ className -> className,
+ ShadowedApiAdaptersGenerator::createAdapterClassWriter)));
+ }
+
+ private static ClassWriter createAdapterClassWriter(ClassName className) {
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+ cw.visit(
+ Opcodes.V1_7,
+ TYPE_ADAPTER_CLASS_ACCESS,
+ className.binaryName(),
+ /* signature= */ null,
+ /* superName= */ "java/lang/Object",
+ /* interfaces= */ new String[0]);
+ return cw;
+ }
+
+ private ShadowedApiAdaptersGenerator emitAdapterMethods() {
+ for (MethodInvocationSite invocationSite : invocationAdapterSites.record()) {
+ MethodInvocationSite adapterSite =
+ ShadowedApiAdapterHelper.getAdapterInvocationSite(invocationSite);
+ ClassName adapterOwner = adapterSite.owner();
+ ClassWriter cv =
+ checkNotNull(
+ typeAdapters.get(adapterOwner),
+ "Expected a class writer present before writing its methods. Requested adapter"
+ + " owner: (%s). Available adapter owners: (%s).",
+ adapterOwner,
+ typeAdapters);
+ MethodKey adapterMethodKey = adapterSite.method();
+ MethodDeclInfo adapterMethodDecl =
+ MethodDeclInfo.create(
+ adapterMethodKey,
+ TYPE_ADAPTER_CLASS_ACCESS,
+ TYPE_CONVERSION_METHOD_ACCESS,
+ /* signature= */ null,
+ /* exceptions= */ new String[] {});
+ MethodVisitor mv = adapterMethodDecl.accept(cv);
+ mv.visitCode();
+
+ int slotOffset = 0;
+ for (Type argType : adapterMethodDecl.argumentTypes()) {
+ ClassName argTypeName = ClassName.create(argType);
+ mv.visitVarInsn(argType.getOpcode(Opcodes.ILOAD), slotOffset);
+ if (argTypeName.isDesugarMirroredType()) {
+ MethodInvocationSite conversion =
+ ShadowedApiAdapterHelper.mirroredToShadowedTypeConversionSite(argTypeName);
+ conversion.accept(mv);
+ }
+ slotOffset += argType.getSize();
+ }
+
+ invocationSite.accept(mv);
+
+ ClassName adapterReturnTypeName = adapterMethodDecl.returnTypeName();
+ if (adapterReturnTypeName.isDesugarMirroredType()) {
+ MethodInvocationSite conversion =
+ ShadowedApiAdapterHelper.shadowedToMirroredTypeConversionSite(
+ adapterReturnTypeName.mirroredToShadowed());
+ conversion.accept(mv);
+ }
+
+ mv.visitInsn(adapterMethodDecl.returnType().getOpcode(Opcodes.IRETURN));
+
+ mv.visitMaxs(slotOffset, slotOffset);
+ mv.visitEnd();
+ }
+ return this;
+ }
+
+ private ShadowedApiAdaptersGenerator closeClassWriters() {
+ typeAdapters.values().forEach(ClassVisitor::visitEnd);
+ return this;
+ }
+
+ private ImmutableList<FileContentProvider<ByteArrayInputStream>> provideFileContents() {
+ return typeAdapters.entrySet().stream()
+ .map(
+ e ->
+ new FileContentProvider<>(
+ e.getKey().classFilePathName(),
+ () -> new ByteArrayInputStream(e.getValue().toByteArray())))
+ .collect(toImmutableList());
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiInvocationSite.java b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiInvocationSite.java
new file mode 100644
index 0000000..163cdcf
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiInvocationSite.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2020 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.corelibadapter;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiAdapterHelper.shouldUseApiTypeAdapter;
+import static com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiAdapterHelper.shouldUseInlineTypeConversion;
+import static com.google.devtools.build.android.desugar.langmodel.ClassName.IMMUTABLE_LABEL_ATTACHER;
+import static com.google.devtools.build.android.desugar.langmodel.ClassName.IN_PROCESS_LABEL_STRIPPER;
+import static com.google.devtools.build.android.desugar.langmodel.ClassName.SHADOWED_TO_MIRRORED_TYPE_MAPPER;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.android.desugar.langmodel.ClassName;
+import com.google.devtools.build.android.desugar.langmodel.InvocationSiteTransformationRecord.InvocationSiteTransformationRecordBuilder;
+import com.google.devtools.build.android.desugar.langmodel.LangModelHelper;
+import com.google.devtools.build.android.desugar.langmodel.MethodInvocationSite;
+import com.google.devtools.build.android.desugar.langmodel.MethodKey;
+import com.google.devtools.build.android.desugar.langmodel.SwitchableTypeMapper;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.ClassRemapper;
+import org.objectweb.asm.commons.MethodRemapper;
+import org.objectweb.asm.tree.MethodNode;
+
+/**
+ * Desugars the bytecode instructions that interacts with desugar-shadowed APIs, which is a method
+ * with desugar-shadowed types, e.g. {@code java.time.MonthDay}.
+ */
+public final class ShadowedApiInvocationSite extends ClassVisitor {
+
+ /** An evolving record that collects the adapter method requests from invocation sites. */
+ private final InvocationSiteTransformationRecordBuilder invocationSiteRecord;
+
+ public ShadowedApiInvocationSite(
+ ClassVisitor classVisitor, InvocationSiteTransformationRecordBuilder invocationSiteRecord) {
+ super(Opcodes.ASM7, classVisitor);
+ this.invocationSiteRecord = invocationSiteRecord;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
+ return mv == null
+ ? null
+ : AndroidJdkLibInvocationSiteMethodVisitor.create(api, mv, invocationSiteRecord);
+ }
+
+ /** Desugars instructions for the enclosing class visitor. */
+ private static class AndroidJdkLibInvocationSiteMethodVisitor extends MethodRemapper {
+
+ /** The tag included in the marker instruction. */
+ private static final String INLINE_PARAM_TYPE_CONVERSION_TAG = "__desugar_param_conversion__:";
+
+ private final SwitchableTypeMapper immutableLabelApplicator;
+
+ private final InvocationSiteTransformationRecordBuilder invocationSiteRecord;
+
+ /**
+ * The served method under parameter type conversion while visiting. The value can be {@code
+ * null} if no in-line type conversion is in process.
+ */
+ private MethodKey methodInParamInlineTypeConversionService;
+
+ private AndroidJdkLibInvocationSiteMethodVisitor(
+ int api,
+ MethodVisitor methodVisitor,
+ InvocationSiteTransformationRecordBuilder invocationSiteRecord,
+ SwitchableTypeMapper immutableLabelApplicator) {
+ super(api, methodVisitor, immutableLabelApplicator);
+ this.invocationSiteRecord = invocationSiteRecord;
+ this.immutableLabelApplicator = immutableLabelApplicator;
+ }
+
+ static AndroidJdkLibInvocationSiteMethodVisitor create(
+ int api,
+ MethodVisitor methodVisitor,
+ InvocationSiteTransformationRecordBuilder invocationSiteRecord) {
+ return new AndroidJdkLibInvocationSiteMethodVisitor(
+ api,
+ methodVisitor,
+ invocationSiteRecord,
+ new SwitchableTypeMapper(IN_PROCESS_LABEL_STRIPPER.andThen(IMMUTABLE_LABEL_ATTACHER)));
+ }
+
+ @Override
+ public void visitLdcInsn(Object value) {
+ if (value instanceof String
+ && ((String) value).startsWith(INLINE_PARAM_TYPE_CONVERSION_TAG)) {
+ String paramTypeConversionTag = (String) value;
+ visitParamInlineTypeConversionStart(
+ MethodKey.decode(
+ paramTypeConversionTag.substring(INLINE_PARAM_TYPE_CONVERSION_TAG.length())));
+ }
+ super.visitLdcInsn(value);
+ }
+
+ @Override
+ public void visitMethodInsn(
+ int opcode, String owner, String name, String descriptor, boolean isInterface) {
+ MethodInvocationSite verbatimInvocationSite =
+ MethodInvocationSite.create(opcode, owner, name, descriptor, isInterface)
+ .acceptTypeMapper(IN_PROCESS_LABEL_STRIPPER);
+
+ if (isInParamInlineTypeConversion()) {
+ super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ if (verbatimInvocationSite.method().equals(methodInParamInlineTypeConversionService)) {
+ visitParamInlineTypeConversionEnd(methodInParamInlineTypeConversionService);
+ }
+ return;
+ }
+
+ if (shouldUseInlineTypeConversion(verbatimInvocationSite)) {
+ MethodNode paramInlineInstructionsContainer = new MethodNode();
+ LangModelHelper.mapOperandStackValues(
+ paramInlineInstructionsContainer,
+ ImmutableList.of(ShadowedApiAdapterHelper::anyMirroredToBuiltinTypeConversion),
+ SHADOWED_TO_MIRRORED_TYPE_MAPPER.map(verbatimInvocationSite.argumentTypeNames()),
+ verbatimInvocationSite.argumentTypeNames(),
+ INLINE_PARAM_TYPE_CONVERSION_TAG + verbatimInvocationSite.method().encode());
+ verbatimInvocationSite.accept(paramInlineInstructionsContainer);
+
+ MethodRemapper methodRemapper = new MethodRemapper(mv, IMMUTABLE_LABEL_ATTACHER);
+ paramInlineInstructionsContainer.accept(methodRemapper);
+ return;
+ }
+
+ if (shouldUseApiTypeAdapter(verbatimInvocationSite)) {
+ checkState(!immutableLabelApplicator.isSwitchOn());
+ MethodInvocationSite adapterSite =
+ ShadowedApiAdapterHelper.getAdapterInvocationSite(verbatimInvocationSite);
+ invocationSiteRecord.addTransformation(verbatimInvocationSite);
+ adapterSite.acceptTypeMapper(IMMUTABLE_LABEL_ATTACHER).accept(mv);
+ return;
+ }
+
+ super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+
+ private boolean isInParamInlineTypeConversion() {
+ boolean isInParamInlineTypeConversion = methodInParamInlineTypeConversionService != null;
+ if (isInParamInlineTypeConversion) {
+ checkState(
+ immutableLabelApplicator.isSwitchOn(),
+ "Inconsistent state: Expected immutable label applicator is turned ON");
+ } else {
+ checkState(
+ !immutableLabelApplicator.isSwitchOn(),
+ "Inconsistent state: Expected immutable label applicator is turned OFF");
+ }
+ return isInParamInlineTypeConversion;
+ }
+
+ private void visitParamInlineTypeConversionStart(MethodKey method) {
+ checkState(!isInParamInlineTypeConversion());
+ methodInParamInlineTypeConversionService = checkNotNull(method);
+ immutableLabelApplicator.turnOn();
+ }
+
+ private void visitParamInlineTypeConversionEnd(MethodKey method) {
+ checkState(isInParamInlineTypeConversion());
+ checkState(
+ method.equals(methodInParamInlineTypeConversionService),
+ "Inconsistent state: Expected to end %s, but % is in service.",
+ method,
+ methodInParamInlineTypeConversionService);
+ methodInParamInlineTypeConversionService = null;
+ immutableLabelApplicator.turnOff();
+ }
+ }
+
+ /** Strips out immutable labels at the end of desugar pipeline. */
+ public static class ImmutableLabelRemover extends ClassRemapper {
+ public ImmutableLabelRemover(ClassVisitor cv) {
+ super(cv, ClassName.IMMUTABLE_LABEL_STRIPPER);
+ }
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/io/CoreLibraryRewriter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/io/CoreLibraryRewriter.java
index 71e6ed2..fa85f81 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/io/CoreLibraryRewriter.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/io/CoreLibraryRewriter.java
@@ -105,7 +105,7 @@
private ClassName prefix(ClassName className) {
if (shouldPrefix(className.binaryName())) {
- return className.prependPrefix(prefix);
+ return className.withPackagePrefix(prefix);
}
return className;
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassName.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassName.java
index 75fa1c5..01ec25e 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassName.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassName.java
@@ -35,8 +35,20 @@
public static final String IN_PROCESS_LABEL = "__desugar__/";
+ private static final String IMMUTABLE_LABEL_LABEL = "__final__/";
+
private static final String TYPE_ADAPTER_PACKAGE_ROOT = "desugar/runtime/typeadapter/";
+ public static final TypeMapper IN_PROCESS_LABEL_STRIPPER =
+ new TypeMapper(className -> className.stripPackagePrefix(IN_PROCESS_LABEL));
+
+ public static final TypeMapper IMMUTABLE_LABEL_STRIPPER =
+ new TypeMapper(className -> className.stripPackagePrefix(IMMUTABLE_LABEL_LABEL));
+
+ private static final String TYPE_ADAPTER_SUFFIX = "Adapter";
+
+ private static final String TYPE_CONVERTER_SUFFIX = "Converter";
+
/**
* The primitive type as specified at
* https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-2.html#jvms-2.3
@@ -53,7 +65,6 @@
.put("J", Type.LONG_TYPE)
.put("D", Type.DOUBLE_TYPE)
.build();
-
/**
* The primitive type as specified at
* https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-2.html#jvms-2.3
@@ -71,26 +82,12 @@
.put(ClassName.create(Type.DOUBLE_TYPE), ClassName.create("java/lang/Double"))
.build();
- private static final ImmutableBiMap<String, String> DELIVERY_TYPE_MAPPINGS =
- ImmutableBiMap.<String, String>builder()
- .put("java/", "j$/")
- .put("javadesugar/", "jd$/")
- .build();
-
- public static final TypeMapper IN_PROCESS_LABEL_STRIPPER =
- new TypeMapper(ClassName::verbatimName);
-
- public static final TypeMapper DELIVERY_TYPE_MAPPER =
- new TypeMapper(ClassName::verbatimToDelivery);
-
- public static final TypeMapper VERBATIM_TYPE_MAPPER =
- new TypeMapper(ClassName::deliveryToVerbatim);
-
- /**
- * The textual binary name used to index the class name, as defined at,
- * https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.2.1
- */
- public abstract String binaryName();
+ private static final ImmutableBiMap<String, String> SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS =
+ ImmutableBiMap.<String, String>builder().put("javadesugar/", "jd$/").build();
+ public static final TypeMapper SHADOWED_TO_MIRRORED_TYPE_MAPPER =
+ new TypeMapper(ClassName::shadowedToMirrored);
+ public static final TypeMapper IMMUTABLE_LABEL_ATTACHER =
+ new TypeMapper(ClassName::withCoreTypeImmutableLabel);
public static ClassName create(String binaryName) {
checkArgument(
@@ -109,6 +106,29 @@
return create(asmType.getInternalName());
}
+ public static ClassName fromClassFileName(String fileName) {
+ checkArgument(
+ fileName.endsWith(".class"), "Expected a class file (*.class). Actual: (%s).", fileName);
+ return ClassName.create(fileName.substring(0, fileName.length() - ".class".length()));
+ }
+
+ private static void checkPackagePrefixFormat(String prefix) {
+ checkArgument(
+ prefix.isEmpty() || prefix.endsWith("/"),
+ "Expected (%s) to be a package prefix of ending with '/'.",
+ prefix);
+ checkArgument(
+ !prefix.contains("."),
+ "Expected a '/'-delimited binary name instead of a '.'-delimited qualified name for %s",
+ prefix);
+ }
+
+ /**
+ * The textual binary name used to index the class name, as defined at,
+ * https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.2.1
+ */
+ public abstract String binaryName();
+
public final Type toAsmObjectType() {
return isPrimitive() ? PRIMITIVES_TYPES.get(binaryName()) : Type.getObjectType(binaryName());
}
@@ -162,39 +182,73 @@
return hasPackagePrefix(IN_PROCESS_LABEL);
}
- private ClassName stripInProcessLabel() {
- return stripPackagePrefix(IN_PROCESS_LABEL);
+ public final boolean hasImmutableLabel() {
+ return hasPackagePrefix(IMMUTABLE_LABEL_LABEL);
}
- private ClassName stripInProcessLabelIfAny() {
- return hasInProcessLabel() ? stripPackagePrefix(IN_PROCESS_LABEL) : this;
- }
-
- /** Strips out in-process labels if any. */
- public final ClassName verbatimName() {
- return stripInProcessLabelIfAny().deliveryToVerbatim();
- }
-
+ /**
+ * Returns a new instance of {@code ClassName} that represents the owner class with adapter
+ * methods for an Android SDK APIs.
+ */
public final ClassName typeAdapterOwner() {
- return verbatimName().withSimpleNameSuffix("Adapter").prependPrefix(TYPE_ADAPTER_PACKAGE_ROOT);
+ checkState(
+ !hasInProcessLabel() && !hasImmutableLabel(),
+ "Expected a label-free type: Actual(%s)",
+ this);
+ checkState(
+ isInPackageEligibleForTypeAdapter(),
+ "Expected an Android SDK type to have an adapter: Actual (%s)",
+ this);
+ return withSimpleNameSuffix(TYPE_ADAPTER_SUFFIX).withPackagePrefix(TYPE_ADAPTER_PACKAGE_ROOT);
}
+ /**
+ * Returns a new instance of {@code ClassName} that represents the owner class with conversion
+ * methods between JDK built-in types and desguar-mirrored types.
+ */
public final ClassName typeConverterOwner() {
- return verbatimName()
- .withSimpleNameSuffix("Converter")
- .prependPrefix(TYPE_ADAPTER_PACKAGE_ROOT);
+ checkState(
+ !hasInProcessLabel() && !hasImmutableLabel(),
+ "Expected a label-free type: Actual(%s)",
+ this);
+ checkState(
+ isDesugarShadowedType(),
+ "Expected an JDK built-in type to have an converter: Actual (%s)",
+ this);
+ return withSimpleNameSuffix(TYPE_CONVERTER_SUFFIX).withPackagePrefix(TYPE_ADAPTER_PACKAGE_ROOT);
}
- public final ClassName verbatimToDelivery() {
- return DELIVERY_TYPE_MAPPINGS.keySet().stream()
+ /**
+ * Returns a new instance of {@code ClassName} attached with an immutable label which marks the
+ * type is not subject to further desugar operations until the final label striping.
+ */
+ public final ClassName withCoreTypeImmutableLabel() {
+ return isDesugarShadowedType() ? withPackagePrefix(IMMUTABLE_LABEL_LABEL) : this;
+ }
+
+ /**
+ * Returns a new instance of {@code ClassName} that is the desugar-mirrored core type (e.g. {@code
+ * j$/time/MonthDay}) of the current shadowed built-in core type, assuming {@code this} instance
+ * is a desugared-shadowed built-in core type.
+ */
+ public final ClassName shadowedToMirrored() {
+ return SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS.keySet().stream()
.filter(this::hasPackagePrefix)
- .map(prefix -> replacePackagePrefix(prefix, DELIVERY_TYPE_MAPPINGS.get(prefix)))
+ .map(
+ prefix ->
+ replacePackagePrefix(prefix, SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS.get(prefix)))
.findAny()
.orElse(this);
}
- public final ClassName deliveryToVerbatim() {
- ImmutableBiMap<String, String> verbatimTypeMappings = DELIVERY_TYPE_MAPPINGS.inverse();
+ /**
+ * Returns a new instance of {@code ClassName} that is a shadowed built-in core type (e.g. {@code
+ * java/time/MonthDay}) of the current desugar-mirrored core type, assuming {@code this} instance
+ * is a desugar-mirrored core type.
+ */
+ public final ClassName mirroredToShadowed() {
+ ImmutableBiMap<String, String> verbatimTypeMappings =
+ SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS.inverse();
return verbatimTypeMappings.keySet().stream()
.filter(this::hasPackagePrefix)
.map(prefix -> replacePackagePrefix(prefix, verbatimTypeMappings.get(prefix)))
@@ -202,7 +256,7 @@
.orElse(this);
}
- public final ClassName prependPrefix(String prefix) {
+ public final ClassName withPackagePrefix(String prefix) {
checkPackagePrefixFormat(prefix);
return ClassName.create(prefix + binaryName());
}
@@ -219,11 +273,43 @@
return prefixes.stream().anyMatch(this::hasPackagePrefix);
}
- public final ClassName stripPackagePrefix(String prefix) {
+ public final boolean isDesugarEligible(boolean enableDesugarBuiltinJdk) {
+ if (isDesugarShadowedType()) {
+ return enableDesugarBuiltinJdk;
+ }
+ return !isInPackageEligibleForTypeAdapter()
+ && !isInDesugarRuntimeLibrary()
+ && !isDesugarMirroredType();
+ }
+
+ public final boolean isInPackageEligibleForTypeAdapter() {
+ // TODO(b/152573900): Update to hasPackagePrefix("android/") once all package-wise incremental
+ // rollouts are complete.
+ return hasAnyPackagePrefix("android/testing/");
+ }
+
+ public final boolean isInDesugarRuntimeLibrary() {
+ return hasAnyPackagePrefix(
+ "com/google/devtools/build/android/desugar/runtime/", "desugar/runtime/");
+ }
+
+ public final boolean isDesugarShadowedType() {
+ return hasAnyPackagePrefix(SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS.keySet());
+ }
+
+ public final boolean isDesugarMirroredType() {
+ return hasAnyPackagePrefix(SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS.values());
+ }
+
+ private ClassName stripPackagePrefix(String prefix) {
+ return hasPackagePrefix(prefix) ? stripRequiredPackagePrefix(prefix) : this;
+ }
+
+ private ClassName stripRequiredPackagePrefix(String prefix) {
return replacePackagePrefix(/* originalPrefix= */ prefix, /* targetPrefix= */ "");
}
- public final ClassName replacePackagePrefix(String originalPrefix, String targetPrefix) {
+ private ClassName replacePackagePrefix(String originalPrefix, String targetPrefix) {
checkState(
hasPackagePrefix(originalPrefix),
"Expected %s to have a package prefix of (%s) before stripping.",
@@ -237,27 +323,4 @@
public ClassName acceptTypeMapper(TypeMapper typeMapper) {
return typeMapper.map(this);
}
-
- private static void checkPackagePrefixFormat(String prefix) {
- checkArgument(
- prefix.isEmpty() || prefix.endsWith("/"),
- "Expected (%s) to be a package prefix of ending with '/'.",
- prefix);
- checkArgument(
- !prefix.contains("."),
- "Expected a '/'-delimited binary name instead of a '.'-delimited qualified name for %s",
- prefix);
- }
-
- public boolean isInProcessCoreType() {
- return hasInProcessLabel() && stripInProcessLabel().isVerbatimCoreType();
- }
-
- public boolean isVerbatimCoreType() {
- return hasAnyPackagePrefix(DELIVERY_TYPE_MAPPINGS.keySet());
- }
-
- public boolean isDeliveryCoreType() {
- return hasAnyPackagePrefix(DELIVERY_TYPE_MAPPINGS.values());
- }
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/LangModelHelper.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/LangModelHelper.java
index a67657b..01aa824 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/LangModelHelper.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/LangModelHelper.java
@@ -21,11 +21,11 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Streams;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
+import java.util.stream.Collectors;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
@@ -158,30 +158,40 @@
* @param mv The current method visitor that is visiting the class.
* @param mappers Applies to an operand stack value if tested positive on {@code filter}.
* @param expectedTypesOnOperandStack The expected types at the bottom of the operand stack. The
- * end of the list corresponds to the the bottom of the operand stack.
+ * @param extraConstants Extra constants to be stored in the object array.
*/
public static ImmutableList<ClassName> collapseStackValuesToObjectArray(
MethodVisitor mv,
ImmutableList<Function<ClassName, Optional<MethodInvocationSite>>> mappers,
- ImmutableList<ClassName> expectedTypesOnOperandStack) {
+ ImmutableList<ClassName> expectedTypesOnOperandStack,
+ ImmutableList<Object> extraConstants) {
// Creates an array of java/lang/Object to store the values on top of the operand stack that
// are subject to string concatenation.
- int numOfValuesOnOperandStack = expectedTypesOnOperandStack.size();
+
+ for (Object constant : extraConstants) {
+ mv.visitLdcInsn(constant);
+ }
+
+ int numOfValuesOnOperandStack = expectedTypesOnOperandStack.size() + extraConstants.size();
+
visitPushInstr(mv, numOfValuesOnOperandStack);
mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
// To preserve the order of the operands to be string-concatenated, we slot the values on
// the top of the stack to the end of the array.
- List<ClassName> actualTypesOnObjectArray = new ArrayList<>(expectedTypesOnOperandStack);
+ List<ClassName> actualTypesInObjectArray =
+ Streams.concat(
+ expectedTypesOnOperandStack.stream(),
+ extraConstants.stream().map(Object::getClass).map(ClassName::create))
+ .collect(Collectors.toList());
for (int i = numOfValuesOnOperandStack - 1; i >= 0; i--) {
- ClassName operandTypeName = expectedTypesOnOperandStack.get(i);
- Type operandType = operandTypeName.toAsmObjectType();
+ Type arrayElementType = actualTypesInObjectArray.get(i).toAsmObjectType();
// Pre-duplicates the array reference for next loop iteration use.
// Post-operation stack bottom to top:
// ..., value_i-1, arrayref, value_i, arrayref.
mv.visitInsn(
getTypeSizeAlignedDupOpcode(
- ImmutableList.of(Type.getType(Object.class)), ImmutableList.of(operandType)));
+ ImmutableList.of(Type.getType(Object.class)), ImmutableList.of(arrayElementType)));
// Pushes the array index and adjusts the order of the values on stack top in the order
// of <bottom/> arrayref, index, value <top/> before emitting an aastore instruction.
@@ -195,7 +205,7 @@
mv.visitInsn(
getTypeSizeAlignedDupOpcode(
ImmutableList.of(Type.getType(Object.class), Type.getType(int.class)),
- ImmutableList.of(operandType)));
+ ImmutableList.of(arrayElementType)));
// Pops arrayref, index, leaving the stack top as value_i.
// Post-operation stack bottom to top:
@@ -206,19 +216,19 @@
int targetArrayIndex = i;
mappers.stream()
- .map(mapper -> mapper.apply(actualTypesOnObjectArray.get(targetArrayIndex)))
+ .map(mapper -> mapper.apply(actualTypesInObjectArray.get(targetArrayIndex)))
.flatMap(Streams::stream)
.forEach(
typeConversionSite -> {
typeConversionSite.accept(mv);
- actualTypesOnObjectArray.set(targetArrayIndex, typeConversionSite.returnTypeName());
+ actualTypesInObjectArray.set(targetArrayIndex, typeConversionSite.returnTypeName());
});
// Post-operation stack bottom to top:
// ..., value_i-1, arrayref.
mv.visitInsn(Opcodes.AASTORE);
}
- return ImmutableList.copyOf(actualTypesOnObjectArray);
+ return ImmutableList.copyOf(actualTypesInObjectArray);
}
/**
@@ -278,6 +288,25 @@
mv.visitInsn(Opcodes.POP);
}
+ public static ImmutableList<ClassName> mapOperandStackValues(
+ MethodVisitor mv,
+ ImmutableList<Function<ClassName, Optional<MethodInvocationSite>>> mappers,
+ ImmutableList<ClassName> sourceTypesOnOperandStack,
+ ImmutableList<ClassName> targetTypesOnOperandStack,
+ String beginningMarker) {
+ ImmutableList<ClassName> classNames =
+ collapseStackValuesToObjectArray(
+ mv,
+ ImmutableList.<Function<ClassName, Optional<MethodInvocationSite>>>builder()
+ .addAll(mappers)
+ .add(LangModelHelper::anyPrimitiveToBoxedTypeInvocationSite)
+ .build(),
+ sourceTypesOnOperandStack,
+ ImmutableList.of(beginningMarker));
+ expandObjectArrayToStackValues(mv, targetTypesOnOperandStack);
+ return classNames;
+ }
+
public static Optional<MethodInvocationSite> anyPrimitiveToStringInvocationSite(
ClassName className) {
return className.isPrimitive()
@@ -304,6 +333,22 @@
.build();
}
+ public static Optional<MethodInvocationSite> anyObjectToStringInvocationSite(
+ ClassName className) {
+ return className.isPrimitive()
+ ? Optional.empty()
+ : Optional.of(
+ MethodInvocationSite.builder()
+ .setInvocationKind(MemberUseKind.INVOKEVIRTUAL)
+ .setMethod(
+ MethodKey.create(
+ className,
+ "toString",
+ Type.getMethodDescriptor(Type.getType(String.class))))
+ .setIsInterface(false)
+ .build());
+ }
+
/** Convenient factory method for converting a primitive type to string call site. */
public static Optional<MethodInvocationSite> anyPrimitiveToBoxedTypeInvocationSite(
ClassName className) {
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodInvocationSite.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodInvocationSite.java
index dffbb3b..704da84 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodInvocationSite.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodInvocationSite.java
@@ -17,6 +17,7 @@
package com.google.devtools.build.android.desugar.langmodel;
import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
import org.objectweb.asm.MethodVisitor;
/** A value object that represents an method invocation site. */
@@ -65,6 +66,10 @@
return method().getReturnTypeName();
}
+ public final ImmutableList<ClassName> argumentTypeNames() {
+ return method().getArgumentTypeNames();
+ }
+
public final boolean isStaticInvocation() {
return invocationKind() == MemberUseKind.INVOKESTATIC;
}
@@ -83,14 +88,6 @@
return toBuilder().setMethod(method().acceptTypeMapper(typeMapper)).build();
}
- public final MethodInvocationSite toAdapterInvocationSite() {
- return MethodInvocationSite.builder()
- .setInvocationKind(MemberUseKind.INVOKESTATIC)
- .setMethod(method().toArgumentTypeAdapter(isStaticInvocation()))
- .setIsInterface(false)
- .build();
- }
-
/** The builder for {@link MethodInvocationSite}. */
@AutoValue.Builder
public abstract static class MethodInvocationSiteBuilder {
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodKey.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodKey.java
index 0bd249c..53f5b90 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodKey.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodKey.java
@@ -20,13 +20,17 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.auto.value.AutoValue;
+import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
+import java.util.List;
import org.objectweb.asm.Type;
/** The key to index a class or interface method or constructor. */
@AutoValue
public abstract class MethodKey extends ClassMemberKey<MethodKey> {
+ private static final Splitter COLON_SPLITTER = Splitter.on(":");
+
/** The factory method for {@link MethodKey}. */
public static MethodKey create(ClassName ownerClass, String name, String descriptor) {
checkState(
@@ -39,6 +43,16 @@
return new AutoValue_MethodKey(ownerClass, name, descriptor);
}
+ public final String encode() {
+ return ownerName() + ":" + name() + ":" + descriptor();
+ }
+
+ public static MethodKey decode(String textFormat) {
+ List<String> payloads = COLON_SPLITTER.splitToList(textFormat);
+ checkState(payloads.size() == 3, "Invalid text format for method key: Actual(%s)", textFormat);
+ return MethodKey.create(ClassName.create(payloads.get(0)), payloads.get(1), payloads.get(2));
+ }
+
/** The return type of a method. */
public Type getReturnType() {
return Type.getReturnType(descriptor());
@@ -64,7 +78,7 @@
return getArgumentTypes().stream().map(ClassName::create).collect(toImmutableList());
}
- public MethodKey toArgumentTypeAdapter(boolean fromStaticOrigin) {
+ public MethodKey toAdapterMethodForArgsAndReturnTypes(boolean fromStaticOrigin) {
ClassName typeAdapterOwner = owner().typeAdapterOwner();
checkState(
!isConstructor(), "Argument type adapter for constructor is not supported: %s. ", this);
@@ -73,7 +87,7 @@
typeAdapterOwner,
name(),
fromStaticOrigin ? descriptor() : instanceMethodToStaticDescriptor())
- .acceptTypeMapper(ClassName.DELIVERY_TYPE_MAPPER);
+ .acceptTypeMapper(ClassName.SHADOWED_TO_MIRRORED_TYPE_MAPPER);
}
/** The synthetic constructor for a private constructor. */
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/SwitchableTypeMapper.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/SwitchableTypeMapper.java
new file mode 100644
index 0000000..abe8d1d
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/SwitchableTypeMapper.java
@@ -0,0 +1,52 @@
+/*
+ *
+ * Copyright 2020 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.langmodel;
+
+import org.objectweb.asm.commons.Remapper;
+
+/** A {@link Remapper} with a user-controlled switch which sets whether the type mapping applies. */
+public final class SwitchableTypeMapper extends Remapper {
+
+ private final TypeMapper mapper;
+ private boolean isSwitchOn;
+
+ public SwitchableTypeMapper(TypeMapper mapper) {
+ this.mapper = mapper;
+ }
+
+ @Override
+ public String map(String binaryName) {
+ return map(ClassName.create(binaryName)).binaryName();
+ }
+
+ public ClassName map(ClassName className) {
+ return isSwitchOn() ? className.acceptTypeMapper(mapper) : className;
+ }
+
+ public boolean isSwitchOn() {
+ return isSwitchOn;
+ }
+
+ public void turnOn() {
+ isSwitchOn = true;
+ }
+
+ public void turnOff() {
+ isSwitchOn = false;
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/TypeMapper.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/TypeMapper.java
index 4ed92bd..d8aeef0 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/TypeMapper.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/TypeMapper.java
@@ -16,6 +16,7 @@
package com.google.devtools.build.android.desugar.langmodel;
+import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
@@ -25,15 +26,15 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
-import java.util.function.Function;
+import java.util.function.UnaryOperator;
import org.objectweb.asm.commons.Remapper;
/** Maps a type to another based on binary names. */
public final class TypeMapper extends Remapper {
- private final Function<ClassName, ClassName> classNameMapper;
+ private final UnaryOperator<ClassName> classNameMapper;
- public TypeMapper(Function<ClassName, ClassName> classNameMapper) {
+ public TypeMapper(UnaryOperator<ClassName> classNameMapper) {
this.classNameMapper = classNameMapper;
}
@@ -46,8 +47,7 @@
return classNameMapper.apply(internalName);
}
- public <E extends TypeMappable<E>> ImmutableList<? extends E> map(
- ImmutableList<E> mappableTypes) {
+ public <E extends TypeMappable<E>> ImmutableList<E> map(ImmutableList<E> mappableTypes) {
return mappableTypes.stream().map(e -> e.acceptTypeMapper(this)).collect(toImmutableList());
}
@@ -76,4 +76,8 @@
.collect(toImmutableMap(e -> e.getKey().acceptTypeMapper(this), e -> e.getValue()));
}
+ public TypeMapper andThen(TypeMapper after) {
+ return new TypeMapper(
+ className -> className.acceptTypeMapper(this).acceptTypeMapper(checkNotNull(after)));
+ }
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/IndyStringConcatDesugaring.java b/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/IndyStringConcatDesugaring.java
index 21ca226..ed937a5 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/IndyStringConcatDesugaring.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/IndyStringConcatDesugaring.java
@@ -110,7 +110,8 @@
ImmutableList.of(LangModelHelper::anyPrimitiveToStringInvocationSite),
Arrays.stream(Type.getArgumentTypes(descriptor))
.map(ClassName::create)
- .collect(toImmutableList()));
+ .collect(toImmutableList()),
+ ImmutableList.of());
String recipe = (String) bootstrapMethodArguments[0];
visitLdcInsn(recipe);