Add tests for Kotlin properties
Tests that property getter and setter methods are removed when not
used. Also test inlining of methods expected to be inlined when
access relaxation is enabled.
Bug: 70158152
Change-Id: I34bea16952177d3083a895e4ffcb92e9ac35e05f
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index cccbb4b..b79e8e6 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -234,6 +234,17 @@
}
/**
+ * Convert class name to a binary name.
+ *
+ * @param className a package name i.e., "java.lang.Object"
+ * @return java class name in a binary name format, i.e., java/lang/Object
+ */
+ public static String getBinaryNameFromJavaType(String className) {
+ return className.replace(JAVA_PACKAGE_SEPARATOR, DESCRIPTOR_PACKAGE_SEPARATOR);
+ }
+
+
+ /**
* Convert a class binary name to a descriptor.
*
* @param typeBinaryName class binary name i.e. "java/lang/Object"
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 77f9ad9..8a7c128 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.SmaliWriter;
+import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -25,6 +26,7 @@
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.PreloadedClassFileProvider;
+import com.android.tools.r8.utils.ZipUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
@@ -481,26 +483,26 @@
}
protected ProcessResult runOnJava(String main, byte[]... classes) throws IOException {
- Path file = writeToZip(classes);
+ Path file = writeToZip(Arrays.asList(classes));
return ToolHelper.runJavaNoVerify(file, main);
}
- private Path writeToZip(byte[]... classes) throws IOException {
- File result = temp.newFile();
+ private Path writeToZip(List<byte[]> classes) throws IOException {
+ File result = temp.newFile("tmp.zip");
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(result.toPath(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
for (byte[] clazz : classes) {
String name = loadClassFromDump(clazz).getTypeName();
- ZipEntry zipEntry = new ZipEntry(DescriptorUtils.getPathFromJavaType(name));
- zipEntry.setSize(clazz.length);
- out.putNextEntry(zipEntry);
- out.write(clazz);
- out.closeEntry();
+ ZipUtils.writeToZipStream(out, DescriptorUtils.getPathFromJavaType(name), clazz);
}
}
return result.toPath();
}
+ protected Path writeToZip(JasminBuilder jasminBuilder) throws Exception {
+ return writeToZip(jasminBuilder.buildClasses());
+ }
+
protected static Class loadClassFromDump(byte[] dump) {
return new DumpLoader().loadClass(dump);
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index 9f520a2..b3820dd 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -20,14 +20,16 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.FieldSubject;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.FileUtils;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableList.Builder;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.Map;
import org.junit.Assume;
import org.junit.runner.RunWith;
@@ -40,7 +42,7 @@
@Parameters(name = "{0}_{1}")
public static Collection<Object[]> data() {
- ImmutableList.Builder<Object[]> builder = new Builder<>();
+ ImmutableList.Builder<Object[]> builder = new ImmutableList.Builder<>();
for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
builder.add(new Object[]{Boolean.TRUE, targetVersion});
builder.add(new Object[]{Boolean.FALSE, targetVersion});
@@ -51,6 +53,12 @@
@Parameter(0) public boolean allowAccessModification;
@Parameter(1) public KotlinTargetVersion targetVersion;
+ private final List<Path> extraClasspath = new ArrayList<>();
+
+ protected void addExtraClasspath(Path path) {
+ extraClasspath.add(path);
+ }
+
protected static void checkMethodIsInvokedAtLeastOnce(DexCode dexCode,
MethodSignature... methodSignatures) {
for (MethodSignature methodSignature : methodSignatures) {
@@ -101,14 +109,29 @@
return classSubject;
}
+ protected static FieldSubject checkFieldIsPresent(ClassSubject classSubject, String fieldType,
+ String fieldName) {
+ FieldSubject fieldSubject = classSubject.field(fieldType, fieldName);
+ assertNotNull(fieldSubject);
+ assertTrue(fieldSubject.isPresent());
+ return fieldSubject;
+ }
+
+ protected static void checkFieldIsAbsent(ClassSubject classSubject, String fieldType,
+ String fieldName) {
+ FieldSubject fieldSubject = classSubject.field(fieldType, fieldName);
+ assertNotNull(fieldSubject);
+ assertFalse(fieldSubject.isPresent());
+ }
+
protected static MethodSubject checkMethodIsPresent(ClassSubject classSubject,
MethodSignature methodSignature) {
return checkMethod(classSubject, methodSignature, true);
}
- protected static MethodSubject checkMethodIsAbsent(ClassSubject classSubject,
+ protected static void checkMethodIsAbsent(ClassSubject classSubject,
MethodSignature methodSignature) {
- return checkMethod(classSubject, methodSignature, false);
+ checkMethod(classSubject, methodSignature, false);
}
protected static MethodSubject checkMethod(ClassSubject classSubject,
@@ -162,9 +185,14 @@
proguardRules += extraProguardRules;
}
+ // Build classpath for compilation (and java execution)
+ List<Path> classpath = new ArrayList<>(extraClasspath.size() + 1);
+ classpath.add(jarFile);
+ classpath.addAll(extraClasspath);
+
// Build with R8
AndroidApp.Builder builder = AndroidApp.builder();
- builder.addProgramFiles(jarFile);
+ builder.addProgramFiles(classpath);
AndroidApp app = compileWithR8(builder.build(), proguardRules.toString());
// Materialize file for execution.
@@ -176,7 +204,7 @@
ToolHelper.runArtNoVerificationErrors(generatedDexFile.toString(), mainClass);
// Compare with Java.
- ToolHelper.ProcessResult javaResult = ToolHelper.runJava(jarFile, mainClass);
+ ToolHelper.ProcessResult javaResult = ToolHelper.runJava(classpath, mainClass);
if (javaResult.exitCode != 0) {
System.out.println(javaResult.stdout);
System.err.println(javaResult.stderr);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java
index 87ffcef..56a727e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.naming.MemberNaming;
import com.google.common.collect.Maps;
-
import java.util.Collections;
import java.util.Map;
@@ -16,15 +15,34 @@
* <p>See https://kotlinlang.org/docs/reference/classes.html</p>
*/
class KotlinClass {
+
+ /**
+ * This is the suffix appended by Kotlin compiler to getter and setter method names of
+ * internal properties.
+ *
+ * It must match the string passed in command-line option "-module-name" of Kotlin compiler. The
+ * default value is "main".
+ */
+ private static final String KOTLIN_MODULE_NAME = "main";
+
+ enum Visibility {
+ PUBLIC,
+ INTERNAL,
+ PROTECTED,
+ PRIVATE;
+ }
+
protected static class KotlinProperty {
private final String name;
private final String type;
+ private final Visibility visibility;
private final int index;
- private KotlinProperty(String name, String type, int index) {
+ private KotlinProperty(String name, String type, Visibility visibility, int index) {
this.name = name;
this.type = type;
this.index = index;
+ this.visibility = visibility;
}
public String getName() {
@@ -35,6 +53,10 @@
return type;
}
+ public Visibility getVisibility() {
+ return visibility;
+ }
+
public int getIndex() {
return index;
}
@@ -50,9 +72,9 @@
return className;
}
- public KotlinClass addProperty(String name, String type) {
+ public KotlinClass addProperty(String name, String type, Visibility visibility) {
assert !properties.containsKey(name);
- properties.put(name, new KotlinProperty(name, type, properties.size()));
+ properties.put(name, new KotlinProperty(name, type, visibility, properties.size()));
return this;
}
@@ -61,17 +83,37 @@
return properties.get(name);
}
- public MemberNaming.MethodSignature getGetterForProperty(String name) {
- String type = getProperty(name).type;
+ public MemberNaming.MethodSignature getGetterForProperty(String propertyName) {
+ KotlinProperty property = getProperty(propertyName);
+ String type = property.type;
String getterName;
- if (name.length() > 2 && name.startsWith("is")
- && (name.charAt(2) == '_' || Character.isUpperCase(name.charAt(2)))) {
+ if (propertyName.length() > 2 && propertyName.startsWith("is")
+ && (propertyName.charAt(2) == '_' || Character.isUpperCase(propertyName.charAt(2)))) {
// Getter for property "isAbc" is "isAbc".
- getterName = name;
+ getterName = propertyName;
} else {
// Getter for property "abc" is "getAbc".
- getterName = "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
+ getterName =
+ "get" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
+ }
+ if (property.getVisibility() == Visibility.INTERNAL) {
+ // Append module name
+ getterName += "$" + KOTLIN_MODULE_NAME;
}
return new MemberNaming.MethodSignature(getterName, type, Collections.emptyList());
}
+
+ public MemberNaming.MethodSignature getSetterForProperty(String propertyName) {
+ KotlinProperty property = getProperty(propertyName);
+ String setterName = "set"
+ + Character.toUpperCase(property.name.charAt(0))
+ + property.name.substring(1);
+ if (property.getVisibility() == Visibility.INTERNAL) {
+ // Append module name
+ setterName += "$" + KOTLIN_MODULE_NAME;
+ }
+ return new MemberNaming.MethodSignature(setterName, "void",
+ Collections.singleton(property.getType()));
+ }
+
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinDataClass.java b/src/test/java/com/android/tools/r8/kotlin/KotlinDataClass.java
index f8861cd..bb24adc 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinDataClass.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinDataClass.java
@@ -23,8 +23,8 @@
}
@Override
- public KotlinDataClass addProperty(String name, String type) {
- return (KotlinDataClass) super.addProperty(name, type);
+ public KotlinDataClass addProperty(String name, String type, Visibility visibility) {
+ return (KotlinDataClass) super.addProperty(name, type, visibility);
}
public MemberNaming.MethodSignature getComponentNFunctionForProperty(String name) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index 379579f..daf05f5 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.kotlin;
import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.kotlin.KotlinClass.Visibility;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
@@ -15,8 +16,8 @@
public class R8KotlinDataClassTest extends AbstractR8KotlinTestBase {
private static final KotlinDataClass TEST_DATA_CLASS = new KotlinDataClass("dataclass.Person")
- .addProperty("name", "java.lang.String")
- .addProperty("age", "int");
+ .addProperty("name", "java.lang.String", Visibility.PUBLIC)
+ .addProperty("age", "int", Visibility.PUBLIC);
private static final MethodSignature NAME_GETTER_METHOD =
TEST_DATA_CLASS.getGetterForProperty("name");
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
new file mode 100644
index 0000000..c9e1298
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -0,0 +1,329 @@
+// Copyright (c) 2018, 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 static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.kotlin.KotlinClass.Visibility;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import java.nio.file.Path;
+import org.junit.Test;
+
+public class R8KotlinPropertiesTest extends AbstractR8KotlinTestBase {
+
+ private static final String PACKAGE_NAME = "properties";
+
+ private static final String JAVA_LANG_STRING = "java.lang.String";
+
+ // This is the name of the Jasmin-generated class which contains the "main" method which will
+ // invoke the tested method.
+ private static final String JASMIN_MAIN_CLASS = "properties.TestMain";
+
+ private static final KotlinClass MUTABLE_PROPERTY_CLASS =
+ new KotlinClass("properties.MutableProperty")
+ .addProperty("privateProp", JAVA_LANG_STRING, Visibility.PRIVATE)
+ .addProperty("protectedProp", JAVA_LANG_STRING, Visibility.PROTECTED)
+ .addProperty("internalProp", JAVA_LANG_STRING, Visibility.INTERNAL)
+ .addProperty("publicProp", JAVA_LANG_STRING, Visibility.PUBLIC)
+ .addProperty("primitiveProp", "int", Visibility.PUBLIC);
+
+ private static final KotlinClass USER_DEFINED_PROPERTY_CLASS =
+ new KotlinClass("properties.UserDefinedProperty")
+ .addProperty("durationInMilliSeconds", "int", Visibility.PUBLIC)
+ .addProperty("durationInSeconds", "int", Visibility.PUBLIC);
+
+ private static final KotlinClass LATE_INIT_PROPERTY_CLASS =
+ new KotlinClass("properties.LateInitProperty")
+ .addProperty("privateLateInitProp", JAVA_LANG_STRING, Visibility.PRIVATE)
+ .addProperty("protectedLateInitProp", JAVA_LANG_STRING, Visibility.PROTECTED)
+ .addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
+ .addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
+
+ @Test
+ public void testMutableProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
+ addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_noUseOfProperties");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ MUTABLE_PROPERTY_CLASS.getClassName());
+ for (String propertyName : MUTABLE_PROPERTY_CLASS.properties.keySet()) {
+ checkMethodIsAbsent(classSubject,
+ MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName));
+ checkMethodIsAbsent(classSubject,
+ MUTABLE_PROPERTY_CLASS.getSetterForProperty(propertyName));
+ }
+ });
+ }
+
+ @Test
+ public void testMutableProperty_privateIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_usePrivateProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ MUTABLE_PROPERTY_CLASS.getClassName());
+ String propertyName = "privateProp";
+ FieldSubject fieldSubject = checkFieldIsPresent(classSubject, JAVA_LANG_STRING, propertyName);
+ if (!allowAccessModification) {
+ assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ } else {
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
+ }
+
+ // Private property has no getter or setter.
+ checkMethodIsAbsent(classSubject, MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName));
+ checkMethodIsAbsent(classSubject, MUTABLE_PROPERTY_CLASS.getSetterForProperty(propertyName));
+ });
+ }
+
+ @Test
+ public void testMutableProperty_protectedIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_useProtectedProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ MUTABLE_PROPERTY_CLASS.getClassName());
+ String propertyName = "protectedProp";
+ FieldSubject fieldSubject = checkFieldIsPresent(classSubject, JAVA_LANG_STRING, propertyName);
+
+ // Protected property has private field.
+ MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName);
+ if (allowAccessModification) {
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
+ checkMethodIsAbsent(classSubject, getter);
+ } else {
+ assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ checkMethodIsPresent(classSubject, getter);
+ }
+ });
+ }
+
+ @Test
+ public void testMutableProperty_internalIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_useInternalProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ MUTABLE_PROPERTY_CLASS.getClassName());
+ String propertyName = "internalProp";
+ FieldSubject fieldSubject = checkFieldIsPresent(classSubject, JAVA_LANG_STRING, propertyName);
+
+ // Internal property has private field
+ MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName);
+ if (allowAccessModification) {
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
+ checkMethodIsAbsent(classSubject, getter);
+ } else {
+ assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ checkMethodIsPresent(classSubject, getter);
+ }
+ });
+ }
+
+ @Test
+ public void testMutableProperty_publicIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_usePublicProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ MUTABLE_PROPERTY_CLASS.getClassName());
+ String propertyName = "publicProp";
+ FieldSubject fieldSubject = checkFieldIsPresent(classSubject, JAVA_LANG_STRING, propertyName);
+
+ // Public property has private field
+ MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName);
+ if (allowAccessModification) {
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
+ checkMethodIsAbsent(classSubject, getter);
+ } else {
+ assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ checkMethodIsPresent(classSubject, getter);
+ }
+ });
+ }
+
+ @Test
+ public void testMutableProperty_primitivePropertyIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_usePrimitiveProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ MUTABLE_PROPERTY_CLASS.getClassName());
+ String propertyName = "primitiveProp";
+ FieldSubject fieldSubject = checkFieldIsPresent(classSubject, "int", propertyName);
+
+ MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName);
+ MethodSignature setter = MUTABLE_PROPERTY_CLASS.getSetterForProperty(propertyName);
+ if (allowAccessModification) {
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
+ checkMethodIsAbsent(classSubject, getter);
+ checkMethodIsAbsent(classSubject, setter);
+ } else {
+ assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ checkMethodIsPresent(classSubject, getter);
+ checkMethodIsPresent(classSubject, setter);
+ }
+ });
+ }
+
+ @Test
+ public void testLateInitProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
+ addMainToClasspath("properties/LateInitPropertyKt", "lateInitProperty_noUseOfProperties");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ LATE_INIT_PROPERTY_CLASS.getClassName());
+ for (String propertyName : LATE_INIT_PROPERTY_CLASS.properties.keySet()) {
+ checkMethodIsAbsent(classSubject,
+ LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName));
+ checkMethodIsAbsent(classSubject,
+ LATE_INIT_PROPERTY_CLASS.getSetterForProperty(propertyName));
+ }
+ });
+ }
+
+ @Test
+ public void testLateInitProperty_privateIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/LateInitPropertyKt", "lateInitProperty_usePrivateLateInitProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ LATE_INIT_PROPERTY_CLASS.getClassName());
+ String propertyName = "privateLateInitProp";
+ FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
+ assertTrue("Field is absent", fieldSubject.isPresent());
+ if (!allowAccessModification) {
+ assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ }
+
+ // Private late init property have no getter or setter.
+ checkMethodIsAbsent(classSubject,
+ LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName));
+ checkMethodIsAbsent(classSubject,
+ LATE_INIT_PROPERTY_CLASS.getSetterForProperty(propertyName));
+ });
+ }
+
+ @Test
+ public void testLateInitProperty_protectedIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/LateInitPropertyKt",
+ "lateInitProperty_useProtectedLateInitProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ LATE_INIT_PROPERTY_CLASS.getClassName());
+ String propertyName = "protectedLateInitProp";
+ FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
+ assertTrue("Field is absent", fieldSubject.isPresent());
+ if (!allowAccessModification) {
+ assertTrue(fieldSubject.getField().accessFlags.isProtected());
+ }
+
+ // Protected late init property have protected getter
+ checkMethodIsAbsent(classSubject,
+ LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName));
+ });
+ }
+
+ @Test
+ public void testLateInitProperty_internalIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/LateInitPropertyKt", "lateInitProperty_useInternalLateInitProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ LATE_INIT_PROPERTY_CLASS.getClassName());
+ String propertyName = "internalLateInitProp";
+ FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
+ assertTrue("Field is absent", fieldSubject.isPresent());
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
+
+ // Internal late init property have protected getter
+ checkMethodIsAbsent(classSubject,
+ LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName));
+ });
+ }
+
+ @Test
+ public void testLateInitProperty_publicIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/LateInitPropertyKt", "lateInitProperty_usePublicLateInitProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ LATE_INIT_PROPERTY_CLASS.getClassName());
+ String propertyName = "publicLateInitProp";
+ FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
+ assertTrue("Field is absent", fieldSubject.isPresent());
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
+
+ // Internal late init property have protected getter
+ checkMethodIsAbsent(classSubject,
+ LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName));
+ });
+ }
+
+ @Test
+ public void testUserDefinedProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
+ addMainToClasspath("properties/UserDefinedPropertyKt", "userDefinedProperty_noUseOfProperties");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ USER_DEFINED_PROPERTY_CLASS.getClassName());
+ for (String propertyName : USER_DEFINED_PROPERTY_CLASS.properties.keySet()) {
+ checkMethodIsAbsent(classSubject,
+ USER_DEFINED_PROPERTY_CLASS.getGetterForProperty(propertyName));
+ checkMethodIsAbsent(classSubject,
+ USER_DEFINED_PROPERTY_CLASS.getSetterForProperty(propertyName));
+ }
+ });
+ }
+
+ @Test
+ public void testUserDefinedProperty_publicIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/UserDefinedPropertyKt", "userDefinedProperty_useProperties");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ USER_DEFINED_PROPERTY_CLASS.getClassName());
+ String propertyName = "durationInSeconds";
+ // The 'wrapper' property is not assigned to a backing field, it only relies on the wrapped
+ // property.
+ checkFieldIsAbsent(classSubject, "int", "durationInSeconds");
+
+ FieldSubject fieldSubject = checkFieldIsPresent(classSubject, "int",
+ "durationInMilliSeconds");
+ MethodSignature getter = USER_DEFINED_PROPERTY_CLASS.getGetterForProperty(propertyName);
+ if (allowAccessModification) {
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
+ checkMethodIsAbsent(classSubject, getter);
+ } else {
+ assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ checkMethodIsPresent(classSubject, getter);
+ }
+ });
+ }
+
+ /**
+ * Generates a "main" class which invokes the given static method on the given klass. This new
+ * class is then added to the test classpath.
+ */
+ private void addMainToClasspath(String klass, String method) throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ ClassBuilder mainClassBuilder =
+ builder.addClass(DescriptorUtils.getBinaryNameFromJavaType(JASMIN_MAIN_CLASS));
+ mainClassBuilder.addMainMethod(
+ "invokestatic " + klass + "/" + method + "()V",
+ "return"
+ );
+
+ Path output = writeToZip(builder);
+ addExtraClasspath(output);
+ }
+}
diff --git a/src/test/kotlinR8TestResources/properties/LateInitProperty.kt b/src/test/kotlinR8TestResources/properties/LateInitProperty.kt
new file mode 100644
index 0000000..41c8ab2
--- /dev/null
+++ b/src/test/kotlinR8TestResources/properties/LateInitProperty.kt
@@ -0,0 +1,58 @@
+// Copyright (c) 2018, 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 properties
+
+open class LateInitProperty {
+ private lateinit var privateLateInitProp: String
+ protected lateinit var protectedLateInitProp: String
+ internal lateinit var internalLateInitProp: String
+ public lateinit var publicLateInitProp: String
+
+ fun callSetterPrivateLateInitProp(v: String) {
+ privateLateInitProp = v
+ }
+
+ fun callGetterPrivateLateInitProp(): String {
+ return privateLateInitProp
+ }
+}
+
+class SubLateInitProperty: LateInitProperty() {
+ fun callSetterProtectedLateInitProp(v: String) {
+ protectedLateInitProp = v
+ }
+
+ fun callGetterProtectedLateInitProp(): String {
+ return protectedLateInitProp
+ }
+}
+
+fun lateInitProperty_noUseOfProperties() {
+ LateInitProperty()
+ println("DONE")
+}
+
+fun lateInitProperty_usePrivateLateInitProp() {
+ val obj = LateInitProperty()
+ obj.callSetterPrivateLateInitProp("foo")
+ println(obj.callGetterPrivateLateInitProp())
+}
+
+fun lateInitProperty_useProtectedLateInitProp() {
+ val obj = SubLateInitProperty()
+ obj.callSetterProtectedLateInitProp("foo")
+ println(obj.callGetterProtectedLateInitProp())
+}
+
+fun lateInitProperty_useInternalLateInitProp() {
+ val obj = LateInitProperty()
+ obj.internalLateInitProp = "foo"
+ println(obj.internalLateInitProp)
+}
+
+fun lateInitProperty_usePublicLateInitProp() {
+ val obj = LateInitProperty()
+ obj.publicLateInitProp = "foo"
+ println(obj.publicLateInitProp)
+}
diff --git a/src/test/kotlinR8TestResources/properties/MutableProperty.kt b/src/test/kotlinR8TestResources/properties/MutableProperty.kt
new file mode 100644
index 0000000..ae945ff
--- /dev/null
+++ b/src/test/kotlinR8TestResources/properties/MutableProperty.kt
@@ -0,0 +1,66 @@
+// Copyright (c) 2018, 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 properties
+
+open class MutableProperty {
+ private var privateProp: String = "privateProp"
+ protected var protectedProp: String = "protectedProp"
+ internal var internalProp: String = "internalProp"
+ public var publicProp: String = "publicProp"
+
+ public var primitiveProp: Int = Int.MAX_VALUE
+
+ fun callSetterPrivateProp(v: String) {
+ privateProp = v
+ }
+
+ fun callGetterPrivateProp(): String {
+ return privateProp
+ }
+}
+
+class SubMutableProperty : MutableProperty() {
+ fun callSetterProtectedProp(v: String) {
+ protectedProp = v
+ }
+
+ fun callGetterProtectedProp(): String {
+ return protectedProp
+ }
+}
+
+fun mutableProperty_noUseOfProperties() {
+ MutableProperty()
+ println("DONE")
+}
+
+fun mutableProperty_usePrivateProp() {
+ val obj = MutableProperty()
+ obj.callSetterPrivateProp("foo")
+ println(obj.callGetterPrivateProp())
+}
+
+fun mutableProperty_useProtectedProp() {
+ val obj = SubMutableProperty()
+ obj.callSetterProtectedProp("foo")
+ println(obj.callGetterProtectedProp())
+}
+
+fun mutableProperty_useInternalProp() {
+ val obj = MutableProperty()
+ obj.internalProp = "foo"
+ println(obj.internalProp)
+}
+
+fun mutableProperty_usePublicProp() {
+ val obj = MutableProperty()
+ obj.publicProp = "foo"
+ println(obj.publicProp)
+}
+
+fun mutableProperty_usePrimitiveProp() {
+ val obj = MutableProperty()
+ obj.primitiveProp = Int.MIN_VALUE
+ println(obj.primitiveProp)
+}
diff --git a/src/test/kotlinR8TestResources/properties/UserDefinedProperty.kt b/src/test/kotlinR8TestResources/properties/UserDefinedProperty.kt
new file mode 100644
index 0000000..5bc55e6
--- /dev/null
+++ b/src/test/kotlinR8TestResources/properties/UserDefinedProperty.kt
@@ -0,0 +1,22 @@
+// Copyright (c) 2018, 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 properties
+
+class UserDefinedProperty() {
+ public var durationInMilliSeconds: Int = 0
+
+ var durationInSeconds: Int
+ get() = durationInMilliSeconds / 1000
+ set(v) { durationInMilliSeconds = v * 1000 }
+}
+
+fun userDefinedProperty_noUseOfProperties() {
+ UserDefinedProperty()
+}
+
+fun userDefinedProperty_useProperties() {
+ val obj = UserDefinedProperty()
+ obj.durationInSeconds = 5
+ println(obj.durationInSeconds)
+}
\ No newline at end of file