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