Add tests for Kotlin companion properties

Also move the generation of "main" class with jasmin to the super
class so that it is easier to add new tests.

Bug: 70158152
Change-Id: Ie7c4bbfd46b56044b0839c4a230fd9e2d160c5dd
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 b3820dd..85f2eb5 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -16,8 +16,11 @@
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
+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;
@@ -40,6 +43,10 @@
 @RunWith(Parameterized.class)
 public abstract class AbstractR8KotlinTestBase extends TestBase {
 
+  // 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 = "TestMain";
+
   @Parameters(name = "{0}_{1}")
   public static Collection<Object[]> data() {
     ImmutableList.Builder<Object[]> builder = new ImmutableList.Builder<>();
@@ -225,4 +232,26 @@
 
     void inspectApp(AndroidApp androidApp) throws Exception;
   }
+
+  /**
+   * Generates a "main" class which invokes the given static method (which has no argument and
+   * return void type). This new class is then added to the test classpath.
+   *
+   * @param methodClass the class of the static method to invoke
+   * @param methodName the name of the static method to invoke
+   * @return the name of the generated class
+   */
+  protected String addMainToClasspath(String methodClass, String methodName) throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    ClassBuilder mainClassBuilder =
+        builder.addClass(DescriptorUtils.getBinaryNameFromJavaType(JASMIN_MAIN_CLASS));
+    mainClassBuilder.addMainMethod(
+        "invokestatic " + methodClass + "/" + methodName + "()V",
+        "return"
+    );
+
+    Path output = writeToZip(builder);
+    addExtraClasspath(output);
+    return JASMIN_MAIN_CLASS;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinCompanionClass.java b/src/test/java/com/android/tools/r8/kotlin/KotlinCompanionClass.java
new file mode 100644
index 0000000..bf36dde
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinCompanionClass.java
@@ -0,0 +1,29 @@
+// 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;
+
+/**
+ * Represents the definition of a Kotlin companion class.
+ *
+ * <p>See https://kotlinlang.org/docs/reference/object-declarations.html#companion-objects</p>
+ */
+public class KotlinCompanionClass extends KotlinClass {
+
+  private final String outerClassName;
+
+  public KotlinCompanionClass(String outerClassName) {
+    super(outerClassName + "$Companion");
+    this.outerClassName = outerClassName;
+  }
+
+  @Override
+  public KotlinCompanionClass addProperty(String name, String type, Visibility visibility) {
+    return (KotlinCompanionClass) super.addProperty(name, type, visibility);
+  }
+
+  public String getOuterClassName() {
+    return outerClassName;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index c9e1298..499cc2f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -6,15 +6,12 @@
 
 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;
 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 {
@@ -23,10 +20,6 @@
 
   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)
@@ -47,10 +40,28 @@
           .addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
           .addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
 
+  private static final KotlinCompanionClass COMPANION_PROPERTY_CLASS =
+      new KotlinCompanionClass("properties.CompanionProperties")
+          .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)
+          .addProperty("privateLateInitProp", JAVA_LANG_STRING, Visibility.PRIVATE)
+          .addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
+          .addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
+
+  private static final KotlinCompanionClass COMPANION_LATE_INIT_PROPERTY_CLASS =
+      new KotlinCompanionClass("properties.CompanionLateInitProperties")
+          .addProperty("privateLateInitProp", JAVA_LANG_STRING, Visibility.PRIVATE)
+          .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) -> {
+    String mainClass = addMainToClasspath("properties/MutablePropertyKt",
+        "mutableProperty_noUseOfProperties");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -65,8 +76,9 @@
 
   @Test
   public void testMutableProperty_privateIsAlwaysInlined() throws Exception {
-    addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_usePrivateProp");
-    runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+    String mainClass = addMainToClasspath("properties/MutablePropertyKt",
+        "mutableProperty_usePrivateProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -86,8 +98,9 @@
 
   @Test
   public void testMutableProperty_protectedIsAlwaysInlined() throws Exception {
-    addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_useProtectedProp");
-    runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+    String mainClass = addMainToClasspath("properties/MutablePropertyKt",
+        "mutableProperty_useProtectedProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -108,8 +121,9 @@
 
   @Test
   public void testMutableProperty_internalIsAlwaysInlined() throws Exception {
-    addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_useInternalProp");
-    runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+    String mainClass = addMainToClasspath("properties/MutablePropertyKt",
+        "mutableProperty_useInternalProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -130,8 +144,9 @@
 
   @Test
   public void testMutableProperty_publicIsAlwaysInlined() throws Exception {
-    addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_usePublicProp");
-    runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+    String mainClass = addMainToClasspath("properties/MutablePropertyKt",
+        "mutableProperty_usePublicProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -152,8 +167,9 @@
 
   @Test
   public void testMutableProperty_primitivePropertyIsAlwaysInlined() throws Exception {
-    addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_usePrimitiveProp");
-    runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+    String mainClass = addMainToClasspath("properties/MutablePropertyKt",
+        "mutableProperty_usePrimitiveProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -176,8 +192,9 @@
 
   @Test
   public void testLateInitProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
-    addMainToClasspath("properties/LateInitPropertyKt", "lateInitProperty_noUseOfProperties");
-    runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+    String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
+        "lateInitProperty_noUseOfProperties");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -192,8 +209,9 @@
 
   @Test
   public void testLateInitProperty_privateIsAlwaysInlined() throws Exception {
-    addMainToClasspath("properties/LateInitPropertyKt", "lateInitProperty_usePrivateLateInitProp");
-    runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+    String mainClass = addMainToClasspath(
+        "properties/LateInitPropertyKt", "lateInitProperty_usePrivateLateInitProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -214,9 +232,9 @@
 
   @Test
   public void testLateInitProperty_protectedIsAlwaysInlined() throws Exception {
-    addMainToClasspath("properties/LateInitPropertyKt",
+    String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
         "lateInitProperty_useProtectedLateInitProp");
-    runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -235,8 +253,9 @@
 
   @Test
   public void testLateInitProperty_internalIsAlwaysInlined() throws Exception {
-    addMainToClasspath("properties/LateInitPropertyKt", "lateInitProperty_useInternalLateInitProp");
-    runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+    String mainClass = addMainToClasspath(
+        "properties/LateInitPropertyKt", "lateInitProperty_useInternalLateInitProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -253,8 +272,9 @@
 
   @Test
   public void testLateInitProperty_publicIsAlwaysInlined() throws Exception {
-    addMainToClasspath("properties/LateInitPropertyKt", "lateInitProperty_usePublicLateInitProp");
-    runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+    String mainClass = addMainToClasspath(
+        "properties/LateInitPropertyKt", "lateInitProperty_usePublicLateInitProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -271,8 +291,9 @@
 
   @Test
   public void testUserDefinedProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
-    addMainToClasspath("properties/UserDefinedPropertyKt", "userDefinedProperty_noUseOfProperties");
-    runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+    String mainClass = addMainToClasspath(
+        "properties/UserDefinedPropertyKt", "userDefinedProperty_noUseOfProperties");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           USER_DEFINED_PROPERTY_CLASS.getClassName());
@@ -287,8 +308,9 @@
 
   @Test
   public void testUserDefinedProperty_publicIsAlwaysInlined() throws Exception {
-    addMainToClasspath("properties/UserDefinedPropertyKt", "userDefinedProperty_useProperties");
-    runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+    String mainClass = addMainToClasspath(
+        "properties/UserDefinedPropertyKt", "userDefinedProperty_useProperties");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           USER_DEFINED_PROPERTY_CLASS.getClassName());
@@ -310,20 +332,209 @@
     });
   }
 
-  /**
-   * 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"
-    );
+  @Test
+  public void testCompanionProperty_primitivePropertyCannotBeInlined() throws Exception {
+    String mainClass = addMainToClasspath(
+        "properties.CompanionPropertiesKt", "companionProperties_usePrimitiveProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject outerClass = checkClassExists(dexInspector,
+          "properties.CompanionProperties");
+      ClassSubject companionClass = checkClassExists(dexInspector,
+          COMPANION_PROPERTY_CLASS.getClassName());
+      String propertyName = "primitiveProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(outerClass, "int", propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
 
-    Path output = writeToZip(builder);
-    addExtraClasspath(output);
+      MemberNaming.MethodSignature getter = COMPANION_PROPERTY_CLASS
+          .getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = COMPANION_PROPERTY_CLASS
+          .getSetterForProperty(propertyName);
+
+      // Getter and setter cannot be inlined because we don't know if null check semantic is
+      // preserved.
+      checkMethodIsPresent(companionClass, getter);
+      checkMethodIsPresent(companionClass, setter);
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+      }
+    });
+  }
+
+  @Test
+  public void testCompanionProperty_privatePropertyIsAlwaysInlined() throws Exception {
+    String mainClass = addMainToClasspath(
+        "properties.CompanionPropertiesKt", "companionProperties_usePrivateProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject outerClass = checkClassExists(dexInspector,
+          "properties.CompanionProperties");
+      ClassSubject companionClass = checkClassExists(dexInspector,
+          COMPANION_PROPERTY_CLASS.getClassName());
+      String propertyName = "privateProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(outerClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getter = COMPANION_PROPERTY_CLASS
+          .getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = COMPANION_PROPERTY_CLASS
+          .getSetterForProperty(propertyName);
+
+      // Because the getter/setter are private, they can only be called from another method in the
+      // class. If this is an instance method, they will be called on 'this' which is known to be
+      // non-null, thus the getter/setter can be inlined if their code is small enough.
+      // Because the backing field is private, they will call into an accessor (static) method. If
+      // access relaxation is enabled, this accessor can be removed.
+      checkMethodIsAbsent(companionClass, getter);
+      checkMethodIsAbsent(companionClass, setter);
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+      }
+    });
+  }
+
+  @Test
+  public void testCompanionProperty_internalPropertyCannotBeInlined() throws Exception {
+    String mainClass = addMainToClasspath(
+        "properties.CompanionPropertiesKt", "companionProperties_useInternalProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject outerClass = checkClassExists(dexInspector,
+          "properties.CompanionProperties");
+      ClassSubject companionClass = checkClassExists(dexInspector,
+          COMPANION_PROPERTY_CLASS.getClassName());
+      String propertyName = "internalProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(outerClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getter = COMPANION_PROPERTY_CLASS
+          .getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = COMPANION_PROPERTY_CLASS
+          .getSetterForProperty(propertyName);
+
+      // Getter and setter cannot be inlined because we don't know if null check semantic is
+      // preserved.
+      checkMethodIsPresent(companionClass, getter);
+      checkMethodIsPresent(companionClass, setter);
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+      }
+    });
+  }
+
+  @Test
+  public void testCompanionProperty_publicPropertyCannotBeInlined() throws Exception {
+    String mainClass = addMainToClasspath(
+        "properties.CompanionPropertiesKt", "companionProperties_usePublicProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject outerClass = checkClassExists(dexInspector,
+          "properties.CompanionProperties");
+      ClassSubject companionClass = checkClassExists(dexInspector,
+          COMPANION_PROPERTY_CLASS.getClassName());
+      String propertyName = "publicProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(outerClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getter = COMPANION_PROPERTY_CLASS
+          .getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = COMPANION_PROPERTY_CLASS
+          .getSetterForProperty(propertyName);
+
+      // Getter and setter cannot be inlined because we don't know if null check semantic is
+      // preserved.
+      checkMethodIsPresent(companionClass, getter);
+      checkMethodIsPresent(companionClass, setter);
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+      }
+    });
+  }
+
+  @Test
+  public void testCompanionProperty_privateLateInitPropertyIsAlwaysInlined() throws Exception {
+    final KotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
+        "companionLateInitProperties_usePrivateLateInitProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
+      ClassSubject companionClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "privateLateInitProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(outerClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+
+      // Because the getter/setter are private, they can only be called from another method in the
+      // class. If this is an instance method, they will be called on 'this' which is known to be
+      // non-null, thus the getter/setter can be inlined if their code is small enough.
+      // Because the backing field is private, they will call into an accessor (static) method. If
+      // access relaxation is enabled, this accessor can be removed.
+      checkMethodIsAbsent(companionClass, getter);
+      checkMethodIsAbsent(companionClass, setter);
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+      }
+    });
+  }
+
+  @Test
+  public void testCompanionProperty_internalLateInitPropertyCannotBeInlined() throws Exception {
+    final KotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
+        "companionLateInitProperties_useInternalLateInitProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
+      ClassSubject companionClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "internalLateInitProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(outerClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+
+      // Getter and setter cannot be inlined because we don't know if null check semantic is
+      // preserved.
+      checkMethodIsPresent(companionClass, getter);
+      checkMethodIsPresent(companionClass, setter);
+      assertTrue(fieldSubject.getField().accessFlags.isPublic());
+    });
+  }
+
+  @Test
+  public void testCompanionProperty_publicLateInitPropertyCannotBeInlined() throws Exception {
+    final KotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
+        "companionLateInitProperties_usePublicLateInitProp");
+    runTest(PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
+      ClassSubject companionClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "publicLateInitProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(outerClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+
+      // Getter and setter cannot be inlined because we don't know if null check semantic is
+      // preserved.
+      checkMethodIsPresent(companionClass, getter);
+      checkMethodIsPresent(companionClass, setter);
+      assertTrue(fieldSubject.getField().accessFlags.isPublic());
+    });
   }
 }
diff --git a/src/test/kotlinR8TestResources/properties/CompanionLateInitProperties.kt b/src/test/kotlinR8TestResources/properties/CompanionLateInitProperties.kt
new file mode 100644
index 0000000..d3db0f4
--- /dev/null
+++ b/src/test/kotlinR8TestResources/properties/CompanionLateInitProperties.kt
@@ -0,0 +1,41 @@
+// 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 CompanionLateInitProperties {
+    companion object {
+        private lateinit var privateLateInitProp: String
+        internal lateinit var internalLateInitProp: String
+        public lateinit var publicLateInitProp: String
+
+        fun callSetterPrivateProp(v: String) {
+            privateLateInitProp = v
+        }
+
+        fun callGetterPrivateProp(): String {
+            return privateLateInitProp
+        }
+    }
+}
+
+fun companionLateInitProperties_noUseOfProperties() {
+    CompanionLateInitProperties()
+    println("DONE")
+}
+
+fun companionLateInitProperties_usePrivateLateInitProp() {
+    CompanionLateInitProperties.callSetterPrivateProp("foo")
+    println(CompanionLateInitProperties.callGetterPrivateProp())
+}
+
+fun companionLateInitProperties_useInternalLateInitProp() {
+    CompanionLateInitProperties.internalLateInitProp = "foo"
+    println(CompanionLateInitProperties.internalLateInitProp)
+}
+
+fun companionLateInitProperties_usePublicLateInitProp() {
+    CompanionLateInitProperties.publicLateInitProp = "foo"
+    println(CompanionLateInitProperties.publicLateInitProp)
+}
diff --git a/src/test/kotlinR8TestResources/properties/CompanionProperties.kt b/src/test/kotlinR8TestResources/properties/CompanionProperties.kt
new file mode 100644
index 0000000..d0e3082
--- /dev/null
+++ b/src/test/kotlinR8TestResources/properties/CompanionProperties.kt
@@ -0,0 +1,48 @@
+// 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 CompanionProperties {
+    companion object {
+        private var privateProp: String = "privateProp"
+        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
+        }
+    }
+}
+
+fun companionProperties_noUseOfProperties() {
+    CompanionProperties()
+    println("DONE")
+}
+
+fun companionProperties_usePrivateProp() {
+    CompanionProperties.callSetterPrivateProp("foo")
+    println(CompanionProperties.callGetterPrivateProp())
+}
+
+fun companionProperties_useInternalProp() {
+    CompanionProperties.internalProp = "foo"
+    println(CompanionProperties.internalProp)
+}
+
+fun companionProperties_usePublicProp() {
+    CompanionProperties.publicProp = "foo"
+    println(CompanionProperties.publicProp)
+}
+
+fun companionProperties_usePrimitiveProp() {
+    CompanionProperties.primitiveProp = Int.MIN_VALUE
+    println(CompanionProperties.primitiveProp)
+}