Merge "Add test for superfluous monitor instructions"
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 24def66..fefe8c5 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -2039,6 +2039,24 @@
private void computeLiveRanges() {
computeLiveRanges(options, code, liveAtEntrySets, liveIntervals);
+ // Art VMs before Android M assume that the register for the receiver never changes its value.
+ // This assumption is used during verification. Allowing the receiver register to be
+ // overwritten can therefore lead to verification errors. If we could be targeting one of these
+ // VMs we block the receiver register throughout the method.
+ if (options.canHaveThisTypeVerifierBug() && !code.method.accessFlags.isStatic()) {
+ for (Instruction instruction : code.blocks.get(0).getInstructions()) {
+ if (instruction.isArgument() && instruction.outValue().isThis()) {
+ Value thisValue = instruction.outValue();
+ LiveIntervals thisIntervals = thisValue.getLiveIntervals();
+ thisIntervals.getRanges().clear();
+ thisIntervals.addRange(new LiveRange(0, code.getNextInstructionNumber()));
+ for (Set<Value> values : liveAtEntrySets.values()) {
+ values.add(thisValue);
+ }
+ return;
+ }
+ }
+ }
}
/**
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/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index a2896a7..1c0face 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -491,4 +491,11 @@
public boolean canUseNotInstruction() {
return minApiLevel >= AndroidApiLevel.L.getLevel();
}
+
+ // Art before M has a verifier bug where the type of the contents of the receiver register is
+ // assumed to not change. If the receiver register is reused for something else the verifier
+ // will fail and the code will not run.
+ public boolean canHaveThisTypeVerifierBug() {
+ return minApiLevel < AndroidApiLevel.M.getLevel();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index a1c1508..fbb4d20 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;
@@ -482,26 +484,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