blob: 0585af6a5a97e4f6e1920339dc3cd6e50706284e [file] [log] [blame]
// 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.ToolHelper.getKotlinAnnotationJar;
import static com.android.tools.r8.ToolHelper.getKotlinReflectJar;
import static com.android.tools.r8.ToolHelper.getKotlinStdlibJar;
import static com.android.tools.r8.utils.codeinspector.Matchers.isDexClass;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.kotlin.Kotlin.ClassClassifiers;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.KmClassifierSubject;
import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
import com.android.tools.r8.utils.codeinspector.KmTypeAliasSubject;
import com.android.tools.r8.utils.codeinspector.KmTypeProjectionSubject;
import com.android.tools.r8.utils.codeinspector.KmTypeSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.nio.file.Path;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class MetadataRewriteInTypeAliasTest extends KotlinMetadataTestBase {
private static final String EXPECTED =
StringUtils.lines(
"Impl::foo",
"Program::foo",
"true",
"42",
"42",
"42",
"42",
"42",
"42",
"42",
"true",
"42",
"1",
"ClassWithCompanion::fooOnCompanion",
"42",
"42",
"1",
"Hello World!",
"class com.android.tools.r8.kotlin.metadata.typealias_lib.Super");
private final TestParameters parameters;
@Parameterized.Parameters(name = "{0}, {1}")
public static Collection<Object[]> data() {
return buildParameters(
getTestParameters().withCfRuntimes().build(),
getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
}
public MetadataRewriteInTypeAliasTest(
TestParameters parameters, KotlinTestParameters kotlinParameters) {
super(kotlinParameters);
this.parameters = parameters;
}
private static final KotlinCompileMemoizer typeAliasLibJarMap =
getCompileMemoizer(
getKotlinFileInTest(PKG_PREFIX + "/typealias_lib", "lib"),
getKotlinFileInTest(PKG_PREFIX + "/typealias_lib", "lib_ext"));
@Test
public void smokeTest() throws Exception {
Path libJar = typeAliasLibJarMap.getForConfiguration(kotlinc, targetVersion);
Path output =
kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
.addClasspathFiles(libJar)
.addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/typealias_app", "main"))
.setOutputPath(temp.newFolder().toPath())
.compile();
testForJvm()
.addRunClasspathFiles(getKotlinStdlibJar(kotlinc), getKotlinReflectJar(kotlinc), libJar)
.addClasspath(output)
.run(parameters.getRuntime(), PKG + ".typealias_app.MainKt")
.assertSuccessWithOutput(EXPECTED);
}
@Test
public void testMetadataInTypeAlias_renamed() throws Exception {
String superTypeName = "com.android.tools.r8.kotlin.metadata.typealias_lib.Super";
String renamedSuperTypeName = "com.android.tools.r8.kotlin.metadata.typealias_lib.FooBar";
Path libJar =
testForR8(parameters.getBackend())
.addClasspathFiles(
getKotlinStdlibJar(kotlinc),
getKotlinReflectJar(kotlinc),
getKotlinAnnotationJar(kotlinc))
.addProgramFiles(typeAliasLibJarMap.getForConfiguration(kotlinc, targetVersion))
// Keep non-private members of Impl
.addKeepRules("-keep class **.Impl { !private *; }")
// Keep but allow obfuscation of types.
.addKeepRules("-keep,allowobfuscation class " + PKG + ".typealias_lib.** { *; }")
.addKeepRules("-keepclassmembernames class " + PKG + ".typealias_lib.**" + " { *; }")
// Keep the Companion class for ClassWithCompanionC.
.addKeepRules("-keep class **.ClassWithCompanion$Companion { *; }")
// Keep the inner class, otherwise it cannot be constructed.
.addKeepRules("-keep class **.*Inner { *; }")
// Keep LibKt that contains the type-aliases and utils.
.addKeepRules("-keep class **.LibKt, **.Lib_extKt { *; }")
// Keep the library test methods
.addKeepRules("-keep class " + PKG + ".typealias_lib.*Tester { *; }")
.addKeepRules("-keep class " + PKG + ".typealias_lib.*Tester$Companion { *; }")
.addKeepRules("-keep class " + PKG + ".typealias_lib.SubTypeOfAlias { *; }")
.addApplyMapping(superTypeName + " -> " + renamedSuperTypeName + ":")
.addKeepAttributes(
ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
ProguardKeepAttributes.SIGNATURE,
ProguardKeepAttributes.INNER_CLASSES,
ProguardKeepAttributes.ENCLOSING_METHOD)
.compile()
.inspect(this::inspect)
.writeToZip();
Path appJar =
kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
.addClasspathFiles(libJar)
.addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/typealias_app", "main"))
.compile();
testForJvm()
.addRunClasspathFiles(getKotlinStdlibJar(kotlinc), getKotlinReflectJar(kotlinc), libJar)
.addClasspath(appJar)
.run(parameters.getRuntime(), PKG + ".typealias_app.MainKt")
.assertSuccessWithOutput(EXPECTED.replace(superTypeName, renamedSuperTypeName));
}
private void inspect(CodeInspector inspector) {
inspectLib(inspector);
inspectLibExt(inspector);
}
private void inspectLib(CodeInspector inspector) {
String packageName = PKG + ".typealias_lib";
String itfClassName = packageName + ".Itf";
String libKtClassName = packageName + ".LibKt";
ClassSubject itf = inspector.clazz(itfClassName);
assertThat(itf, isPresentAndRenamed());
ClassSubject libKt = inspector.clazz(libKtClassName);
assertThat(libKt, isPresentAndNotRenamed());
MethodSubject seq = libKt.uniqueMethodWithName("seq");
assertThat(seq, isPresentAndNotRenamed());
// API entry is kept, hence the presence of Metadata.
KmPackageSubject kmPackage = libKt.getKmPackage();
assertThat(kmPackage, isPresent());
String arrayDescriptor =
DescriptorUtils.getDescriptorFromKotlinClassifier(ClassClassifiers.arrayBinaryName);
// Check that typealias myAliasedArray<T> = Array<T> exists.
KmTypeAliasSubject myAliasedArray = kmPackage.kmTypeAliasWithUniqueName("myAliasedArray");
assertThat(myAliasedArray, isPresent());
assertEquals(arrayDescriptor, myAliasedArray.expandedType().descriptor());
// Check that typealias API = Itf has been rewritten correctly.
KmTypeAliasSubject api = kmPackage.kmTypeAliasWithUniqueName("API");
assertThat(api, isPresent());
assertThat(api.expandedType(), isDexClass(itf.getDexProgramClass()));
assertThat(api.underlyingType(), isDexClass(itf.getDexProgramClass()));
// Check that the type-alias APIs exist and that the expanded type is renamed.
KmTypeAliasSubject apIs = kmPackage.kmTypeAliasWithUniqueName("APIs");
assertThat(apIs, isPresent());
assertEquals(arrayDescriptor, apIs.expandedType().descriptor());
assertEquals(1, apIs.expandedType().typeArguments().size());
KmTypeProjectionSubject expandedArgument = apIs.expandedType().typeArguments().get(0);
assertThat(expandedArgument.type(), isDexClass(itf.getDexProgramClass()));
assertEquals(myAliasedArray.descriptor(packageName), apIs.underlyingType().descriptor());
assertEquals(1, apIs.underlyingType().typeArguments().size());
KmTypeProjectionSubject underlyingArgument = apIs.underlyingType().typeArguments().get(0);
KmTypeSubject type = underlyingArgument.type();
assertNotNull(type);
assertTrue(type.classifier().isTypeAlias());
assertEquals(api.descriptor(packageName), type.descriptor());
}
private void inspectLibExt(CodeInspector inspector) {
String packageName = PKG + ".typealias_lib";
String libKtClassName = packageName + ".Lib_extKt";
// Check that Arr has been renamed.
ClassSubject arr = inspector.clazz(packageName + ".Arr");
assertThat(arr, isPresentAndRenamed());
ClassSubject libKt = inspector.clazz(libKtClassName);
KmPackageSubject kmPackage = libKt.getKmPackage();
// typealias Arr1D<K> = Arr<K>
KmTypeAliasSubject arr1D = kmPackage.kmTypeAliasWithUniqueName("Arr1D");
assertThat(arr1D, isPresent());
assertThat(arr1D.expandedType(), isDexClass(arr.getDexProgramClass()));
// typealias Arr2D<K> = Arr1D<Arr1D<K>>
KmTypeAliasSubject arr2D = kmPackage.kmTypeAliasWithUniqueName("Arr2D");
assertThat(arr2D, isPresent());
assertThat(arr2D.expandedType(), isDexClass(arr.getDexProgramClass()));
assertEquals(1, arr2D.expandedType().typeArguments().size());
KmTypeProjectionSubject arr2DexpandedArg = arr2D.expandedType().typeArguments().get(0);
assertThat(arr2DexpandedArg.type(), isDexClass(arr.getDexProgramClass()));
assertEquals(arr1D.descriptor(packageName), arr2D.underlyingType().descriptor());
assertEquals(1, arr2D.underlyingType().typeArguments().size());
KmTypeProjectionSubject arr2DunderlyingArg = arr2D.underlyingType().typeArguments().get(0);
assertEquals(arr1D.descriptor(packageName), arr2DunderlyingArg.type().descriptor());
// typealias IntSet = Set<Int>
// typealias MyMapToSetOfInt<K> = MutableMap<K, IntSet>
KmTypeAliasSubject intSet = kmPackage.kmTypeAliasWithUniqueName("IntSet");
assertThat(intSet, isPresent());
KmTypeAliasSubject myMapToSetOfInt = kmPackage.kmTypeAliasWithUniqueName("MyMapToSetOfInt");
assertThat(myMapToSetOfInt, isPresent());
assertEquals(2, myMapToSetOfInt.underlyingType().typeArguments().size());
assertEquals(2, myMapToSetOfInt.expandedType().typeArguments().size());
assertEquals(1, myMapToSetOfInt.typeParameters().size());
KmClassifierSubject typeClassifier =
myMapToSetOfInt.underlyingType().typeArguments().get(0).type().classifier();
assertTrue(typeClassifier.isTypeParameter());
// Check that the type-variable K in 'MyMapToSetOfInt<K>' is the first argument in
// MutableMap<K, IntSet>.
assertEquals(
myMapToSetOfInt.typeParameters().get(0).getId(), typeClassifier.asTypeParameter().getId());
KmTypeSubject underlyingType = myMapToSetOfInt.underlyingType().typeArguments().get(1).type();
assertEquals(intSet.descriptor(packageName), underlyingType.descriptor());
KmTypeSubject expandedType = myMapToSetOfInt.expandedType().typeArguments().get(1).type();
assertTrue(intSet.expandedType().equalUpToAbbreviatedType(expandedType));
// Check that the following exist:
// typealias MyHandler = (Int, Any) -> Unit
// typealias MyGenericPredicate<T> = (T) -> Boolean
assertThat(kmPackage.kmTypeAliasWithUniqueName("MyHandler"), isPresent());
KmTypeAliasSubject genericPredicate = kmPackage.kmTypeAliasWithUniqueName("MyGenericPredicate");
assertThat(genericPredicate, isPresent());
// Check that the type-variable T in 'MyGenericPredicate<T>' is the input argument in
// (T) -> Boolean.
assertEquals(1, genericPredicate.typeParameters().size());
assertEquals(2, genericPredicate.expandedType().typeArguments().size());
KmTypeProjectionSubject kmTypeGenericArgumentSubject =
genericPredicate.expandedType().typeArguments().get(0);
assertTrue(kmTypeGenericArgumentSubject.type().classifier().isTypeParameter());
assertEquals(
genericPredicate.typeParameters().get(0).getId(),
kmTypeGenericArgumentSubject.type().classifier().asTypeParameter().getId());
// typealias ClassWithCompanionC = ClassWithCompanion.Companion
KmTypeAliasSubject classWithCompanionC =
kmPackage.kmTypeAliasWithUniqueName("ClassWithCompanionC");
assertThat(classWithCompanionC, isPresent());
ClassSubject companionClazz = inspector.clazz(packageName + ".ClassWithCompanion$Companion");
assertThat(classWithCompanionC.expandedType(), isDexClass(companionClazz.getDexProgramClass()));
}
}