Merge "Introduce ProguardIfRule that represents -if and subsequent -keep rule."
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 8d5857a..58bc675 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -1061,7 +1061,7 @@
     return hare;
   }
 
-  public boolean isSimpleAlwaysThrowingPath(boolean failOnMissingPosition) {
+  public boolean isSimpleAlwaysThrowingPath() {
     // See Floyd's cycle-finding algorithm for reference.
     BasicBlock hare = this;
     BasicBlock tortuous = this;
@@ -1072,10 +1072,6 @@
         return false;
       }
 
-      if (failOnMissingPosition && hasThrowingInstructionWithoutPosition(hare)) {
-        return false;
-      }
-
       if (normalSuccessors.size() == 0) {
         return hare.exit().isThrow();
       }
@@ -1089,15 +1085,6 @@
     }
   }
 
-  private boolean hasThrowingInstructionWithoutPosition(BasicBlock hare) {
-    for (Instruction instruction : hare.instructions) {
-      if (instruction.instructionTypeCanThrow() && instruction.getPosition().isNone()) {
-        return true;
-      }
-    }
-    return false;
-  }
-
   public Position getPosition() {
     BasicBlock block = endOfGotoChain();
     return block != null ? block.entry().getPosition() : Position.none();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index b9bbe5a..228f594 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -78,28 +78,35 @@
   private final Position callerPosition;
   private final DexMethod method;
   private final boolean preserveCaller;
+  private final Position preamblePosition;
 
   public DexSourceCode(
       DexCode code, DexEncodedMethod method, Position callerPosition, boolean preserveCaller) {
     this.code = code;
     this.proto = method.method.proto;
     this.accessFlags = method.accessFlags;
+    this.callerPosition = callerPosition;
+    this.method = method.method;
     this.preserveCaller = preserveCaller;
+
     argumentTypes = computeArgumentTypes();
     DexDebugInfo info = code.getDebugInfo();
     if (info != null) {
       debugEntries = info.computeEntries(method.method);
-      canonicalPositions = new HashMap<>(debugEntries.size());
     }
-    if (info != null && callerPosition != null) {
-      // Canonicalize callerPosition
-      this.callerPosition = callerPosition;
+    canonicalPositions =
+        new HashMap<>(
+            1
+                + (callerPosition == null ? 0 : 1)
+                + (debugEntries == null ? 0 : debugEntries.size()));
+    if (callerPosition != null) {
       canonicalPositions.put(callerPosition, callerPosition);
-    } else {
-      this.callerPosition = null;
     }
-
-    this.method = method.method;
+    preamblePosition =
+        callerPosition == null
+            ? Position.synthetic(0, this.method, null)
+            : new Position(0, null, this.method, callerPosition);
+    canonicalPositions.put(preamblePosition, preamblePosition);
   }
 
   @Override
@@ -136,7 +143,7 @@
 
   @Override
   public void buildPrelude(IRBuilder builder) {
-    currentPosition = Position.synthetic(0, method, null);
+    currentPosition = preamblePosition;
     if (code.incomingRegisterSize == 0) {
       return;
     }
@@ -231,7 +238,7 @@
       current = entry;
     }
     if (current == null) {
-      currentPosition = Position.none();
+      currentPosition = preamblePosition;
     } else {
       currentPosition = getCanonicalPositionAppendCaller(current);
       if (current.lineEntry && current.address == offset) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index fa0958b..c752fb5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -142,6 +142,7 @@
       accumulatedVirtualMethods.addAll(Arrays.asList(clazz.virtualMethods()));
 
       List<DexEncodedMethod> defaultMethodsInDirectInterface = helper.createFullList();
+
       List<DexEncodedMethod> toBeImplementedFromDirectInterface =
           new ArrayList<>(defaultMethodsInDirectInterface.size());
       hideCandidates(accumulatedVirtualMethods,
@@ -209,16 +210,7 @@
 
         // Everything still in candidate list is not hidden.
         toBeImplemented.addAll(candidates);
-        // NOTE: we also intentionally remove all candidates coming from android.jar
-        // since it is only possible in case v24+ version of android.jar is provided.
-        // WARNING: This may result in incorrect code on older platforms!
-        toBeImplemented.removeIf(
-            method -> {
-              DexClass holder = rewriter.findDefinitionFor(method.method.holder);
-              // Holder of a found method to implement is a defined interface.
-              assert holder != null;
-              return holder.isLibraryClass();
-            });
+
         return toBeImplemented;
       }
       current = superClass;
@@ -283,6 +275,14 @@
           + implementing.toString() + "`.");
     }
 
+    if (definedInterface.isLibraryClass()) {
+      // NOTE: We intentionally ignore all candidates coming from android.jar
+      // since it is only possible in case v24+ version of android.jar is provided.
+      // WARNING: This may result in incorrect code if something else than Android bootclasspath
+      // classes are given as libraries!
+      return helper.wrapInCollection();
+    }
+
     // Merge information from all superinterfaces.
     for (DexType superinterface : definedInterface.interfaces.values) {
       helper.merge(getOrCreateInterfaceInfo(classToDesugar, definedInterface, superinterface));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 769228d..069f67c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1434,7 +1434,7 @@
           // and the dominator or the original block has catch handlers.
           continue;
         }
-        if (!dominator.isSimpleAlwaysThrowingPath(false)) {
+        if (!dominator.isSimpleAlwaysThrowingPath()) {
           // Only move string constants into blocks being part of simple
           // always throwing path.
           continue;
@@ -1881,7 +1881,7 @@
       }
       if (block.exit().isIf()) {
         // Flip then/else branches if needed.
-        if (flipIfBranchesIfNeeded(block, code.hasDebugPositions)) {
+        if (flipIfBranchesIfNeeded(block)) {
           ifBranchFlipped = true;
         }
         // First rewrite zero comparison.
@@ -2207,18 +2207,17 @@
     }
   }
 
-  private boolean flipIfBranchesIfNeeded(BasicBlock block, boolean failOnMissingPosition) {
+  private boolean flipIfBranchesIfNeeded(BasicBlock block) {
     If theIf = block.exit().asIf();
     BasicBlock trueTarget = theIf.getTrueTarget();
     BasicBlock fallthrough = theIf.fallthroughBlock();
     assert trueTarget != fallthrough;
 
-    if (!fallthrough.isSimpleAlwaysThrowingPath(failOnMissingPosition) ||
-        trueTarget.isSimpleAlwaysThrowingPath(failOnMissingPosition)) {
+    if (!fallthrough.isSimpleAlwaysThrowingPath() || trueTarget.isSimpleAlwaysThrowingPath()) {
       return false;
     }
 
-    // In case fall-through block always trows there is a good chance that it
+    // In case fall-through block always throws there is a good chance that it
     // is created for error checks and 'trueTarget' represents most more common
     // non-error case. Flipping the if in this case may result in faster code
     // on older Android versions.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index da28ce0..a5ac28c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -374,8 +374,14 @@
     if (instruction_allowance < 0) {
       return;
     }
-    InliningOracle oracle = new InliningOracle(
-        this, method, typeEnvironment, callSiteInformation, isProcessedConcurrently);
+    InliningOracle oracle =
+        new InliningOracle(
+            this,
+            method,
+            typeEnvironment,
+            callSiteInformation,
+            isProcessedConcurrently,
+            options.inliningInstructionLimit);
 
     List<BasicBlock> blocksToRemove = new ArrayList<>();
     ListIterator<BasicBlock> blockIterator = code.listIterator();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index 0e3228c..fde6293 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -24,27 +24,28 @@
  */
 public class InliningOracle {
 
-  private static final int INLINING_INSTRUCTION_LIMIT = 5;
-
   private final Inliner inliner;
   private final DexEncodedMethod method;
   private final TypeEnvironment typeEnvironment;
   private final CallSiteInformation callSiteInformation;
   private final Predicate<DexEncodedMethod> isProcessedConcurrently;
   private final InliningInfo info;
+  private final int inliningInstructionLimit;
 
   InliningOracle(
       Inliner inliner,
       DexEncodedMethod method,
       TypeEnvironment typeEnvironment,
       CallSiteInformation callSiteInformation,
-      Predicate<DexEncodedMethod> isProcessedConcurrently) {
+      Predicate<DexEncodedMethod> isProcessedConcurrently,
+      int inliningInstructionLimit) {
     this.inliner = inliner;
     this.method = method;
     this.typeEnvironment = typeEnvironment;
     this.callSiteInformation = callSiteInformation;
     this.isProcessedConcurrently = isProcessedConcurrently;
     info = Log.ENABLED ? new InliningInfo(method) : null;
+    this.inliningInstructionLimit = inliningInstructionLimit;
   }
 
   void finish() {
@@ -196,7 +197,7 @@
     if (reason == Reason.SIMPLE) {
       // If we are looking for a simple method, only inline if actually simple.
       Code code = candidate.getCode();
-      if (code.estimatedSizeForInlining() > INLINING_INSTRUCTION_LIMIT) {
+      if (code.estimatedSizeForInlining() > inliningInstructionLimit) {
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
index e006bd4..f0d7c63 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
@@ -42,11 +42,4 @@
     return "identifiernamestring";
   }
 
-  public static ProguardIdentifierNameStringRule defaultAdaptAllRule() {
-    ProguardIdentifierNameStringRule.Builder builder = ProguardIdentifierNameStringRule.builder();
-    builder.setClassNames(
-        ProguardClassNameList.singletonList(ProguardTypeMatcher.defaultAllMatcher()));
-    return builder.build();
-  }
-
 }
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 1c0face..3831116 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -87,6 +87,7 @@
   public boolean enableDevirtualization = true;
   public boolean enableNonNullTracking = true;
   public boolean enableInlining = true;
+  public int inliningInstructionLimit = 5;
   public boolean enableSwitchMapRemoval = true;
   public final OutlineOptions outline = new OutlineOptions();
   public boolean enableValuePropagation = true;
diff --git a/src/test/examplesAndroidO/desugaringwithmissingclasstest6/MissingSuperImplementIterator.java b/src/test/examplesAndroidO/desugaringwithmissingclasstest6/MissingSuperImplementIterator.java
new file mode 100644
index 0000000..86d2882
--- /dev/null
+++ b/src/test/examplesAndroidO/desugaringwithmissingclasstest6/MissingSuperImplementIterator.java
@@ -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 desugaringwithmissingclasstest6;
+
+import desugaringwithmissingclasslib3.C;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public class MissingSuperImplementIterator extends C implements Iterator {
+
+  @Override
+  public boolean hasNext() {
+    return false;
+  }
+
+  @Override
+  public Object next() {
+    throw new NoSuchElementException();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
index b5802f4..77959d9 100644
--- a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
@@ -415,6 +415,34 @@
   }
 
   @Test
+  public void testMissingSuperDesugaredWithLibInterfaceAndroidK() throws Throwable {
+    AndroidApiLevel minApi = AndroidApiLevel.K;
+
+    // Reference case: there's nothing to do, we should not complain.
+    // Class MissingSuperImplementIterator extends C implements Iterator should not require
+    // desugaring because Iterator has no default method at this API level.
+    // The test is compiled with incomplete classpath: lib3 is missing so
+    // MissingSuperImplementIterator is missing its super class.
+    test("desugaringwithmissingclasstest6", "desugaringwithmissingclasstest6", "N/A")
+        .withInterfaceMethodDesugaring(OffOrAuto.Auto)
+        .withAndroidJar(AndroidApiLevel.K)
+        .withMinApiLevel(minApi)
+        .build();
+
+    // More litigious case, D8 needs to detect that the default method is part of bootclasspath.
+    // Class MissingSuperImplementIterator extends C implements Iterator should not require
+    // desugaring of Iterator default method because it is part of the Android API and thus is
+    // declaring the default method only when default methods are supported by the runtime.
+    // test is compiled with incomplete classpath: lib3 is missing so
+    // MissingSuperImplementIterator is missing its super class.
+    test("desugaringwithmissingclasstest6", "desugaringwithmissingclasstest6", "N/A")
+        .withInterfaceMethodDesugaring(OffOrAuto.Auto)
+        .withAndroidJar(AndroidApiLevel.N)
+        .withMinApiLevel(minApi)
+        .build();
+  }
+
+  @Test
   public void testMissingSuperDesugaredWithProgramCrossImplementationAndroidK() throws Throwable {
     AndroidApiLevel minApi = AndroidApiLevel.K;
 
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index d03a03f..8ff72c7 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -4541,10 +4541,6 @@
           // 1) t02
           // java.lang.AssertionError: Failed to load serialization resource file: serialization/com/google/jctf/test/lib/java/util/concurrent/PriorityBlockingQueue/serialization/PriorityBlockingQueue_serialization_A01.golden.0.ser
 
-          .put("lang.ThreadGroup.destroy.ThreadGroup_destroy_A01", match(R8_NOT_AFTER_D8_COMPILER))
-          // 1) t05
-          // java.lang.AssertionError: Destroyed thread group was not finalized
-
           .put("lang.ThreadGroup.destroy.ThreadGroup_destroy_A01",
               match(D8_COMPILER, runtimesUpTo(Version.V6_0_1)))
           // 1) t02
diff --git a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
new file mode 100644
index 0000000..c67cf32
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
@@ -0,0 +1,202 @@
+// 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.debuginfo;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.debuginfo.InliningWithoutPositionsTestSourceDump.Location;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
+import com.android.tools.r8.naming.Range;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InliningWithoutPositionsTestRunner {
+
+  private static final String TEST_CLASS = "InliningWithoutPositionsTestSource";
+  private static final String TEST_PACKAGE = "com.android.tools.r8.debuginfo";
+
+  @ClassRule public static TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  private final boolean mainPos;
+  private final boolean foo1Pos;
+  private final boolean barPos;
+  private final boolean foo2Pos;
+  private final Location throwLocation;
+
+  @Parameters(name = "main/foo1/bar/foo2 positions: {0}/{1}/{2}/{3}, throwLocation: {4}")
+  public static Collection<Object[]> data() {
+    List<Object[]> testCases = new ArrayList<>();
+    for (int i = 0; i < 16; ++i) {
+      for (Location throwLocation : Location.values()) {
+        if (throwLocation != Location.MAIN) {
+          testCases.add(
+              new Object[] {(i & 1) != 0, (i & 2) != 0, (i & 4) != 0, (i & 8) != 0, throwLocation});
+        }
+      }
+    }
+    return testCases;
+  }
+
+  public InliningWithoutPositionsTestRunner(
+      boolean mainPos, boolean foo1Pos, boolean barPos, boolean foo2Pos, Location throwLocation) {
+    this.mainPos = mainPos;
+    this.foo1Pos = foo1Pos;
+    this.barPos = barPos;
+    this.foo2Pos = foo2Pos;
+    this.throwLocation = throwLocation;
+  }
+
+  @Test
+  public void testStackTrace() throws IOException, Exception {
+    // See InliningWithoutPositionsTestSourceDump for the code compiled here.
+    Path testClassDir = temp.newFolder(TEST_PACKAGE.split(".")).toPath();
+    Path testClassPath = testClassDir.resolve(TEST_CLASS + ".class");
+    Path outputDexPath = temp.newFolder().toPath();
+
+    Files.write(
+        testClassPath,
+        InliningWithoutPositionsTestSourceDump.dump(
+            mainPos, foo1Pos, barPos, foo2Pos, throwLocation));
+
+    AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
+    Path proguardMapPath = testClassDir.resolve("proguard.map");
+
+    ToolHelper.runR8(
+        R8Command.builder()
+            .addProgramFiles(testClassPath)
+            .setMinApiLevel(minSdk.getLevel())
+            .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
+            .setOutput(outputDexPath, OutputMode.DexIndexed)
+            .setMode(CompilationMode.RELEASE)
+            .setProguardMapOutputPath(proguardMapPath)
+            .build(),
+        options -> options.inliningInstructionLimit = 20);
+
+    ArtCommandBuilder artCommandBuilder = new ArtCommandBuilder();
+    artCommandBuilder.appendClasspath(outputDexPath.resolve("classes.dex").toString());
+    artCommandBuilder.setMainClass(TEST_PACKAGE + "." + TEST_CLASS);
+
+    ProcessResult result = ToolHelper.runArtRaw(artCommandBuilder);
+
+    assertNotEquals(result.exitCode, 0);
+
+    // Verify stack trace.
+    // result.stderr looks like this:
+    //
+    //     Exception in thread "main" java.lang.RuntimeException: <FOO1-exception>
+    //       at
+    // com.android.tools.r8.debuginfo.InliningWithoutPositionsTestSource.main(InliningWithoutPositionsTestSource.java:1)
+    String[] lines = result.stderr.split("\n");
+
+    // The line containing 'java.lang.RuntimeException' should contain the expected message, which
+    // is "LOCATIONCODE-exception>"
+    int i = 0;
+    boolean foundException = false;
+    for (; i < lines.length && !foundException; ++i) {
+      boolean hasExpectedException = lines[i].contains("<" + throwLocation + "-exception>");
+      if (lines[i].contains("java.lang.RuntimeException")) {
+        assertTrue(hasExpectedException);
+        foundException = true;
+      } else {
+        assertFalse(hasExpectedException);
+      }
+    }
+    assertTrue(foundException);
+
+    // The next line, the stack trace, must always be the same, indicating 'main' and line = 1 or 2.
+    Assert.assertTrue(i < lines.length);
+    String line = lines[i].trim();
+    assertTrue(line.startsWith("at " + TEST_PACKAGE + "." + TEST_CLASS + "." + "main"));
+
+    // It must contain the '<source-file>:1' or ':2', if we're throwing at foo2.
+    int expectedLineNumber = throwLocation == Location.FOO2 ? 2 : 1;
+    String expectedFilePos = TEST_CLASS + ".java:" + expectedLineNumber;
+    int idx = line.indexOf(expectedFilePos);
+    assertTrue(idx >= 0);
+
+    // And the next character must be a non-digit or nothing.
+    int idxAfter = idx + expectedFilePos.length();
+    assertTrue(idxAfter == line.length() || !Character.isDigit(line.charAt(idxAfter)));
+
+    // Reading the Proguard map. An example map (only the relevant part, 'main'):
+    //
+    //     1:1:void bar():0:0 -> main
+    //     1:1:void foo(boolean):0 -> main
+    //     1:1:void main(java.lang.String[]):0 -> main
+    ClassNameMapper mapper = ClassNameMapper.mapperFromFile(proguardMapPath);
+    assertNotNull(mapper);
+
+    ClassNamingForNameMapper classNaming = mapper.getClassNaming(TEST_PACKAGE + "." + TEST_CLASS);
+    assertNotNull(classNaming);
+
+    MappedRangesOfName rangesForMain = classNaming.mappedRangesByName.get("main");
+    assertNotNull(rangesForMain);
+
+    List<MappedRange> frames = rangesForMain.allRangesForLine(expectedLineNumber);
+
+    switch (throwLocation) {
+      case FOO1:
+        assertEquals(2, frames.size());
+        assertFrame(true, "foo", Location.FOO1, foo1Pos, frames.get(0));
+        break;
+      case BAR:
+        assertEquals(3, frames.size());
+        assertFrame(true, "bar", Location.BAR, barPos, frames.get(0));
+        assertFrame(false, "foo", Location.FOO1, foo1Pos, frames.get(1));
+        break;
+      case FOO2:
+        assertEquals(2, frames.size());
+        // If there's no foo2Pos then we expect foo1 pos at this location.
+        if (foo2Pos) {
+          assertFrame(true, "foo", Location.FOO2, true, frames.get(0));
+        } else {
+          assertFrame(true, "foo", Location.FOO1, foo1Pos, frames.get(0));
+        }
+        break;
+      default:
+        Assert.fail();
+    }
+    assertFrame(false, "main", Location.MAIN, mainPos, frames.get(frames.size() - 1));
+  }
+
+  private void assertFrame(
+      boolean innermostFrame,
+      String function,
+      Location location,
+      boolean hasPosition,
+      MappedRange range) {
+    assertEquals(function, range.signature.name);
+    int expectedLineNumber = hasPosition ? location.line : 0;
+    Object expectedOriginalRange =
+        innermostFrame ? new Range(expectedLineNumber, expectedLineNumber) : expectedLineNumber;
+    assertEquals(expectedOriginalRange, range.originalRange);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestSourceDump.java b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestSourceDump.java
new file mode 100644
index 0000000..53d6df3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestSourceDump.java
@@ -0,0 +1,242 @@
+// 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.debuginfo;
+
+import java.util.*;
+import org.objectweb.asm.*;
+
+public class InliningWithoutPositionsTestSourceDump implements Opcodes {
+
+  public enum Location {
+    MAIN(123),
+    FOO1(234),
+    BAR(345),
+    FOO2(456);
+    final int line;
+
+    Location(int line) {
+      this.line = line;
+    }
+  };
+
+  /*
+  This generator creates the class 'InliningWithoutPositionsTestSource' with three methods,
+  'main', calling 'foo', which is calling 'bar'.
+
+  Depending on the mainPos, foo1Pos, barPos and foo2Pos parameters, we insert line numbers in
+  'main', in 'foo' before calling 'bar' (marked foo1), in 'foo' after calling 'bar' (marked foo2)
+  and in 'bar'. The methods contain no other line numbers.
+
+  The throwLocation parameter determines at which location will be an exception thrown.
+
+  The generator code is based on the ASMifier dump of the following class:
+
+      package com.android.tools.r8.debuginfo;
+
+      public class InliningWithoutPositionsTestSource {
+
+        private static void throwingBar() {
+          throw new RuntimeException("<BAR-exception>");
+        }
+
+        private static void printingBar() {
+          System.err.println("<in-BAR>");
+        }
+
+        private static void foo(boolean b) {
+          if (b) throw new RuntimeException("<FOO1-exception>");
+          throwingBar();
+        }
+
+        private static void foo2() {
+          throwingBar();
+          throw new RuntimeException("<FOO2-exception>");
+        }
+
+        public static void main(String[] args) {
+          foo(true);
+        }
+      }
+  */
+  public static byte[] dump(
+      boolean mainPos, boolean foo1Pos, boolean barPos, boolean foo2Pos, Location throwLocation)
+      throws Exception {
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+    AnnotationVisitor av0;
+
+    cw.visit(
+        V1_8,
+        ACC_PUBLIC + ACC_SUPER,
+        "com/android/tools/r8/debuginfo/InliningWithoutPositionsTestSource",
+        null,
+        "java/lang/Object",
+        null);
+
+    cw.visitSource("InliningWithoutPositionsTestSource.java", null);
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      mv.visitCode();
+      Label l0 = new Label();
+      mv.visitLabel(l0);
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      Label l1 = new Label();
+      mv.visitLabel(l1);
+      mv.visitLocalVariable(
+          "this",
+          "Lcom/android/tools/r8/debuginfo/InliningWithoutPositionsTestSource;",
+          null,
+          l0,
+          l1,
+          0);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    if (throwLocation == Location.BAR) {
+      mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, "bar", "()V", null, null);
+      mv.visitCode();
+      Label l0 = new Label();
+      mv.visitLabel(l0);
+      if (barPos) {
+        mv.visitLineNumber(Location.BAR.line, l0);
+      }
+      mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
+      mv.visitInsn(DUP);
+      mv.visitLdcInsn("<BAR-exception>");
+      mv.visitMethodInsn(
+          INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V", false);
+      mv.visitInsn(ATHROW);
+      mv.visitMaxs(3, 0);
+      mv.visitEnd();
+    } else {
+      mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, "bar", "()V", null, null);
+      mv.visitCode();
+      Label l0 = new Label();
+      mv.visitLabel(l0);
+      if (barPos) {
+        mv.visitLineNumber(Location.BAR.line, l0);
+      }
+      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
+      mv.visitLdcInsn("<in-BAR>");
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      Label l1 = new Label();
+      mv.visitLabel(l1);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(2, 0);
+      mv.visitEnd();
+    }
+    if (throwLocation == Location.FOO2) {
+      mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, "foo", "()V", null, null);
+      mv.visitCode();
+      Label l0 = new Label();
+      mv.visitLabel(l0);
+      if (foo1Pos) {
+        mv.visitLineNumber(Location.FOO1.line, l0);
+      }
+      mv.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/debuginfo/InliningWithoutPositionsTestSource",
+          "bar",
+          "()V",
+          false);
+      Label l1 = new Label();
+      mv.visitLabel(l1);
+      if (foo2Pos) {
+        mv.visitLineNumber(Location.FOO2.line, l1);
+      }
+      mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
+      mv.visitInsn(DUP);
+      mv.visitLdcInsn("<FOO2-exception>");
+      mv.visitMethodInsn(
+          INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V", false);
+      mv.visitInsn(ATHROW);
+      mv.visitMaxs(3, 0);
+      mv.visitEnd();
+    } else {
+      mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, "foo", "(Z)V", null, null);
+      mv.visitCode();
+      Label l0 = new Label();
+      mv.visitLabel(l0);
+      if (foo1Pos) {
+        mv.visitLineNumber(Location.FOO1.line, l0);
+      }
+      mv.visitVarInsn(ILOAD, 0);
+      Label l1 = new Label();
+      mv.visitJumpInsn(IFEQ, l1);
+      mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
+      mv.visitInsn(DUP);
+      mv.visitLdcInsn("<FOO1-exception>");
+      mv.visitMethodInsn(
+          INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V", false);
+      mv.visitInsn(ATHROW);
+      mv.visitLabel(l1);
+      mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      mv.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/debuginfo/InliningWithoutPositionsTestSource",
+          "bar",
+          "()V",
+          false);
+      Label l2 = new Label();
+      mv.visitLabel(l2);
+      if (foo2Pos) {
+        mv.visitLineNumber(Location.FOO2.line, l2);
+      }
+      mv.visitInsn(RETURN);
+      Label l3 = new Label();
+      mv.visitLabel(l3);
+      mv.visitLocalVariable("b", "Z", null, l0, l3, 0);
+      mv.visitMaxs(3, 1);
+      mv.visitEnd();
+    }
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+      mv.visitCode();
+      Label l0 = new Label();
+      mv.visitLabel(l0);
+      if (mainPos) {
+        mv.visitLineNumber(Location.MAIN.line, l0);
+      }
+      if (throwLocation == Location.FOO2) {
+        mv.visitMethodInsn(
+            INVOKESTATIC,
+            "com/android/tools/r8/debuginfo/InliningWithoutPositionsTestSource",
+            "foo",
+            "()V",
+            false);
+      } else {
+        mv.visitInsn(throwLocation == Location.FOO1 ? ICONST_1 : ICONST_0);
+        mv.visitMethodInsn(
+            INVOKESTATIC,
+            "com/android/tools/r8/debuginfo/InliningWithoutPositionsTestSource",
+            "foo",
+            "(Z)V",
+            false);
+      }
+      Label l1 = new Label();
+      mv.visitLabel(l1);
+      mv.visitInsn(RETURN);
+      Label l2 = new Label();
+      mv.visitLabel(l2);
+      mv.visitLocalVariable("args", "[Ljava/lang/String;", null, l0, l2, 0);
+      if (throwLocation == Location.FOO2) {
+        mv.visitMaxs(0, 1);
+      } else {
+        mv.visitMaxs(1, 1);
+      }
+      mv.visitEnd();
+    }
+
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+}
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)
+}