Add tests for @Metadata rewriting of sealedSubclasses.
Bug: 70169921
Change-Id: I32193c60a0522323e0ce61a9d839c6044b0efb26
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInCompanionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInCompanionTest.java
index 949b0f2..a5c01a5 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInCompanionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInCompanionTest.java
@@ -75,7 +75,6 @@
final String companionClassName = pkg + ".companion_lib.B$Companion";
compileResult.inspect(inspector -> {
ClassSubject sup = inspector.clazz(superClassName);
- assertThat(sup, isPresent());
assertThat(sup, isRenamed());
ClassSubject impl = inspector.clazz(bClassName);
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSealedClassTest.java
new file mode 100644
index 0000000..725576d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSealedClassTest.java
@@ -0,0 +1,185 @@
+// Copyright (c) 2020, 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.DescriptorUtils.descriptorToJavaType;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
+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.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+
+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;
+import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
+import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRenameInSealedClassTest 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 MetadataRenameInSealedClassTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static Path sealedLibJar;
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ String sealedLibFolder = PKG_PREFIX + "/sealed_lib";
+ sealedLibJar =
+ kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addSourceFiles(getKotlinFileInTest(sealedLibFolder, "lib"))
+ .compile();
+ }
+
+ @Test
+ public void testMetadataInSealedClass_valid() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(sealedLibJar)
+ // Keep the Expr class
+ .addKeepRules("-keep class **.Expr")
+ // Keep the extension function
+ .addKeepRules("-keep class **.LibKt { <methods>; }")
+ // Keep the factory object and utils
+ .addKeepRules("-keep class **.ExprFactory { *; }")
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile();
+ String pkg = getClass().getPackage().getName();
+ final String numClassName = pkg + ".sealed_lib.Num";
+ final String exprClassName = pkg + ".sealed_lib.Expr";
+ final String libClassName = pkg + ".sealed_lib.LibKt";
+ compileResult.inspect(inspector -> {
+ ClassSubject num = inspector.clazz(numClassName);
+ assertThat(num, isRenamed());
+
+ ClassSubject expr = inspector.clazz(exprClassName);
+ assertThat(expr, isPresent());
+ assertThat(expr, not(isRenamed()));
+
+ KmClassSubject kmClass = expr.getKmClass();
+ assertThat(kmClass, isPresent());
+
+ kmClass.getSealedSubclassDescriptors().forEach(sealedSubclassDescriptor -> {
+ ClassSubject sealedSubclass =
+ inspector.clazz(descriptorToJavaType(sealedSubclassDescriptor));
+ // TODO(b/70169921): sealedSubclasses in KmClass should refer to live classes.
+ // assertThat(sealedSubclass, isRenamed());
+ // TODO(b/70169921): sealedSubclasses in KmClass should refer to renamed classes.
+ assertNotEquals(sealedSubclassDescriptor, sealedSubclass.getFinalDescriptor());
+ });
+
+ ClassSubject libKt = inspector.clazz(libClassName);
+ assertThat(expr, isPresent());
+ assertThat(expr, not(isRenamed()));
+
+ KmPackageSubject kmPackage = libKt.getKmPackage();
+ assertThat(kmPackage, isPresent());
+
+ KmFunctionSubject eval = kmPackage.kmFunctionExtensionWithUniqueName("eval");
+ assertThat(eval, isPresent());
+ assertThat(eval, isExtensionFunction());
+ });
+
+ Path libJar = compileResult.writeToZip();
+
+ String appFolder = PKG_PREFIX + "/sealed_app";
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(appFolder, "valid"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), pkg + ".sealed_app.ValidKt")
+ .assertSuccessWithOutputLines("6");
+ }
+
+ @Test
+ public void testMetadataInSealedClass_invalid() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(sealedLibJar)
+ // Keep the Expr class
+ .addKeepRules("-keep class **.Expr")
+ // Keep the extension function
+ .addKeepRules("-keep class **.LibKt { <methods>; }")
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile();
+ String pkg = getClass().getPackage().getName();
+ final String exprClassName = pkg + ".sealed_lib.Expr";
+ final String numClassName = pkg + ".sealed_lib.Num";
+ final String libClassName = pkg + ".sealed_lib.LibKt";
+ compileResult.inspect(inspector -> {
+ // Without any specific keep rule and no instantiation point, it's not necessary to keep
+ // sub classes of Expr.
+ ClassSubject num = inspector.clazz(numClassName);
+ assertThat(num, not(isPresent()));
+
+ ClassSubject expr = inspector.clazz(exprClassName);
+ assertThat(expr, isPresent());
+ assertThat(expr, not(isRenamed()));
+
+ KmClassSubject kmClass = expr.getKmClass();
+ assertThat(kmClass, isPresent());
+
+ // TODO(b/70169921): Then, we can cleanup sealedSubclasses in KmClass
+ assertFalse(kmClass.getSealedSubclassDescriptors().isEmpty());
+
+ ClassSubject libKt = inspector.clazz(libClassName);
+ assertThat(expr, isPresent());
+ assertThat(expr, not(isRenamed()));
+
+ KmPackageSubject kmPackage = libKt.getKmPackage();
+ assertThat(kmPackage, isPresent());
+
+ KmFunctionSubject eval = kmPackage.kmFunctionExtensionWithUniqueName("eval");
+ assertThat(eval, isPresent());
+ assertThat(eval, isExtensionFunction());
+ });
+
+ Path libJar = compileResult.writeToZip();
+
+ String appFolder = PKG_PREFIX + "/sealed_app";
+ ProcessResult kotlinTestCompileResult =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(appFolder, "invalid"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compileRaw();
+
+ assertNotEquals(0, kotlinTestCompileResult.exitCode);
+ assertThat(kotlinTestCompileResult.stderr, containsString("cannot access"));
+ assertThat(kotlinTestCompileResult.stderr, containsString("private in 'Expr'"));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/invalid.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/invalid.kt
new file mode 100644
index 0000000..88f401c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/invalid.kt
@@ -0,0 +1,14 @@
+// Copyright (c) 2020, 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.sealed_app
+
+import com.android.tools.r8.kotlin.metadata.sealed_lib.Expr
+import com.android.tools.r8.kotlin.metadata.sealed_lib.eval
+
+// A sealed class can only be extended within that file.
+class MyExpr : Expr()
+
+fun main() {
+ println(MyExpr().eval())
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/valid.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/valid.kt
new file mode 100644
index 0000000..379c17e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/valid.kt
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, 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.sealed_app
+
+import com.android.tools.r8.kotlin.metadata.sealed_lib.eval
+import com.android.tools.r8.kotlin.metadata.sealed_lib.ExprFactory
+
+fun main() {
+ println(ExprFactory.createSum(ExprFactory.createNum(4), ExprFactory.createNum(2)).eval())
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_lib/lib.kt
new file mode 100644
index 0000000..ea64d13
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_lib/lib.kt
@@ -0,0 +1,26 @@
+// Copyright (c) 2020, 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.sealed_lib
+
+sealed class Expr
+
+sealed class BinOp(val e1: Expr, val e2: Expr, val op: String) : Expr() {
+ override fun toString() = "$e1 $op $e2"
+}
+
+data class Num(val num: Int) : Expr()
+
+data class Sum(val left: Expr, val right: Expr) : BinOp(left, right, "+")
+
+object ExprFactory {
+ fun createNum(i: Int): Expr = Num(i)
+ fun createSum(e1: Expr, e2: Expr): Expr = Sum(e1, e2)
+}
+
+fun Expr.eval(): Int =
+ when(this) {
+ is Num -> num
+ is Sum -> e1.eval() + e2.eval()
+ else -> throw IllegalArgumentException("Unknown Expr: $this")
+ }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
index 6a2f504..1a640ed 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
@@ -97,4 +97,14 @@
public List<ClassSubject> getSuperTypes() {
return null;
}
+
+ @Override
+ public List<String> getSealedSubclassDescriptors() {
+ return null;
+ }
+
+ @Override
+ public List<ClassSubject> getSealedSubclasses() {
+ return null;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
index e60f9f3..6c65c2d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils.codeinspector;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.utils.DescriptorUtils;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -69,4 +70,19 @@
.filter(ClassSubject::isPresent)
.collect(Collectors.toList());
}
+
+ @Override
+ public List<String> getSealedSubclassDescriptors() {
+ return kmClass.getSealedSubclasses().stream()
+ .map(DescriptorUtils::getDescriptorFromKotlinClassifier)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<ClassSubject> getSealedSubclasses() {
+ return kmClass.getSealedSubclasses().stream()
+ .map(DescriptorUtils::getDescriptorFromKotlinClassifier)
+ .map(this::getClassSubjectFromDescriptor)
+ .collect(Collectors.toList());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
index 34bc771..9fece50 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
@@ -140,12 +140,16 @@
.collect(Collectors.toList());
}
+ default ClassSubject getClassSubjectFromDescriptor(String descriptor) {
+ return codeInspector().clazz(Reference.classFromDescriptor(descriptor));
+ }
+
default ClassSubject getClassSubjectFromKmType(KmType kmType) {
String descriptor = getDescriptorFromKmType(kmType);
if (descriptor == null) {
return new AbsentClassSubject();
}
- return codeInspector().clazz(Reference.classFromDescriptor(descriptor));
+ return getClassSubjectFromDescriptor(descriptor);
}
@Override
@@ -167,7 +171,7 @@
}
// TODO(b/145824437): This is a dup of KotlinMetadataJvmExtensionUtils$KmPropertyProcessor
- static class KmPropertyProcessor {
+ class KmPropertyProcessor {
JvmFieldSignature fieldSignature = null;
// Custom getter via @get:JvmName("..."). Otherwise, null.
JvmMethodSignature getterSignature = null;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
index d36f233..97b233d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
@@ -12,4 +12,8 @@
public abstract List<String> getSuperTypeDescriptors();
public abstract List<ClassSubject> getSuperTypes();
+
+ public abstract List<String> getSealedSubclassDescriptors();
+
+ public abstract List<ClassSubject> getSealedSubclasses();
}