Towards synthesizing Kotlin @Metadata: supertypes.
Bug: 143687784, 70169921
Change-Id: I8b955570f9d7864979c139d642e984c52d6a5f47
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index acb6c55..9f77f7a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -4,17 +4,16 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmType;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.DescriptorUtils;
-import java.util.ListIterator;
+import java.util.List;
import kotlinx.metadata.KmClass;
import kotlinx.metadata.KmType;
-import kotlinx.metadata.KmTypeVisitor;
import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -42,35 +41,18 @@
@Override
void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
- ListIterator<KmType> superTypeIterator = kmClass.getSupertypes().listIterator();
- while (superTypeIterator.hasNext()) {
- KmType kmType = superTypeIterator.next();
- Box<Boolean> isLive = new Box<>(false);
- Box<DexType> renamed = new Box<>(null);
- kmType.accept(new KmTypeVisitor() {
- @Override
- public void visitClass(String name) {
- String descriptor = DescriptorUtils.getDescriptorFromKotlinClassifier(name);
- DexType type = appView.dexItemFactory().createType(descriptor);
- isLive.set(appView.appInfo().isLiveProgramType(type));
- DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
- if (renamedType != type) {
- renamed.set(renamedType);
- }
- }
- });
- if (!isLive.get()) {
- superTypeIterator.remove();
- continue;
+ List<KmType> superTypes = kmClass.getSupertypes();
+ superTypes.clear();
+ for (DexType itfType : clazz.interfaces.values) {
+ KmType kmType = toRenamedKmType(itfType, appView, lens);
+ if (kmType != null) {
+ superTypes.add(kmType);
}
- if (renamed.get() != null) {
- // TODO(b/70169921): need a general util to convert the current clazz's access flag.
- KmType renamedKmType = new KmType(kmType.getFlags());
- renamedKmType.visitClass(
- DescriptorUtils.descriptorToInternalName(renamed.get().toDescriptorString()));
- superTypeIterator.remove();
- superTypeIterator.add(renamedKmType);
- }
+ }
+ assert clazz.superType != null;
+ KmType kmTypeForSupertype = toRenamedKmType(clazz.superType, appView, lens);
+ if (kmTypeForSupertype != null) {
+ superTypes.add(kmTypeForSupertype);
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
new file mode 100644
index 0000000..0b061f3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import kotlinx.metadata.KmType;
+
+class KotlinMetadataSynthesizer {
+ static KmType toRenamedKmType(
+ DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ DexClass clazz = appView.definitionFor(type);
+ if (clazz == null) {
+ return null;
+ }
+ // For library or classpath class, synthesize @Metadata always.
+ if (clazz.isNotProgramClass()) {
+ KmType kmType = new KmType(clazz.accessFlags.getAsCfAccessFlags());
+ assert type == lens.lookupType(type, appView.dexItemFactory());
+ kmType.visitClass(DescriptorUtils.descriptorToInternalName(type.toDescriptorString()));
+ return kmType;
+ }
+ // From now on, it is a program class. First, make sure it is live.
+ if (!appView.appInfo().isLiveProgramType(type)) {
+ return null;
+ }
+ KmType kmType = new KmType(clazz.accessFlags.getAsCfAccessFlags());
+ DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
+ kmType.visitClass(DescriptorUtils.descriptorToInternalName(renamedType.toDescriptorString()));
+ return kmType;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java
new file mode 100644
index 0000000..d5297d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java
@@ -0,0 +1,168 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRenameInClasspathTypeTest extends KotlinMetadataTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRenameInClasspathTypeTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static Path baseLibJar;
+ private static Path extLibJar;
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ String baseLibFolder = PKG_PREFIX + "/classpath_lib_base";
+ baseLibJar =
+ kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addSourceFiles(getKotlinFileInTest(baseLibFolder, "itf"))
+ .compile();
+ String extLibFolder = PKG_PREFIX + "/classpath_lib_ext";
+ extLibJar =
+ kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addClasspathFiles(baseLibJar)
+ .addSourceFiles(getKotlinFileInTest(extLibFolder, "impl"))
+ .compile();
+ }
+
+ @Test
+ public void testMetadataInClasspathType_merged() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(baseLibJar)
+ .addProgramFiles(extLibJar)
+ // Keep the Extra class and its interface (which has the method).
+ .addKeepRules("-keep class **.Extra")
+ // Keep the ImplKt extension method which requires metadata
+ // to be called with Kotlin syntax from other kotlin code.
+ .addKeepRules("-keep class **.ImplKt { <methods>; }")
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile();
+ String pkg = getClass().getPackage().getName();
+ final String implClassName = pkg + ".classpath_lib_ext.Impl";
+ final String extraClassName = pkg + ".classpath_lib_ext.Extra";
+ compileResult.inspect(inspector -> {
+ ClassSubject impl = inspector.clazz(implClassName);
+ assertThat(impl, not(isPresent()));
+
+ ClassSubject extra = inspector.clazz(extraClassName);
+ assertThat(extra, isPresent());
+ assertThat(extra, not(isRenamed()));
+ // API entry is kept, hence the presence of Metadata.
+ KmClassSubject kmClass = extra.getKmClass();
+ assertThat(kmClass, isPresent());
+ List<ClassSubject> superTypes = kmClass.getSuperTypes();
+ assertTrue(superTypes.stream().noneMatch(
+ supertype -> supertype.getFinalDescriptor().contains("Impl")));
+ // Can't build ClassSubject with Itf in classpath. Instead, check if the reference to Itf is
+ // not altered via descriptors.
+ List<String> superTypeDescriptors = kmClass.getSuperTypeDescriptors();
+ assertTrue(superTypeDescriptors.stream().noneMatch(supertype -> supertype.contains("Impl")));
+ assertTrue(superTypeDescriptors.stream().anyMatch(supertype -> supertype.contains("Itf")));
+ });
+
+ Path libJar = compileResult.writeToZip();
+
+ String appFolder = PKG_PREFIX + "/classpath_app";
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addClasspathFiles(baseLibJar, libJar)
+ .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), baseLibJar, libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), pkg + ".classpath_app.MainKt")
+ .assertSuccessWithOutputLines("Impl::foo");
+ }
+
+ @Test
+ public void testMetadataInClasspathType_renamed() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(baseLibJar)
+ .addProgramFiles(extLibJar)
+ // Keep the Extra class and its interface (which has the method).
+ .addKeepRules("-keep class **.Extra")
+ // Keep Super, but allow minification.
+ .addKeepRules("-keep,allowobfuscation class **.Impl")
+ // Keep the ImplKt extension method which requires metadata
+ // to be called with Kotlin syntax from other kotlin code.
+ .addKeepRules("-keep class **.ImplKt { <methods>; }")
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile();
+ String pkg = getClass().getPackage().getName();
+ final String implClassName = pkg + ".classpath_lib_ext.Impl";
+ final String extraClassName = pkg + ".classpath_lib_ext.Extra";
+ compileResult.inspect(inspector -> {
+ ClassSubject impl = inspector.clazz(implClassName);
+ assertThat(impl, isPresent());
+ assertThat(impl, isRenamed());
+
+ ClassSubject extra = inspector.clazz(extraClassName);
+ assertThat(extra, isPresent());
+ assertThat(extra, not(isRenamed()));
+ // API entry is kept, hence the presence of Metadata.
+ KmClassSubject kmClass = extra.getKmClass();
+ assertThat(kmClass, isPresent());
+ List<ClassSubject> superTypes = kmClass.getSuperTypes();
+ assertTrue(superTypes.stream().noneMatch(
+ supertype -> supertype.getFinalDescriptor().contains("Impl")));
+ assertTrue(superTypes.stream().anyMatch(
+ supertype -> supertype.getFinalDescriptor().equals(impl.getFinalDescriptor())));
+ });
+
+ Path libJar = compileResult.writeToZip();
+
+ String appFolder = PKG_PREFIX + "/classpath_app";
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addClasspathFiles(baseLibJar, libJar)
+ .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), baseLibJar, libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), pkg + ".classpath_app.MainKt")
+ .assertSuccessWithOutputLines("Impl::foo");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
index 640b18b..e7692bc 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
@@ -6,17 +6,14 @@
import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.KmClassSubject;
@@ -90,16 +87,18 @@
Path libJar = compileResult.writeToZip();
String appFolder = PKG_PREFIX + "/extension_app";
- ProcessResult kotlinTestCompileResult =
+ Path output =
kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
.addClasspathFiles(libJar)
.addSourceFiles(getKotlinFileInTest(appFolder, "main"))
.setOutputPath(temp.newFolder().toPath())
- // TODO(b/143687784): update to just .compile() once fixed.
- .compileRaw();
- // TODO(b/143687784): should be able to compile!
- assertNotEquals(0, kotlinTestCompileResult.exitCode);
- assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: doStuff"));
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), pkg + ".extension_app.MainKt")
+ .assertSuccessWithOutputLines("do stuff", "do stuff");
}
@Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt
new file mode 100644
index 0000000..3c1585a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt
@@ -0,0 +1,11 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata.classpath_app
+
+import com.android.tools.r8.kotlin.metadata.classpath_lib_ext.Extra
+import com.android.tools.r8.kotlin.metadata.classpath_lib_ext.extension
+
+fun main() {
+ Extra().extension()
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_base/itf.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_base/itf.kt
new file mode 100644
index 0000000..9df88be
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_base/itf.kt
@@ -0,0 +1,8 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata.classpath_lib_base
+
+interface Itf {
+ fun foo()
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt
new file mode 100644
index 0000000..975865b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt
@@ -0,0 +1,18 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata.classpath_lib_ext
+
+import com.android.tools.r8.kotlin.metadata.classpath_lib_base.Itf
+
+open class Impl : Itf {
+ override fun foo() {
+ println("Impl::foo")
+ }
+}
+
+class Extra : Impl()
+
+fun Extra.extension() {
+ foo()
+}
\ No newline at end of file