Merge "Make desugaring more tolerant on missing hierarchy"
diff --git a/build.gradle b/build.gradle
index 9b8d59b..f857f9e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -300,6 +300,7 @@
                 "android_jar/lib-v25",
                 "android_jar/lib-v26",
                 "proguard/proguard5.2.1",
+                "proguard/proguard6.0",
                 "gradle/gradle",
                 "jdwp-tests",
                 "jasmin",
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
index 89dee13..b038fcf 100644
--- a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -86,7 +86,7 @@
 
     private static String getDefaultDexFileName(int fileIndex) {
       return fileIndex == 0
-          ? "classes.dex"
+          ? "classes" + FileUtils.DEX_EXTENSION
           : ("classes" + (fileIndex + 1) + FileUtils.DEX_EXTENSION);
     }
 
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 7004d5f..9a61a9f 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -319,6 +319,9 @@
 
       assert getProgramConsumer() != null;
 
+      boolean desugaring =
+          (getProgramConsumer() instanceof ClassFileConsumer) ? false : !getDisableDesugaring();
+
       R8Command command =
           new R8Command(
               getAppBuilder().build(),
@@ -329,7 +332,7 @@
               getMode(),
               getMinApiLevel(),
               reporter,
-              !getDisableDesugaring(),
+              desugaring,
               configuration.isShrinking(),
               configuration.isObfuscating(),
               forceProguardCompatibility,
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 5d77ad3..fed4a00 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "v1.1.7-dev";
+  public static final String LABEL = "v1.1.8-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index f7c3b24..58451eb 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -10,10 +10,13 @@
 import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Consumer;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 
@@ -150,11 +153,26 @@
   @Override
   public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
       throws ApiLevelException {
-    throw new Unimplemented("Converting Java class- file bytecode to IR not yet supported");
+    throw new Unimplemented("Converting Java class-file bytecode to IR not yet supported");
   }
 
   @Override
-  public void registerReachableDefinitions(UseRegistry registry) {
+  public IRCode buildInliningIR(
+      DexEncodedMethod encodedMethod,
+      InternalOptions options,
+      ValueNumberGenerator valueNumberGenerator,
+      Position callerPosition)
+      throws ApiLevelException {
+    throw new Unimplemented("Converting Java class-file bytecode to IR not yet supported");
+  }
+
+  @Override
+  public void registerInstructionsReferences(UseRegistry registry) {
+    throw new Unimplemented("Inspecting Java class-file bytecode not yet supported");
+  }
+
+  @Override
+  public void registerCaughtTypes(Consumer<DexType> dexTypeConsumer) {
     throw new Unimplemented("Inspecting Java class-file bytecode not yet supported");
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 14c4bbc..b6c1583 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.optimize.Outliner.OutlineCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.function.Consumer;
 
 public abstract class Code extends CachedHashValueDexItem {
 
@@ -29,7 +30,9 @@
         + getClass().getCanonicalName());
   }
 
-  public abstract void registerReachableDefinitions(UseRegistry registry);
+  public abstract void registerInstructionsReferences(UseRegistry registry);
+
+  public abstract void registerCaughtTypes(Consumer<DexType> dexTypeConsumer);
 
   @Override
   public abstract String toString();
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index a522e82..1517dfb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
@@ -27,6 +28,7 @@
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Consumer;
 
 // DexCode corresponds to code item in dalvik/dex-format.html
 public class DexCode extends Code {
@@ -188,13 +190,22 @@
   }
 
   @Override
-  public void registerReachableDefinitions(UseRegistry registry) {
+  public void registerInstructionsReferences(UseRegistry registry) {
     for (Instruction insn : instructions) {
       insn.registerUse(registry);
     }
   }
 
   @Override
+  public void registerCaughtTypes(Consumer<DexType> dexTypeConsumer) {
+    for (TryHandler handler : handlers) {
+      for (TypeAddrPair pair : handler.pairs) {
+        dexTypeConsumer.accept(pair.type);
+      }
+    }
+  }
+
+  @Override
   public String toString() {
     return toString(null, null);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index 69fb348..bfc0a9f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -195,11 +195,13 @@
   private void emitDebugPosition(int pc, Position position) {
     assert !position.equals(emittedPosition);
     if (startLine == NO_LINE_INFO) {
-      if (position.synthetic) {
+      assert emittedPosition.isNone();
+      if (position.synthetic && position.callerPosition == null) {
         // Ignore synthetic positions prior to any actual position.
+        // We do need to preserve synthetic position establishing the stack frame for inlined
+        // methods.
         return;
       }
-      assert emittedPosition.isNone();
       startLine = position.line;
       emittedPosition = new Position(position.line, null, method.method, null);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index a1e8821..4fdd8ac 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -501,12 +501,21 @@
     return !annotations.isEmpty() || !parameterAnnotations.isEmpty();
   }
 
-  public void registerReachableDefinitions(UseRegistry registry) {
+  public void registerInstructionsReferences(UseRegistry registry) {
     if (code != null) {
       if (Log.ENABLED) {
         Log.verbose(getClass(), "Registering definitions reachable from `%s`.", method);
       }
-      code.registerReachableDefinitions(registry);
+      code.registerInstructionsReferences(registry);
+    }
+  }
+
+  public void registerCatchedTypes(Consumer<DexType> dexTypeConsumer) {
+    if (code != null) {
+      if (Log.ENABLED) {
+        Log.verbose(getClass(), "Visiting catched types `%s`.", method);
+      }
+      code.registerCaughtTypes(dexTypeConsumer);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 62dc43f..f9ea993 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -13,11 +13,13 @@
 import com.android.tools.r8.jar.JarRegisterEffectsVisitor;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.MethodVisitor;
@@ -60,6 +62,11 @@
     context.codeList.add(this);
   }
 
+  public MethodNode getNode() {
+    triggerDelayedParsingIfNeccessary();
+    return node;
+  }
+
   @Override
   public boolean isJarCode() {
     return true;
@@ -148,13 +155,20 @@
   }
 
   @Override
-  public void registerReachableDefinitions(UseRegistry registry) {
+  public void registerInstructionsReferences(UseRegistry registry) {
     triggerDelayedParsingIfNeccessary();
     node.instructions.accept(
         new JarRegisterEffectsVisitor(method.getHolder(), registry, application));
   }
 
   @Override
+  public void registerCaughtTypes(Consumer<DexType> dexTypeConsumer) {
+    node.tryCatchBlocks.forEach(tryCatchBlockNode ->
+        dexTypeConsumer.accept(application.getTypeFromDescriptor(
+            DescriptorUtils.getDescriptorFromClassBinaryName(tryCatchBlockNode.type))));
+  }
+
+  @Override
   public String toString() {
     triggerDelayedParsingIfNeccessary();
     TraceMethodVisitor visitor = new TraceMethodVisitor(new Textifier());
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
index ba08ebb..821a318 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
@@ -34,17 +34,23 @@
   }
 
   @Override
+  public void analyze() {
+    while (!worklist.isEmpty()) {
+      analyzeValue(worklist.poll());
+    }
+  }
+
+  @Override
   public void analyzeBlocks(List<BasicBlock> blocks) {
     assert worklist.isEmpty();
     for (BasicBlock block : blocks) {
       processBasicBlock(block);
     }
-    while (!worklist.isEmpty()) {
-      processValue(worklist.poll());
-    }
+    analyze();
   }
 
-  private void addToWorklist(Value v) {
+  @Override
+  public void enqueue(Value v) {
     assert v != null;
     if (!worklist.contains(v)) {
       worklist.add(v);
@@ -72,16 +78,16 @@
         updateTypeOfValue(outValue, derived);
       } else {
         if (outValue != null) {
-          addToWorklist(outValue);
+          enqueue(outValue);
         }
       }
     }
     for (Phi phi : block.getPhis()) {
-      addToWorklist(phi);
+      enqueue(phi);
     }
   }
 
-  private void processValue(Value value) {
+  private void analyzeValue(Value value) {
     TypeLatticeElement derived =
         value.isPhi()
             ? computePhiType(value.asPhi())
@@ -100,14 +106,12 @@
     for (Instruction instruction : value.uniqueUsers()) {
       Value outValue = instruction.outValue();
       if (outValue != null) {
-        // TODO(b/72693244): processValue instead of queueing.
-        addToWorklist(outValue);
+        enqueue(outValue);
       }
     }
     // Propagate the type change to phi users if any.
     for (Phi phi : value.uniquePhiUsers()) {
-      // TODO(b/72693244): processValue instead of queueing.
-      addToWorklist(phi);
+      enqueue(phi);
     }
   }
 
@@ -141,6 +145,18 @@
 
   private static final TypeEnvironment DEFAULT_ENVIRONMENT = new TypeEnvironment() {
     @Override
+    public void analyze() {
+    }
+
+    @Override
+    public void analyzeBlocks(List<BasicBlock> blocks) {
+    }
+
+    @Override
+    public void enqueue(Value value) {
+    }
+
+    @Override
     public TypeLatticeElement getLatticeElement(Value value) {
       return Top.getInstance();
     }
@@ -149,10 +165,6 @@
     public DexType getRefinedReceiverType(InvokeMethodWithReceiver invoke) {
       return invoke.getInvokedMethod().holder;
     }
-
-    @Override
-    public void analyzeBlocks(List<BasicBlock> blocks) {
-    }
   };
 
   // TODO(b/72693244): By annotating type lattice to value, remove the default type env at all.
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeEnvironment.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeEnvironment.java
index 890af8a..85fc5d9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeEnvironment.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeEnvironment.java
@@ -10,7 +10,10 @@
 import java.util.List;
 
 public interface TypeEnvironment {
+  void analyze();
+  void analyzeBlocks(List<BasicBlock> blocks);
+  void enqueue(Value value);
+
   TypeLatticeElement getLatticeElement(Value value);
   DexType getRefinedReceiverType(InvokeMethodWithReceiver invoke);
-  void analyzeBlocks(List<BasicBlock> blocks);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index cde22b0..8d522de 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -31,6 +31,7 @@
 
   @Override
   public void buildDex(DexBuilder builder) {
+    assert getPosition().isSome() && !getPosition().synthetic;
     builder.addDebugPosition(this);
   }
 
@@ -73,6 +74,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
+    assert getPosition().isSome() && !getPosition().synthetic;
     // All redundant debug positions are removed. Remaining ones must force a pc advance.
     builder.add(new CfNop());
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NonNull.java b/src/main/java/com/android/tools/r8/ir/code/NonNull.java
index 0fcb330..51ef234 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NonNull.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NonNull.java
@@ -15,10 +15,13 @@
 public class NonNull extends Instruction {
   private final static String ERROR_MESSAGE = "This fake IR should be removed after inlining.";
 
-  public NonNull(Value dest, Value src) {
+  final Instruction origin;
+
+  public NonNull(Value dest, Value src, Instruction origin) {
     super(dest, src);
     assert !src.isNeverNull();
     dest.markNeverNull();
+    this.origin = origin;
   }
 
   public Value dest() {
@@ -29,6 +32,10 @@
     return inValues.get(0);
   }
 
+  public Instruction origin() {
+    return origin;
+  }
+
   @Override
   public boolean isNonNull() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index b2dafdd..f863778 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -144,7 +144,7 @@
       for (DexEncodedMethod method : clazz.allMethodsSorted()) {
         Node node = graph.ensureMethodNode(method);
         InvokeExtractor extractor = new InvokeExtractor(appInfo, graphLense, node, graph);
-        method.registerReachableDefinitions(extractor);
+        method.registerInstructionsReferences(extractor);
       }
     }
     assert allMethodsExists(application, graph);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 96b2122..3ad0168 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -984,14 +984,7 @@
       int source = builder.getInfo(jump).getOffset();
       Info targetInfo = builder.getTargetInfo(jump.getTarget());
       int relativeOffset = targetInfo.getOffset() - source;
-      // Emit a return if the target is a return and the size of the return is the computed
-      // size of this instruction.
-      Return ret = targetInfo.getIR().asReturn();
-      if (ret != null && size == targetInfo.getSize() && ret.getPosition().isNone()) {
-        Instruction dex = ret.createDexInstruction(builder);
-        dex.setOffset(getOffset()); // for better printing of the dex code.
-        instructions.add(dex);
-      } else if (size == relativeOffset) {
+      if (size == relativeOffset) {
         // We should never generate a goto targeting the next instruction. However, if we do
         // we replace it with nops. This works around a dalvik bug where the dalvik tracing
         // jit crashes on 'goto next instruction' on Android 4.1.1.
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 55e4b83..b9bbe5a 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
@@ -136,7 +136,7 @@
 
   @Override
   public void buildPrelude(IRBuilder builder) {
-    currentPosition = Position.none();
+    currentPosition = Position.synthetic(0, method, null);
     if (code.incomingRegisterSize == 0) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 98c1015..6364c82 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -396,10 +396,6 @@
     // necessary.
     ir.splitCriticalEdges();
 
-    if (options.testing.invertConditionals) {
-      invertConditionalsForTesting(ir);
-    }
-
     // Create block order and make sure that all blocks are immediately followed by their
     // fallthrough block if any.
     ir.traceBlocks();
@@ -440,7 +436,7 @@
           } else {
             current = position;
           }
-        } else if (position.isSome() && !position.equals(current)) {
+        } else if (position.isSome() && !position.synthetic && !position.equals(current)) {
           DebugPosition positionChange = new DebugPosition();
           positionChange.setPosition(position);
           it.previous();
@@ -2006,14 +2002,6 @@
     return type != NumericType.FLOAT && type != NumericType.DOUBLE && type != NumericType.LONG;
   }
 
-  private static void invertConditionalsForTesting(IRCode code) {
-    for (BasicBlock block : code.blocks) {
-      if (block.exit().isIf()) {
-        block.exit().asIf().invert();
-      }
-    }
-  }
-
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 39cea77..f9bcf1d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
+import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
@@ -31,6 +32,7 @@
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.ir.optimize.Devirtualizer;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.MemberValuePropagation;
@@ -86,6 +88,7 @@
   private final Inliner inliner;
   private final ProtoLitePruner protoLiteRewriter;
   private final IdentifierNameStringMarker identifierNameStringMarker;
+  private final Devirtualizer devirtualizer;
 
   private final OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
   private DexString highestSortingString;
@@ -129,9 +132,12 @@
         } else {
           this.identifierNameStringMarker = null;
         }
+        this.devirtualizer =
+            options.enableDevirtualization ? new Devirtualizer(appInfo.withLiveness()) : null;
       } else {
         this.protoLiteRewriter = null;
         this.identifierNameStringMarker = null;
+        this.devirtualizer = null;
       }
     } else {
       this.nonNullTracker = null;
@@ -141,6 +147,7 @@
       this.lensCodeRewriter = null;
       this.protoLiteRewriter = null;
       this.identifierNameStringMarker = null;
+      this.devirtualizer = null;
     }
   }
 
@@ -511,6 +518,14 @@
     }
   }
 
+  private static void invertConditionalsForTesting(IRCode code) {
+    for (BasicBlock block : code.blocks) {
+      if (block.exit().isIf()) {
+        block.exit().asIf().invert();
+      }
+    }
+  }
+
   private void rewriteCode(DexEncodedMethod method,
       OptimizationFeedback feedback,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
@@ -578,7 +593,9 @@
       inliner.performInlining(
           method, code, typeEnvironment, isProcessedConcurrently, callSiteInformation);
     }
-    // TODO(b/69962188): MethodDevirtualizer can perform optimizations using type analysis.
+    if (devirtualizer != null) {
+      devirtualizer.devirtualizeInvokeInterface(code, typeEnvironment, method.method.getHolder());
+    }
     codeRewriter.removeCasts(code, typeEnvironment);
     codeRewriter.rewriteLongCompareAndRequireNonNull(code, options);
     codeRewriter.commonSubexpressionElimination(code);
@@ -589,6 +606,12 @@
     codeRewriter.rewriteSwitch(code);
     codeRewriter.processMethodsNeverReturningNormally(code);
     codeRewriter.simplifyIf(code, typeEnvironment);
+
+    if (options.testing.invertConditionals) {
+      invertConditionalsForTesting(code);
+      code.traceBlocks();
+    }
+
     if (options.enableNonNullTracking && nonNullTracker != null) {
       nonNullTracker.cleanupNonNull(code);
       assert code.isConsistentSSA();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 69ecb83..12429e9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -186,6 +186,9 @@
   // Canonicalized positions to lower memory usage.
   private final Int2ReferenceMap<Position> canonicalPositions = new Int2ReferenceOpenHashMap<>();
 
+  // Synthetic position with line = 0.
+  private Position preamblePosition = null;
+
   // Cooked position to indicate positions in synthesized code (ie, for synchronization).
   private Position syntheticPosition = null;
 
@@ -262,6 +265,8 @@
 
   @Override
   public void buildPrelude(IRBuilder builder) {
+    currentPosition = getPreamblePosition();
+
     // Record types for arguments.
     Int2ReferenceMap<ValueType> argumentLocals = recordArgumentTypes();
     Int2ReferenceMap<ValueType> initializedLocals = new Int2ReferenceOpenHashMap<>(argumentLocals);
@@ -327,8 +332,6 @@
       locals = state.openLocals(getOffset(initialLabel));
     }
 
-    currentPosition = Position.none();
-
     // Build the actual argument instructions now that type and debug information is known
     // for arguments.
     buildArgumentInstructions(builder);
@@ -457,7 +460,7 @@
   private void buildExceptionalPostlude(IRBuilder builder) {
     assert isSynchronized();
     generatingMethodSynchronization = true;
-    currentPosition = getSyntheticPosition();
+    currentPosition = getExceptionalExitPosition();
     buildMonitorExit(builder);
     builder.addThrow(getMoveExceptionRegister());
     generatingMethodSynchronization = false;
@@ -503,7 +506,9 @@
     // writes after the line has changed and thus causing locals to become visible too late.
     currentPosition =
         getDebugPositionAtOffset(
-            insn instanceof LabelNode ? instructionIndex - 1 : instructionIndex);
+            ((instructionIndex > 0) && (insn instanceof LabelNode))
+                ? instructionIndex - 1
+                : instructionIndex);
 
     build(insn, builder);
 
@@ -2906,10 +2911,11 @@
   @Override
   public Position getDebugPositionAtOffset(int offset) {
     if (offset == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
-      return getSyntheticPosition();
+      return getExceptionalExitPosition();
     }
     int index = instructionIndex(offset);
     if (index < 0 || instructionCount() <= index) {
+      assert false;
       return Position.none();
     }
     AbstractInsnNode insn = node.instructions.get(index);
@@ -2923,7 +2929,7 @@
       LineNumberNode line = (LineNumberNode) insn;
       return getCanonicalPosition(line.line);
     }
-    return Position.none();
+    return getPreamblePosition();
   }
 
   @Override
@@ -2936,12 +2942,19 @@
         line, l -> new Position(l, null, method, callerPosition));
   }
 
+  private Position getPreamblePosition() {
+    if (preamblePosition == null) {
+      preamblePosition = Position.synthetic(0, method, null);
+    }
+    return preamblePosition;
+  }
+
   // If we need to emit a synthetic position for exceptional monitor exits, we try to cook up a
   // position that is not actually a valid program position, so as not to incorrectly position the
   // user on an exit that is not the actual exit being taken. Our heuristic for this is that if the
   // method has at least two positions we use the first position minus one as the synthetic exit.
   // If the method only has one position it is safe to just use that position.
-  private Position getSyntheticPosition() {
+  private Position getExceptionalExitPosition() {
     if (syntheticPosition == null) {
       int min = Integer.MAX_VALUE;
       int max = Integer.MIN_VALUE;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
new file mode 100644
index 0000000..a62d43f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -0,0 +1,171 @@
+// 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.ir.optimize;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeInterface;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.NonNull;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.util.IdentityHashMap;
+import java.util.ListIterator;
+import java.util.Map;
+
+public class Devirtualizer {
+
+  private final AppInfoWithLiveness appInfo;
+
+  public Devirtualizer(AppInfoWithLiveness appInfo) {
+    this.appInfo = appInfo;
+  }
+
+  public void devirtualizeInvokeInterface(
+      IRCode code, TypeEnvironment typeEnvironment, DexType invocationContext) {
+    Map<InvokeInterface, InvokeVirtual> devirtualizedCall = new IdentityHashMap<>();
+    DominatorTree dominatorTree = new DominatorTree(code);
+    Map<Value, Map<DexType, Value>> castedReceiverCache = new IdentityHashMap<>();
+
+    ListIterator<BasicBlock> blocks = code.listIterator();
+    while (blocks.hasNext()) {
+      BasicBlock block = blocks.next();
+      InstructionListIterator it = block.listIterator();
+      while (it.hasNext()) {
+        Instruction current = it.next();
+
+        // (out <-) invoke-interface rcv_i, ... I#foo
+        // ...  // could be split due to catch handlers
+        // non_null_rcv <- non-null rcv_i
+        //
+        //   ~>
+        //
+        // rcv_c <- check-cast C rcv_i
+        // (out <-) invoke-virtual rcv_c, ... C#foo
+        // ...
+        // non_null_rcv <- non-null rcv_c  // <- Update the input rcv to the non-null, too.
+        if (current.isNonNull()) {
+          NonNull nonNull = current.asNonNull();
+          Instruction origin = nonNull.origin();
+          if (origin.isInvokeInterface()
+              && devirtualizedCall.containsKey(origin.asInvokeInterface())) {
+            InvokeVirtual devirtualizedInvoke = devirtualizedCall.get(origin.asInvokeInterface());
+            if (dominatorTree.dominatedBy(block, devirtualizedInvoke.getBlock())) {
+              nonNull.src().replaceSelectiveUsers(
+                  devirtualizedInvoke.getReceiver(), ImmutableSet.of(nonNull), ImmutableSet.of());
+            }
+          }
+        }
+
+        if (!current.isInvokeInterface()) {
+          continue;
+        }
+        InvokeInterface invoke = current.asInvokeInterface();
+        DexEncodedMethod target =
+            invoke.computeSingleTarget(appInfo, typeEnvironment, invocationContext);
+        if (target == null) {
+          continue;
+        }
+        DexType holderType = target.method.getHolder();
+        DexClass holderClass = appInfo.definitionFor(holderType);
+        // Make sure we are not landing on another interface, e.g., interface's default method.
+        if (holderClass == null || holderClass.isInterface()) {
+          continue;
+        }
+        // Due to the potential downcast below, make sure the new target holder is visible.
+        Constraint visibility = Constraint.classIsVisible(invocationContext, holderType, appInfo);
+        if (visibility == Constraint.NEVER) {
+          continue;
+        }
+
+        InvokeVirtual devirtualizedInvoke =
+            new InvokeVirtual(target.method, invoke.outValue(), invoke.inValues());
+        it.replaceCurrentInstruction(devirtualizedInvoke);
+        devirtualizedCall.put(invoke, devirtualizedInvoke);
+
+        // We may need to add downcast together. E.g.,
+        // i <- check-cast I o  // suppose it is known to be of type class A, not interface I
+        // (out <-) invoke-interface i, ... I#foo
+        //
+        //  ~>
+        //
+        // i <- check-cast I o  // could be removed by {@link CodeRewriter#removeCasts}.
+        // a <- check-cast A i  // Otherwise ART verification error.
+        // (out <-) invoke-virtual a, ... A#foo
+        if (holderType != invoke.getInvokedMethod().getHolder()) {
+          Value receiver = invoke.getReceiver();
+          TypeLatticeElement receiverTypeLattice = typeEnvironment.getLatticeElement(receiver);
+          TypeLatticeElement castTypeLattice =
+              TypeLatticeElement.fromDexType(holderType, receiverTypeLattice.isNullable());
+          // Avoid adding trivial cast and up-cast.
+          // We should not use strictlyLessThan(castType, receiverType), which detects downcast,
+          // due to side-casts, e.g., A (unused) < I, B < I, and cast from A to B.
+          if (!TypeLatticeElement.lessThanOrEqual(appInfo, receiverTypeLattice, castTypeLattice)) {
+            Value newReceiver = null;
+            // If this value is ever downcast'ed to the same holder type before, and that casted
+            // value is safely accessible, i.e., the current line is dominated by that cast, use it.
+            // Otherwise, we will see something like:
+            // ...
+            // a1 <- check-cast A i
+            // invoke-virtual a1, ... A#m1 (from I#m1)
+            // ...
+            // a2 <- check-cast A i  // We should be able to reuse a1 here!
+            // invoke-virtual a2, ... A#m2 (from I#m2)
+            if (castedReceiverCache.containsKey(receiver)
+                && castedReceiverCache.get(receiver).containsKey(holderType)) {
+              Value cachedReceiver = castedReceiverCache.get(receiver).get(holderType);
+              if (dominatorTree.dominatedBy(block, cachedReceiver.definition.getBlock())) {
+                newReceiver = cachedReceiver;
+              }
+            }
+
+            // No cached, we need a new downcast'ed receiver.
+            if (newReceiver == null) {
+              newReceiver =
+                  receiver.definition != null
+                      ? code.createValue(receiver.outType(), receiver.definition.getLocalInfo())
+                      : code.createValue(receiver.outType());
+              // Cache the new receiver with a narrower type to avoid redundant checkcast.
+              castedReceiverCache.putIfAbsent(receiver, new IdentityHashMap<>());
+              castedReceiverCache.get(receiver).put(holderType, newReceiver);
+              CheckCast checkCast = new CheckCast(newReceiver, receiver, holderType);
+              checkCast.setPosition(invoke.getPosition());
+
+              // We need to add this checkcast *before* the devirtualized invoke-virtual.
+              it.previous();
+              it.add(checkCast);
+              // If the current block has catch handlers, split the new checkcast on its own block.
+              if (block.hasCatchHandlers()) {
+                // Move the cursor backward to the newly added checkcast.
+                assert it.previous() == checkCast;
+                it.split(code, 1, blocks);
+                // Update the dominator tree after the split.
+                dominatorTree = new DominatorTree(code);
+              }
+            }
+
+            receiver.replaceSelectiveUsers(
+                newReceiver, ImmutableSet.of(devirtualizedInvoke), ImmutableSet.of());
+            // TODO(b/72693244): Analyze it when creating a new Value or after replace*Users
+            typeEnvironment.enqueue(newReceiver);
+            typeEnvironment.analyze();
+          }
+        }
+      }
+    }
+    assert code.isConsistentSSA();
+  }
+
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 9f15bc2..4eae3d2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -100,7 +100,7 @@
         // ...y
         Value nonNullValue =
             code.createValue(ValueType.OBJECT, knownToBeNonNullValue.getLocalInfo());
-        NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue);
+        NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, current);
         nonNull.setPosition(current.getPosition());
         if (blockWithNonNullInstruction !=  block) {
           // If we split, add non-null IR on top of the new split block.
@@ -194,7 +194,7 @@
               if (!dominatedUsers.isEmpty() && !dominatedPhiUsers.isEmpty()) {
                 Value nonNullValue = code.createValue(
                     knownToBeNonNullValue.outType(), knownToBeNonNullValue.getLocalInfo());
-                NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue);
+                NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, theIf);
                 InstructionListIterator targetIterator = target.listIterator();
                 nonNull.setPosition(targetIterator.next().getPosition());
                 targetIterator.previous();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index e834b61..536e4b7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -62,6 +62,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.function.Consumer;
 
 public class Outliner {
 
@@ -1009,7 +1010,12 @@
     }
 
     @Override
-    public void registerReachableDefinitions(UseRegistry registry) {
+    public void registerInstructionsReferences(UseRegistry registry) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public void registerCaughtTypes(Consumer<DexType> dexTypeConsumer) {
       throw new Unreachable();
     }
 
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/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index 0de034a..26ef333 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -47,11 +48,18 @@
   }
 
   @Override
-  public void registerReachableDefinitions(UseRegistry registry) {
+  public void registerInstructionsReferences(UseRegistry registry) {
     registryCallback.accept(registry);
   }
 
   @Override
+  public void registerCaughtTypes(Consumer<DexType> dexTypeConsumer) {
+    // Support for synthesized code with catch handler is not implemented.
+    // Let's check that we're in a well known where no catch handler is possible.
+    assert sourceCode.instructionCount() == 1 || sourceCode instanceof SingleBlockSourceCode;
+  }
+
+  @Override
   protected final int computeHashCode() {
     return sourceCode.hashCode();
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
index a915849..321001e 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
@@ -38,7 +38,7 @@
     // be removed.
     if (method.accessFlags.isBridge() && !method.accessFlags.isAbstract()) {
       InvokeSingleTargetExtractor targetExtractor = new InvokeSingleTargetExtractor();
-      method.getCode().registerReachableDefinitions(targetExtractor);
+      method.getCode().registerInstructionsReferences(targetExtractor);
       DexMethod target = targetExtractor.getTarget();
       InvokeKind kind = targetExtractor.getKind();
       if (target != null && target.getArity() == method.method.getArity()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index 7f5a4f3..685e733 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -30,7 +30,7 @@
   private void identifyBridgeMethod(DexEncodedMethod method) {
     if (method.accessFlags.isBridge()) {
       InvokeSingleTargetExtractor targetExtractor = new InvokeSingleTargetExtractor();
-      method.getCode().registerReachableDefinitions(targetExtractor);
+      method.getCode().registerInstructionsReferences(targetExtractor);
       DexMethod target = targetExtractor.getTarget();
       InvokeKind kind = targetExtractor.getKind();
       if (target != null &&
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index ead6bb7..f36f8a6 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1122,7 +1122,7 @@
       if (protoLiteExtension != null && protoLiteExtension.appliesTo(method)) {
         protoLiteExtension.processMethod(method, new UseRegistry(method), protoLiteFields);
       } else {
-        method.registerReachableDefinitions(new UseRegistry(method));
+        method.registerInstructionsReferences(new UseRegistry(method));
       }
       // Add all dependent members to the workqueue.
       enqueueRootItems(rootSet.getDependentItems(method));
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index f8b54ab..de21187 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -151,7 +151,8 @@
       clazz.forEachField(field -> addMainDexType(field.field.type));
       clazz.forEachMethod(method -> {
         traceMethodDirectDependencies(method.method);
-        method.registerReachableDefinitions(codeDirectReferenceCollector);
+        method.registerInstructionsReferences(codeDirectReferenceCollector);
+        method.registerCatchedTypes(this::addMainDexType);
       });
     }
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 9cf59b3..ca62583 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -46,19 +46,28 @@
 
   private static final List<String> IGNORED_SINGLE_ARG_OPTIONS = ImmutableList
       .of("protomapping",
-          "target");
+          "target"
+      );
   private static final List<String> IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS = ImmutableList
-      .of("keepdirectories", "runtype", "laststageoutput");
+      .of("keepdirectories",
+          "runtype",
+          "laststageoutput"
+      );
   private static final List<String> IGNORED_FLAG_OPTIONS = ImmutableList
-      .of("forceprocessing", "dontusemixedcaseclassnames",
-          "dontpreverify", "experimentalshrinkunusedprotofields",
+      .of("forceprocessing",
+          "dontusemixedcaseclassnames",
+          "dontpreverify",
+          "experimentalshrinkunusedprotofields",
           "filterlibraryjarswithorginalprogramjars",
           "dontskipnonpubliclibraryclasses",
           "dontskipnonpubliclibraryclassmembers",
-          "invokebasemethod");
+          "invokebasemethod",
+          "android"
+      );
   private static final List<String> IGNORED_CLASS_DESCRIPTOR_OPTIONS = ImmutableList
       .of("isclassnamestring",
-          "whyarenotsimple");
+          "whyarenotsimple"
+      );
 
   private static final List<String> WARNED_SINGLE_ARG_OPTIONS = ImmutableList
       .of("dontnote",
@@ -66,9 +75,22 @@
           // TODO -outjars (http://b/37137994) and -adaptresourcefilecontents (http://b/37139570)
           // should be reported as errors, not just as warnings!
           "outjars",
-          "adaptresourcefilecontents");
+          "adaptresourcefilecontents"
+      );
   private static final List<String> WARNED_FLAG_OPTIONS = ImmutableList
-      .of();
+      .of(
+          // TODO(b/73707846): add support -addconfigurationdebugging
+          "addconfigurationdebugging"
+      );
+  private static final List<String> WARNED_CLASS_DESCRIPTOR_OPTIONS = ImmutableList
+      .of(
+          // TODO(b/73708157): add support -assumenoexternalsideeffects <class_spec>
+          "assumenoexternalsideeffects",
+          // TODO(b/73707404): add support -assumenoescapingparameters <class_spec>
+          "assumenoescapingparameters",
+          // TODO(b/73708085): add support -assumenoexternalreturnvalues <class_spec>
+          "assumenoexternalreturnvalues"
+      );
 
   // Those options are unsupported and are treated as compilation errors.
   // Just ignoring them would produce outputs incompatible with user expectations.
@@ -91,7 +113,7 @@
     if (configurationBuilder.isKeepParameterNames() && configurationBuilder.isObfuscating()) {
       // The flag -keepparameternames has only effect when minifying, so ignore it if we
       // are not.
-      reporter.fatalError(new StringDiagnostic(
+      throw reporter.fatalError(new StringDiagnostic(
           "-keepparameternames is not supported",
           configurationBuilder.getKeepParameterNamesOptionOrigin(),
           configurationBuilder.getKeepParameterNamesOptionPosition()));
@@ -181,14 +203,17 @@
       } else if (
           (option = Iterables.find(WARNED_SINGLE_ARG_OPTIONS,
               this::skipOptionWithSingleArg, null)) != null
-              || (option = Iterables.find(WARNED_FLAG_OPTIONS, this::skipFlag, null)) != null) {
+              || (option = Iterables.find(WARNED_FLAG_OPTIONS,
+                  this::skipFlag, null)) != null
+              || (option = Iterables.find(WARNED_CLASS_DESCRIPTOR_OPTIONS,
+                  this::skipOptionWithClassSpec, null)) != null) {
         warnIgnoringOptions(option, optionStart);
       } else if (
           (option = Iterables.find(UNSUPPORTED_FLAG_OPTIONS, this::skipFlag, null)) != null) {
         reporter.error(new StringDiagnostic(
             "Unsupported option: -" + option,
             origin,
-            getPostion(optionStart)));
+            getPosition(optionStart)));
       } else if (acceptString("renamesourcefileattribute")) {
         skipWhitespace();
         if (isOptionalArgumentGiven()) {
@@ -202,8 +227,7 @@
         ProguardKeepPackageNamesRule rule = parseKeepPackageNamesRule();
         configurationBuilder.addRule(rule);
       } else if (acceptString("keepparameternames")) {
-        configurationBuilder.setKeepParameterNames(true,
-            origin, getPostion(optionStart));
+        configurationBuilder.setKeepParameterNames(true, origin, getPosition(optionStart));
       } else if (acceptString("checkdiscard")) {
         ProguardCheckDiscardRule rule = parseCheckDiscardRule();
         configurationBuilder.addRule(rule);
@@ -220,9 +244,7 @@
         Integer expectedOptimizationPasses = acceptInteger();
         if (expectedOptimizationPasses == null) {
           throw reporter.fatalError(new StringDiagnostic(
-              "Missing n of \"-optimizationpasses n\"",
-              origin,
-              getPostion(optionStart)));
+              "Missing n of \"-optimizationpasses n\"", origin, getPosition(optionStart)));
         }
         warnIgnoringOptions("optimizationpasses", optionStart);
       } else if (acceptString("dontobfuscate")) {
@@ -249,8 +271,7 @@
         }
       } else if (acceptString("repackageclasses")) {
         if (configurationBuilder.getPackageObfuscationMode() == PackageObfuscationMode.FLATTEN) {
-          warnOverridingOptions("repackageclasses", "flattenpackagehierarchy",
-              optionStart);
+          warnOverridingOptions("repackageclasses", "flattenpackagehierarchy", optionStart);
         }
         skipWhitespace();
         if (acceptChar('\'')) {
@@ -261,8 +282,7 @@
         }
       } else if (acceptString("flattenpackagehierarchy")) {
         if (configurationBuilder.getPackageObfuscationMode() == PackageObfuscationMode.REPACKAGE) {
-          warnOverridingOptions("repackageclasses", "flattenpackagehierarchy",
-              optionStart);
+          warnOverridingOptions("repackageclasses", "flattenpackagehierarchy", optionStart);
           skipWhitespace();
           if (isOptionalArgumentGiven()) {
             skipSingleArgument();
@@ -331,15 +351,30 @@
         }
       } else if (acceptString("identifiernamestring")) {
         configurationBuilder.addRule(parseIdentifierNameStringRule());
+      } else if (acceptString("if")) {
+        // TODO(b/73708139): add support -if <class_spec>
+        ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
+        parseClassSpec(keepRuleBuilder, true);
+        ProguardKeepRule ifRule = keepRuleBuilder.build();
+        assert ifRule.getClassType() == ProguardClassType.CLASS;
+
+        // Required a subsequent keep rule.
+        skipWhitespace();
+        if (acceptString("-keep")) {
+          ProguardKeepRule subsequentRule = parseKeepRule();
+          warnIgnoringOptions("if", optionStart);
+        } else {
+          throw reporter.fatalError(new StringDiagnostic(
+              "Option -if without a subsequent keep rule.", origin, getPosition(optionStart)));
+        }
       } else {
         String unknownOption = acceptString();
-        reporter.error(new StringDiagnostic("Unknown option \"-" + unknownOption + "\"",
-            origin, getPostion(optionStart)));
+        reporter.error(new StringDiagnostic(
+            "Unknown option \"-" + unknownOption + "\"", origin, getPosition(optionStart)));
       }
       return true;
     }
 
-
     private void parseInclude() throws ProguardRuleParserException {
       TextPosition start = getPosition();
       Path included = parseFileName();
@@ -639,9 +674,8 @@
       } else if (acceptString("enum")) {
         builder.setClassType(ProguardClassType.ENUM);
       } else {
-        throw reporter
-            .fatalError(new StringDiagnostic("Expected [!]interface|@interface|class|enum",
-            origin, getPostion(start)));
+        throw reporter.fatalError(new StringDiagnostic(
+            "Expected [!]interface|@interface|class|enum", origin, getPosition(start)));
       }
     }
 
@@ -1199,28 +1233,25 @@
     private ProguardRuleParserException parseError(String message, TextPosition start,
         Throwable cause) {
       return new ProguardRuleParserException(message, snippetForPosition(start),
-          origin, getPostion(start), cause);
+          origin, getPosition(start), cause);
     }
 
     private ProguardRuleParserException parseError(String message, TextPosition start) {
       return new ProguardRuleParserException(message, snippetForPosition(start),
-          origin, getPostion(start));
+          origin, getPosition(start));
     }
 
     private void warnIgnoringOptions(String optionName, TextPosition start) {
       reporter.warning(new StringDiagnostic(
-          "Ignoring option: -" + optionName,
-          origin,
-          getPostion(start)));
+          "Ignoring option: -" + optionName, origin, getPosition(start)));
     }
 
     private void warnOverridingOptions(String optionName, String victim, TextPosition start) {
-      reporter.warning(
-          new StringDiagnostic("Option -" + optionName + " overrides -" + victim,
-              origin, getPostion(start)));
+      reporter.warning(new StringDiagnostic(
+          "Option -" + optionName + " overrides -" + victim, origin, getPosition(start)));
     }
 
-    private Position getPostion(TextPosition start) {
+    private Position getPosition(TextPosition start) {
       if (start.getOffset() == position) {
         return start;
       } else {
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
index 02159cf..63475b7 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -371,7 +371,7 @@
         return existing;
       } else if (existing.accessFlags.isBridge()) {
         InvokeSingleTargetExtractor extractor = new InvokeSingleTargetExtractor();
-        existing.getCode().registerReachableDefinitions(extractor);
+        existing.getCode().registerInstructionsReferences(extractor);
         if (extractor.getTarget() != method.method) {
           abortMerge = true;
         }
diff --git a/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLiteExtension.java b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLiteExtension.java
index 8049bf4..c2ba7e5 100644
--- a/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLiteExtension.java
+++ b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLiteExtension.java
@@ -116,13 +116,13 @@
       // serialized stream for this proto. As we mask all reads in the writing code and normally
       // remove fields that are only written but never read, we have to mark fields used in setters
       // as read and written.
-      method.registerReachableDefinitions(
+      method.registerInstructionsReferences(
           new FieldWriteImpliesReadUseRegistry(registry, method.method.holder));
     } else {
       // Filter all getters and field accesses in these methods. We do not want fields to become
       // live just due to being referenced in a special method. The pruning phase will remove
       // all references to dead fields in the code later.
-      method.registerReachableDefinitions(new FilteringUseRegistry(registry, method.method.holder,
+      method.registerInstructionsReferences(new FilteringUseRegistry(registry, method.method.holder,
           protoLiteFields));
     }
   }
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 6fc9e17..1c0face 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -68,6 +68,7 @@
     // -dontoptimize disables optimizations by flipping related flags.
     if (!proguardConfiguration.isOptimizing()) {
       enableClassMerging = false;
+      enableDevirtualization = false;
       enableNonNullTracking = false;
       enableInlining = false;
       enableSwitchMapRemoval = false;
@@ -83,6 +84,7 @@
 
   // Optimization-related flags. These should conform to -dontoptimize.
   public boolean enableClassMerging = false;
+  public boolean enableDevirtualization = true;
   public boolean enableNonNullTracking = true;
   public boolean enableInlining = true;
   public boolean enableSwitchMapRemoval = true;
@@ -489,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/examples/multidex006/ClassForMainDex.java b/src/test/examples/multidex006/ClassForMainDex.java
new file mode 100644
index 0000000..86c2e9a
--- /dev/null
+++ b/src/test/examples/multidex006/ClassForMainDex.java
@@ -0,0 +1,25 @@
+// 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 multidex006;
+
+/**
+ * Class directly referenced from Activity, will be kept in main dex. The class is not referenced
+ * by <clinit> or <init>, its direct references are not kept in main dex.
+ */
+public class ClassForMainDex {
+
+  public static void method1() {
+    try {
+      doNothing();
+    } catch (NotThrownException e) {
+      // ignore
+    }
+  }
+
+  public static void doNothing() {
+
+  }
+
+}
diff --git a/src/test/examples/multidex006/NotThrownException.java b/src/test/examples/multidex006/NotThrownException.java
new file mode 100644
index 0000000..4aa5e6c
--- /dev/null
+++ b/src/test/examples/multidex006/NotThrownException.java
@@ -0,0 +1,8 @@
+// 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 multidex006;
+
+public class NotThrownException extends RuntimeException {
+}
diff --git a/src/test/examples/multidex006/main-dex-rules-1.txt b/src/test/examples/multidex006/main-dex-rules-1.txt
new file mode 100644
index 0000000..333d866
--- /dev/null
+++ b/src/test/examples/multidex006/main-dex-rules-1.txt
@@ -0,0 +1,6 @@
+# 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.
+
+-keep public class *.ClassForMainDex {
+}
\ No newline at end of file
diff --git a/src/test/examples/multidex006/ref-list-1.txt b/src/test/examples/multidex006/ref-list-1.txt
new file mode 100644
index 0000000..b39deba
--- /dev/null
+++ b/src/test/examples/multidex006/ref-list-1.txt
@@ -0,0 +1,2 @@
+Lmultidex006/ClassForMainDex;
+Lmultidex006/NotThrownException;
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/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 77f9ad9..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;
@@ -44,6 +46,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import java.util.jar.JarOutputStream;
+import java.util.stream.Stream;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 import java.util.zip.ZipOutputStream;
@@ -481,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);
   }
@@ -557,6 +560,13 @@
     }
   }
 
+  protected Stream<Instruction> filterInstructionKind(
+      DexCode dexCode, Class<? extends Instruction> kind) {
+    return Arrays.stream(dexCode.instructions)
+        .filter(kind::isInstance)
+        .map(kind::cast);
+  }
+
   public enum MinifyMode {
     NONE,
     JAVA,
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index cc325e2..0c0cd84 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -90,7 +90,9 @@
   private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
   private static final AndroidApiLevel DEFAULT_MIN_SDK = AndroidApiLevel.I;
 
-  private static final String PROGUARD = "third_party/proguard/proguard5.2.1/bin/proguard.sh";
+  private static final String PROGUARD5_2_1 = "third_party/proguard/proguard5.2.1/bin/proguard.sh";
+  private static final String PROGUARD6_0 = "third_party/proguard/proguard6.0/bin/proguard.sh";
+  private static final String PROGUARD = PROGUARD5_2_1;
 
   public enum DexVm {
     ART_4_0_4_TARGET(Version.V4_0_4, Kind.TARGET),
@@ -947,6 +949,10 @@
     return Paths.get(System.getProperty("java.home"), "bin", "java").toString();
   }
 
+  public static ProcessResult runArtRaw(ArtCommandBuilder builder) throws IOException {
+    return runArtProcessRaw(builder);
+  }
+
   public static ProcessResult runArtRaw(String file, String mainClass)
       throws IOException {
     return runArtRaw(Collections.singletonList(file), mainClass, null);
diff --git a/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTest.java b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTest.java
new file mode 100644
index 0000000..c1d079e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTest.java
@@ -0,0 +1,13 @@
+/*
+ * 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.cf;
+
+public class SynchronizedNoopTest {
+  public static synchronized void noop() {
+    System.out.println("Foo");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
new file mode 100644
index 0000000..20acb9f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
@@ -0,0 +1,59 @@
+/*
+ * 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.cf;
+
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.JarCode;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.DexInspector;
+import java.util.ArrayList;
+import java.util.Collections;
+import org.junit.Test;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.MethodNode;
+
+public class SynchronizedNoopTestRunner {
+  private byte[] data;
+  static final Class CLASS = SynchronizedNoopTest.class;
+
+  @Test
+  public void testSynchronizedNoop() throws Exception {
+    AndroidAppConsumers a = new AndroidAppConsumers();
+    R8.run(
+        R8Command.builder()
+            .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
+            .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+            .setProgramConsumer(a.wrapClassFileConsumer(null))
+            .build());
+    DexInspector inspector = new DexInspector(a.build());
+    DexEncodedMethod method =
+        inspector.clazz(CLASS).method("void", "noop", Collections.emptyList()).getMethod();
+    ArrayList<AbstractInsnNode> insns = new ArrayList<>();
+    JarCode jarCode = method.getCode().asJarCode();
+    MethodNode node = jarCode.getNode();
+    assert node != null;
+    InsnList asmInsns = node.instructions;
+    for (int i = 0; i < asmInsns.size(); i++) {
+      insns.add(asmInsns.get(i));
+    }
+    boolean hasMonitor =
+        insns
+            .stream()
+            .anyMatch(
+                insn ->
+                    insn.getOpcode() == Opcodes.MONITORENTER
+                        || insn.getOpcode() == Opcodes.MONITOREXIT);
+    // TODO(b/73921688): Should not have monitor instruction here
+    assert hasMonitor;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestRunner.java
new file mode 100644
index 0000000..14f9f98
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestRunner.java
@@ -0,0 +1,83 @@
+// 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.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class PreamblePositionTestRunner {
+
+  private static final String TEST_CLASS = "PreamblePositionTestSource";
+  private static final String TEST_PACKAGE = "com.android.tools.r8.debuginfo";
+
+  @ClassRule public static TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Test
+  public void testBothBranchesDefaultConditionals() throws Exception {
+    testBothBranches(false);
+  }
+
+  @Test
+  public void testBothBranchesInvertConditionals() throws Exception {
+    testBothBranches(true);
+  }
+
+  private void testBothBranches(boolean invertConditionals) throws Exception {
+    Path testClassDir = temp.newFolder(TEST_PACKAGE.split(".")).toPath();
+    Path testClassPath = testClassDir.resolve(TEST_CLASS + ".class");
+    Path outputDexPath = temp.newFolder().toPath();
+
+    Files.write(testClassPath, PreamblePositionTestSourceDump.dump());
+
+    ToolHelper.runD8(
+        D8Command.builder()
+            .addProgramFiles(testClassPath)
+            .setOutput(outputDexPath, OutputMode.DexIndexed)
+            .setMode(CompilationMode.RELEASE),
+        options -> {
+          options.testing.invertConditionals = invertConditionals;
+        });
+
+    String fileName = TEST_CLASS + ".java";
+
+    for (boolean testTrueBranch : new boolean[] {false, true}) {
+      ArtCommandBuilder artCommandBuilder = new ArtCommandBuilder();
+      artCommandBuilder.appendClasspath(outputDexPath.resolve("classes.dex").toString());
+      artCommandBuilder.setMainClass(TEST_PACKAGE + "." + TEST_CLASS);
+      if (!testTrueBranch) {
+        artCommandBuilder.appendProgramArgument("1");
+      }
+
+      ProcessResult result = ToolHelper.runArtRaw(artCommandBuilder);
+
+      assertNotEquals(result.exitCode, 0);
+      if (testTrueBranch) {
+        assertTrue(result.stderr.contains("<true-branch-exception>"));
+        // Must have either explicit line = 0 or no line info at all.
+        assertTrue(
+            result.stderr.contains(fileName + ":0")
+                || (result.stderr.contains("at " + TEST_PACKAGE + "." + TEST_CLASS + ".main")
+                    && !result.stderr.contains(fileName + ":")));
+      } else {
+        assertTrue(result.stderr.contains("<false-branch-exception>"));
+        assertTrue(
+            result.stderr.contains(
+                fileName + ":" + PreamblePositionTestSourceDump.FALSE_BRANCH_LINE_NUMBER));
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestSourceDump.java b/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestSourceDump.java
new file mode 100644
index 0000000..5ed2a4e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestSourceDump.java
@@ -0,0 +1,106 @@
+// 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.*;
+
+/*
+Generated from the source code below, line numbers removed, except for the false branch,
+which is set to FALSE_BRANCH_LINE_NUMBER.
+
+    package com.android.tools.r8.debuginfo;
+
+    public class PreamblePositionTestSource {
+      public static void main(String[] args) {
+        System.err.println("<first-line>");
+        if (args.length == 0) {
+          throw new RuntimeException("<true-branch-exception>");
+        } else {
+          throw new RuntimeException("<false-branch-exception>");
+        }
+      }
+    }
+*/
+
+public class PreamblePositionTestSourceDump implements Opcodes {
+
+  static final int FALSE_BRANCH_LINE_NUMBER = 123;
+
+  public static byte[] dump() 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/PreamblePositionTestSource",
+        null,
+        "java/lang/Object",
+        null);
+
+    cw.visitSource("PreamblePositionTestSource.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/PreamblePositionTestSource;", null, l0, l1, 0);
+      mv.visitMaxs(1, 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);
+      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
+      mv.visitLdcInsn("<first-line>");
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      Label l1 = new Label();
+      mv.visitLabel(l1);
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitInsn(ARRAYLENGTH);
+      Label l2 = new Label();
+      mv.visitJumpInsn(IFNE, l2);
+      Label l3 = new Label();
+      mv.visitLabel(l3);
+      mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
+      mv.visitInsn(DUP);
+      mv.visitLdcInsn("<true-branch-exception>");
+      mv.visitMethodInsn(
+          INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V", false);
+      mv.visitInsn(ATHROW);
+      mv.visitLabel(l2);
+      mv.visitLineNumber(FALSE_BRANCH_LINE_NUMBER, l2);
+      mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
+      mv.visitInsn(DUP);
+      mv.visitLdcInsn("<false-branch-exception>");
+      mv.visitMethodInsn(
+          INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V", false);
+      mv.visitInsn(ATHROW);
+      Label l4 = new Label();
+      mv.visitLabel(l4);
+      mv.visitLocalVariable("args", "[Ljava/lang/String;", null, l0, l4, 0);
+      mv.visitMaxs(3, 1);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
new file mode 100644
index 0000000..b152308
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
@@ -0,0 +1,87 @@
+// 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.ir.optimize.devirtualize;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.code.CheckCast;
+import com.android.tools.r8.code.InvokeInterface;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.A;
+import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.A0;
+import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.A1;
+import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.I;
+import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.Main;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class InvokeInterfaceToInvokeVirtualTest extends TestBase {
+
+  private AndroidApp runR8(AndroidApp app, Class main, Path out) throws Exception {
+    R8Command command =
+        ToolHelper.addProguardConfigurationConsumer(
+            ToolHelper.prepareR8CommandBuilder(app),
+            pgConfig -> {
+              pgConfig.setPrintMapping(true);
+              pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
+            })
+        .addProguardConfiguration(
+            ImmutableList.of(keepMainProguardConfiguration(main)),
+            Origin.unknown())
+        .setOutput(out, OutputMode.DexIndexed)
+        .build();
+    return ToolHelper.runR8(command);
+  }
+
+  @Test
+  public void listOfInterface() throws Exception {
+    byte[][] classes = {
+        ToolHelper.getClassAsBytes(I.class),
+        ToolHelper.getClassAsBytes(A.class),
+        ToolHelper.getClassAsBytes(A0.class),
+        ToolHelper.getClassAsBytes(A1.class),
+        ToolHelper.getClassAsBytes(Main.class)
+    };
+    String main = Main.class.getCanonicalName();
+    ProcessResult javaOutput = runOnJava(main, classes);
+    assertEquals(0, javaOutput.exitCode);
+
+    AndroidApp originalApp = buildAndroidApp(classes);
+    Path out = temp.getRoot().toPath();
+    AndroidApp processedApp = runR8(originalApp, Main.class, out);
+
+    DexInspector dexInspector = new DexInspector(processedApp);
+    ClassSubject clazz = dexInspector.clazz(main);
+    DexEncodedMethod m = clazz.method(DexInspector.MAIN).getMethod();
+    DexCode code = m.getCode().asDexCode();
+    long numOfInvokeInterface = filterInstructionKind(code, InvokeInterface.class).count();
+    // List#add, List#get
+    assertEquals(2, numOfInvokeInterface);
+    long numOfInvokeVirtual = filterInstructionKind(code, InvokeVirtual.class).count();
+    // System.out.println, I#get ~> A0#get
+    assertEquals(2, numOfInvokeVirtual);
+    long numOfCast = filterInstructionKind(code, CheckCast.class).count();
+    // check-cast I ~> check-cast A0
+    assertEquals(1, numOfCast);
+
+    ProcessResult artOutput = runOnArtRaw(processedApp, main);
+    assertEquals(0, artOutput.exitCode);
+    assertEquals(javaOutput.stdout.trim(), artOutput.stdout.trim());
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A.java
new file mode 100644
index 0000000..c767950
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A.java
@@ -0,0 +1,7 @@
+// 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.ir.optimize.devirtualize.invokeinterface;
+
+public abstract class A implements I {
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A0.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A0.java
new file mode 100644
index 0000000..67de6f1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A0.java
@@ -0,0 +1,11 @@
+// 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.ir.optimize.devirtualize.invokeinterface;
+
+public class A0 extends A {
+  @Override
+  public int get() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A1.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A1.java
new file mode 100644
index 0000000..10f80cf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A1.java
@@ -0,0 +1,11 @@
+// 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.ir.optimize.devirtualize.invokeinterface;
+
+public class A1 extends A {
+  @Override
+  public int get() {
+    return 1;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/I.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/I.java
new file mode 100644
index 0000000..ee0a0a9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/I.java
@@ -0,0 +1,8 @@
+// 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.ir.optimize.devirtualize.invokeinterface;
+
+public interface I {
+  int get();
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/Main.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/Main.java
new file mode 100644
index 0000000..570122d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/Main.java
@@ -0,0 +1,25 @@
+// 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.ir.optimize.devirtualize.invokeinterface;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Main {
+  private static final int COUNT = 8;
+
+  public static void main(String[] args) {
+    I instance = new A0();
+    List<I> l = new ArrayList<>();
+    for (int i = 0; i < COUNT; i++) {
+      l.add(instance);
+    }
+
+    int sum = 0;
+    for (int i = 0; i < COUNT; i++) {
+      sum += l.get(i).get();
+    }
+    System.out.println(sum);
+  }
+}
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/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index 36ad4c9..2e8645c 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -124,6 +124,17 @@
     doTest5(6);
   }
 
+  @Test
+  public void traceMainDexList006() throws Throwable {
+    doTest(
+        "traceMainDexList006",
+        "multidex006",
+        EXAMPLE_BUILD_DIR,
+        Paths.get(EXAMPLE_SRC_DIR, "multidex006", "main-dex-rules-1.txt"),
+        Paths.get(EXAMPLE_SRC_DIR, "multidex006", "ref-list-1.txt"),
+        AndroidApiLevel.I);
+  }
+
   private void doTest5(int variant) throws Throwable {
     doTest(
         "traceMainDexList003",
@@ -229,9 +240,15 @@
       int nonLambdaOffset = 0;
       for (int i = 0; i < refList.length; i++) {
         String reference = refList[i].trim();
+        if (r8MainDexList.size() <= i) {
+          Assert.fail("R8 main dex list is missing '" + reference + "'");
+        }
         checkSameMainDexEntry(reference, r8MainDexList.get(i));
         // The main dex list generator does not do any lambda desugaring.
         if (!isLambda(reference)) {
+          if (mainDexGeneratorMainDexList.size() <= i - nonLambdaOffset) {
+            Assert.fail("Main dex list generator is missing '" + reference + "'");
+          }
           checkSameMainDexEntry(reference, mainDexGeneratorMainDexList.get(i - nonLambdaOffset));
           checkSameMainDexEntry(
               reference, mainDexGeneratorMainDexListFromConsumer.get(i - nonLambdaOffset));
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 183c458..00fa060 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -157,6 +157,7 @@
     // Parse from file.
     parser = new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(PROGUARD_SPEC_FILE));
+    verifyParserEndsCleanly();
     List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
     assertEquals(24, rules.size());
     assertEquals(1, rules.get(0).getMemberRules().size());
@@ -175,6 +176,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(MULTIPLE_NAME_PATTERNS_FILE));
+    verifyParserEndsCleanly();
     List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
     assertEquals(1, rules.size());
     ProguardConfigurationRule rule = rules.get(0);
@@ -204,6 +206,7 @@
             "  p-.-OtherNameWithDash- -method-(-p.-WithDash-, -package-.-ClassNameWithDash-[]); ",
             "}"));
     parser.parse(createConfigurationForTesting(ImmutableList.of(nonJavaIdentifiers)));
+    verifyParserEndsCleanly();
     List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
     assertEquals(1, rules.size());
     assertEquals(ProguardClassType.CLASS, rules.get(0).getClassType());
@@ -239,6 +242,7 @@
         new ProguardConfigurationParser(dexItemFactory, reporter);
     String dontwarn = "-dontwarn !foobar,*bar";
     parser.parse(createConfigurationForTesting(ImmutableList.of(dontwarn)));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertFalse(
         config.getDontWarnPatterns().matches(dexItemFactory.createType("Lboobaz;")));
@@ -257,6 +261,7 @@
     List<String> configuration2 = ImmutableList.of("-dontwarn foo.**", "-dontwarn bar.**");
     for (List<String> configuration : ImmutableList.of(configuration1, configuration2)) {
       parser.parse(createConfigurationForTesting(configuration));
+      verifyParserEndsCleanly();
       ProguardConfiguration config = parser.getConfig();
       assertTrue(
           config.getDontWarnPatterns().matches(dexItemFactory.createType("Lfoo/Bar;")));
@@ -274,6 +279,7 @@
         new ProguardConfigurationParser(dexItemFactory, reporter);
     String dontwarnAll = "-dontwarn *";
     parser.parse(createConfigurationForTesting(ImmutableList.of(dontwarnAll)));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertTrue(
         config.getDontWarnPatterns().matches(dexItemFactory.createType("Lboobaz;")));
@@ -305,6 +311,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(ACCESS_FLAGS_FILE));
+    verifyParserEndsCleanly();
     List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
     assertEquals(1, rules.size());
     ProguardConfigurationRule rule = rules.get(0);
@@ -350,6 +357,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(WHY_ARE_YOU_KEEPING_FILE));
+    verifyParserEndsCleanly();
     List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
     assertEquals(1, rules.size());
     ProguardConfigurationRule rule = rules.get(0);
@@ -364,6 +372,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(ASSUME_NO_SIDE_EFFECTS));
+    verifyParserEndsCleanly();
     List<ProguardConfigurationRule> assumeNoSideEffects = parser.getConfig().getRules();
     assertEquals(1, assumeNoSideEffects.size());
     assumeNoSideEffects.get(0).getMemberRules().forEach(rule -> {
@@ -376,6 +385,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(ASSUME_NO_SIDE_EFFECTS_WITH_RETURN_VALUE));
+    verifyParserEndsCleanly();
     List<ProguardConfigurationRule> assumeNoSideEffects = parser.getConfig().getRules();
     assertEquals(1, assumeNoSideEffects.size());
     int matches = 0;
@@ -432,6 +442,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(ASSUME_VALUES_WITH_RETURN_VALUE));
+    verifyParserEndsCleanly();
     List<ProguardConfigurationRule> assumeValues = parser.getConfig().getRules();
     assertEquals(1, assumeValues.size());
     int matches = 0;
@@ -490,6 +501,7 @@
         new ProguardConfigurationParser(dexItemFactory, reporter);
     String adaptClassStrings = "-adaptclassstrings !foobar,*bar";
     parser.parse(createConfigurationForTesting(ImmutableList.of(adaptClassStrings)));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertFalse(
         config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
@@ -509,6 +521,7 @@
         ImmutableList.of("-adaptclassstrings foo.**", "-adaptclassstrings bar.**");
     for (List<String> configuration : ImmutableList.of(configuration1, configuration2)) {
       parser.parse(createConfigurationForTesting(configuration));
+      verifyParserEndsCleanly();
       ProguardConfiguration config = parser.getConfig();
       assertTrue(
           config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoo/Bar;")));
@@ -526,6 +539,7 @@
         new ProguardConfigurationParser(dexItemFactory, reporter);
     String adaptAll = "-adaptclassstrings *";
     parser.parse(createConfigurationForTesting(ImmutableList.of(adaptAll)));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertTrue(
         config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
@@ -542,6 +556,7 @@
         new ProguardConfigurationParser(dexItemFactory, reporter);
     String adaptAll = "-adaptclassstrings";
     parser.parse(createConfigurationForTesting(ImmutableList.of(adaptAll)));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertTrue(
         config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
@@ -568,6 +583,7 @@
         + "  @my.annotations.IdentifierNameString *;\n"
         + "}";
     parser.parse(createConfigurationForTesting(ImmutableList.of(config1, config2, config3)));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     List<ProguardConfigurationRule> identifierNameStrings = config.getRules();
     assertEquals(3, identifierNameStrings.size());
@@ -600,6 +616,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(DONT_OBFUSCATE));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertFalse(config.isObfuscating());
   }
@@ -609,6 +626,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(PACKAGE_OBFUSCATION_1));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
     assertNotNull(config.getPackagePrefix());
@@ -620,6 +638,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(PACKAGE_OBFUSCATION_2));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
     assertNotNull(config.getPackagePrefix());
@@ -631,6 +650,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(PACKAGE_OBFUSCATION_3));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertEquals(PackageObfuscationMode.FLATTEN, config.getPackageObfuscationMode());
     assertNotNull(config.getPackagePrefix());
@@ -642,6 +662,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(PACKAGE_OBFUSCATION_4));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertEquals(PackageObfuscationMode.FLATTEN, config.getPackageObfuscationMode());
     assertNotNull(config.getPackagePrefix());
@@ -649,11 +670,13 @@
   }
 
   @Test
-  public void flattenPackageHierarchyCannotOverrideRepackageClasses()
-      throws Exception {
+  public void flattenPackageHierarchyCannotOverrideRepackageClasses() throws Exception {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
-    parser.parse(Paths.get(PACKAGE_OBFUSCATION_5));
+    Path path = Paths.get(PACKAGE_OBFUSCATION_5);
+    parser.parse(path);
+    checkDiagnostic(handler.warnings, path, 6, 1,
+        "repackageclasses", "overrides", "flattenpackagehierarchy");
     ProguardConfiguration config = parser.getConfig();
     assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
     assertNotNull(config.getPackagePrefix());
@@ -661,11 +684,13 @@
   }
 
   @Test
-  public void repackageClassesOverridesFlattenPackageHierarchy()
-      throws Exception {
+  public void repackageClassesOverridesFlattenPackageHierarchy() throws Exception {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
-    parser.parse(Paths.get(PACKAGE_OBFUSCATION_6));
+    Path path = Paths.get(PACKAGE_OBFUSCATION_6);
+    parser.parse(path);
+    checkDiagnostic(handler.warnings, path, 6, 1,
+        "repackageclasses", "overrides", "flattenpackagehierarchy");
     ProguardConfiguration config = parser.getConfig();
     assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
     assertNotNull(config.getPackagePrefix());
@@ -677,6 +702,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(APPLY_MAPPING));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertTrue(config.hasApplyMappingFile());
   }
@@ -696,8 +722,10 @@
 
   @Test
   public void parseIncluding() throws Exception {
-    new ProguardConfigurationParser(new DexItemFactory(), reporter)
-        .parse(Paths.get(INCLUDING));
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(Paths.get(INCLUDING));
+    verifyParserEndsCleanly();
   }
 
   @Test
@@ -743,11 +771,10 @@
           new ProguardConfigurationParser(new DexItemFactory(), reporter);
       parser.parse(createConfigurationForTesting(
           Collections.singletonList("-injars abc.jar(*.zip;*.class)")));
+      fail();
     } catch (AbortException e) {
       assertEquals(1, handler.errors.size());
-      return;
     }
-    fail();
   }
 
   @Test
@@ -755,6 +782,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(SEEDS));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertTrue(config.isPrintSeeds());
     assertNull(config.getSeedFile());
@@ -765,6 +793,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(SEEDS_2));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertTrue(config.isPrintSeeds());
     assertNotNull(config.getSeedFile());
@@ -775,6 +804,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(VERBOSE));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertTrue(config.isVerbose());
   }
@@ -784,6 +814,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(KEEPDIRECTORIES));
+    verifyParserEndsCleanly();
   }
 
   @Test
@@ -791,6 +822,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(DONT_SHRINK));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertFalse(config.isShrinking());
   }
@@ -800,6 +832,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES));
+    verifyParserEndsCleanly();
   }
 
   @Test
@@ -807,6 +840,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS));
+    verifyParserEndsCleanly();
   }
 
   @Test
@@ -815,8 +849,7 @@
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     Path source = Paths.get(IDENTIFIER_NAME_STRING);
     parser.parse(source);
-    assertEquals(0, handler.infos.size());
-    assertEquals(0, handler.warnings.size());
+    verifyParserEndsCleanly();
   }
 
   @Test
@@ -824,6 +857,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(OVERLOAD_AGGRESIVELY));
+    verifyParserEndsCleanly();
   }
 
   @Test
@@ -832,21 +866,30 @@
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(DONT_OPTIMIZE));
     ProguardConfiguration config = parser.getConfig();
+    verifyParserEndsCleanly();
+    assertFalse(config.isOptimizing());
   }
 
   @Test
   public void parseDontOptimizeOverridesPasses() throws Exception {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
-    parser.parse(Paths.get(DONT_OPTIMIZE_OVERRIDES_PASSES));
+    Path path = Paths.get(DONT_OPTIMIZE_OVERRIDES_PASSES);
+    parser.parse(path);
+    checkDiagnostic(handler.warnings, path, 7, 1,
+        "Ignoring", "-optimizationpasses");
     ProguardConfiguration config = parser.getConfig();
+    assertFalse(config.isOptimizing());
   }
 
   @Test
   public void parseOptimizationPasses() throws Exception {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
-    parser.parse(Paths.get(OPTIMIZATION_PASSES));
+    Path path = Paths.get(OPTIMIZATION_PASSES);
+    parser.parse(path);
+    checkDiagnostic(handler.warnings, path, 5, 1,
+        "Ignoring", "-optimizationpasses");
     ProguardConfiguration config = parser.getConfig();
   }
 
@@ -882,6 +925,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(PARSE_AND_SKIP_SINGLE_ARGUMENT));
+    verifyParserEndsCleanly();
   }
 
   @Test
@@ -889,6 +933,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(PRINT_USAGE));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertTrue(config.isPrintUsage());
     assertNull(config.getPrintUsageFile());
@@ -899,6 +944,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(PRINT_USAGE_TO_FILE));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertTrue(config.isPrintUsage());
     assertNotNull(config.getPrintUsageFile());
@@ -909,6 +955,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(TARGET));
+    verifyParserEndsCleanly();
   }
 
   @Test
@@ -938,6 +985,7 @@
         "-laststageoutput /some/file/name  "
     );
     parser.parse(proguardConfig);
+    verifyParserEndsCleanly();
   }
 
   @Test
@@ -947,6 +995,7 @@
     String config1 = "-renamesourcefileattribute PG\n";
     String config2 = "-keepattributes SourceFile,SourceDir\n";
     parser.parse(createConfigurationForTesting(ImmutableList.of(config1, config2)));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertEquals("PG", config.getRenameSourceFileAttribute());
     assertTrue(config.getKeepAttributes().sourceFile);
@@ -969,6 +1018,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(createConfigurationForTesting(ImmutableList.of(config)));
+    verifyParserEndsCleanly();
     assertEquals(
         ProguardKeepAttributes.fromPatterns(expected),
         parser.getConfigRawForTesting().getKeepAttributes());
@@ -1011,6 +1061,7 @@
     parser.parse(createConfigurationForTesting(ImmutableList.of(
         "-useuniqueclassmembernames"
     )));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertTrue(config.isUseUniqueClassMemberNames());
   }
@@ -1039,6 +1090,7 @@
         "-keepparameternames",
         "-dontobfuscate"
     )));
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertTrue(config.isKeepParameterNames());
 
@@ -1046,9 +1098,11 @@
     parser.parse(createConfigurationForTesting(ImmutableList.of(
         "-keepparameternames"
     )));
+    verifyParserEndsCleanly();
     parser.parse(createConfigurationForTesting(ImmutableList.of(
         "-dontobfuscate"
     )));
+    verifyParserEndsCleanly();
     config = parser.getConfig();
     assertTrue(config.isKeepParameterNames());
   }
@@ -1059,12 +1113,11 @@
       ProguardConfigurationParser parser =
           new ProguardConfigurationParser(new DexItemFactory(), reporter);
       parser.parse(createConfigurationForTesting(Collections.singletonList("-")));
+      fail();
     } catch (AbortException e) {
       assertEquals(1, handler.errors.size());
       assertTrue(handler.errors.get(0).getDiagnosticMessage().contains("-"));
-      return;
     }
-    fail();
   }
 
   @Test
@@ -1073,12 +1126,160 @@
       ProguardConfigurationParser parser =
           new ProguardConfigurationParser(new DexItemFactory(), reporter);
       parser.parse(createConfigurationForTesting(Collections.singletonList("--no-locals")));
+      fail();
     } catch (AbortException e) {
+
       assertEquals(1, handler.errors.size());
       assertTrue(handler.errors.get(0).getDiagnosticMessage().contains("--no-locals"));
-      return;
     }
-    fail();
+  }
+
+  @Test
+  public void parse_if() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-if   class **$$ModuleAdapter",
+        "-keep class A",
+        "-if   class **$$InjectAdapter",
+        "-keep class B",
+        "-if   class **$$StaticInjection",
+        "-keep class C",
+        "-keepnames class dagger.Lazy"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    assertEquals(3, handler.warnings.size());
+    for (int i = 0; i < 3; i++) {
+      assertTrue(handler.warnings.get(i).getDiagnosticMessage().contains("Ignoring option: -if"));
+    }
+  }
+
+  @Test
+  public void parse_if_if() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-if   class **$$ModuleAdapter",
+        "-if   class **$$InjectAdapter"
+    );
+    try {
+      ProguardConfigurationParser parser =
+          new ProguardConfigurationParser(new DexItemFactory(), reporter);
+      parser.parse(proguardConfig);
+      fail();
+    } catch (AbortException e) {
+      checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+          "without", "subsequent", "keep");
+    }
+  }
+
+  @Test
+  public void parse_if_end() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-if   class **$$ModuleAdapter"
+    );
+    try {
+      ProguardConfigurationParser parser =
+          new ProguardConfigurationParser(new DexItemFactory(), reporter);
+      parser.parse(proguardConfig);
+      fail();
+    } catch (AbortException e) {
+      checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+          "without", "subsequent", "keep");
+    }
+  }
+
+  @Test
+  public void parse_assumenoexternalsideeffects() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-assumenoexternalsideeffects class java.lang.StringBuilder {",
+        "  public java.lang.StringBuilder();",
+        "  public java.lang.StringBuilder(int);",
+        "  public java.lang.StringBuilder(java.lang.String);",
+        "  public java.lang.StringBuilder append(java.lang.Object);",
+        "  public java.lang.StringBuilder append(java.lang.String);",
+        "  public java.lang.StringBuilder append(java.lang.StringBuffer);",
+        "  public java.lang.StringBuilder append(char[]);",
+        "  public java.lang.StringBuilder append(char[], int, int);",
+        "  public java.lang.StringBuilder append(boolean);",
+        "  public java.lang.StringBuilder append(char);",
+        "  public java.lang.StringBuilder append(int);",
+        "  public java.lang.StringBuilder append(long);",
+        "  public java.lang.StringBuilder append(float);",
+        "  public java.lang.StringBuilder append(double);",
+        "  public java.lang.String toString();",
+        "}"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+        "Ignoring", "-assumenoexternalsideeffects");
+  }
+
+  @Test
+  public void parse_assumenoescapingparameters() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-assumenoescapingparameters class java.lang.System {",
+        "  public static void arraycopy(java.lang.Object, int, java.lang.Object, int, int);",
+        "}"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+        "Ignoring", "-assumenoescapingparameters");
+  }
+
+  @Test
+  public void parse_assumenoexternalreturnvalues() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-assumenoexternalreturnvalues class java.lang.StringBuilder {",
+        "  public java.lang.StringBuilder append(java.lang.Object);",
+        "  public java.lang.StringBuilder append(java.lang.String);",
+        "  public java.lang.StringBuilder append(java.lang.StringBuffer);",
+        "  public java.lang.StringBuilder append(char[]);",
+        "  public java.lang.StringBuilder append(char[], int, int);",
+        "  public java.lang.StringBuilder append(boolean);",
+        "  public java.lang.StringBuilder append(char);",
+        "  public java.lang.StringBuilder append(int);",
+        "  public java.lang.StringBuilder append(long);",
+        "  public java.lang.StringBuilder append(float);",
+        "  public java.lang.StringBuilder append(double);",
+        "}"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+        "Ignoring", "-assumenoexternalreturnvalues");
+  }
+
+  @Test
+  public void parse_android() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-android"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    verifyParserEndsCleanly();
+  }
+
+  @Test
+  public void parse_addconfigurationdebugging() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-addconfigurationdebugging"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+        "Ignoring", "-addconfigurationdebugging");
+  }
+
+  private void verifyParserEndsCleanly() {
+    assertEquals(0, handler.infos.size());
+    assertEquals(0, handler.warnings.size());
+    assertEquals(0, handler.errors.size());
   }
 
   private Diagnostic checkDiagnostic(List<Diagnostic> diagnostics, Path path, int lineStart,
@@ -1094,8 +1295,8 @@
     }
     assertEquals(lineStart, position.getLine());
     assertEquals(columnStart, position.getColumn());
-    for (String part:messageParts) {
-      assertTrue(diagnostic.getDiagnosticMessage()+ "doesn't contain \"" + part + "\"",
+    for (String part : messageParts) {
+      assertTrue(diagnostic.getDiagnosticMessage()+ " doesn't contain \"" + part + "\"",
           diagnostic.getDiagnosticMessage().contains(part));
     }
     return diagnostic;
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 085e46c..cfbfa84 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -296,9 +296,12 @@
           ImmutableList.of(mainClass, forNameClass1, forNameClass2)),
           proguardedJar, proguardConfigFile);
       Set<String> classesAfterProguard = readClassesInJar(proguardedJar);
+      assertEquals(3, classesAfterProguard.size());
       assertTrue(classesAfterProguard.contains(mainClass.getCanonicalName()));
-      assertTrue(classesAfterProguard.contains(forNameClass1.getCanonicalName()));
-      assertTrue(classesAfterProguard.contains(forNameClass2.getCanonicalName()));
+      if (!allowObfuscation) {
+        assertTrue(classesAfterProguard.contains(forNameClass1.getCanonicalName()));
+        assertTrue(classesAfterProguard.contains(forNameClass2.getCanonicalName()));
+      }
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
index 16545bd..ca3fd2a 100644
--- a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
@@ -11,18 +11,15 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.DexInspector;
 import com.google.common.collect.ImmutableList;
-import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
 import org.junit.Test;
 
 public class IncludeDescriptorClassesTest extends TestBase {
+  // Actually running Proguard should only be during development.
+  private final boolean RUN_PROGUARD = false;
 
   private class Result {
     final DexInspector inspector;
@@ -75,8 +72,8 @@
 
     Set<String> classesAfterProguard = null;
     // Actually running Proguard should only be during development.
-    if (false) {
-      Path proguardedJar = temp.newFile("proguarded.jar").toPath();
+    if (RUN_PROGUARD) {
+      Path proguardedJar = temp.newFolder().toPath().resolve("proguarded.jar");
       ToolHelper.runProguard(jarTestClasses(classes), proguardedJar, proguardConfig);
       classesAfterProguard = readClassesInJar(proguardedJar);
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
index 9affccf..8e39b67 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.InvokeInterface;
 import com.android.tools.r8.code.InvokeVirtual;
 import com.android.tools.r8.graph.DexCode;
@@ -24,7 +25,6 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.Arrays;
 import java.util.List;
 import org.junit.Test;
 
@@ -50,58 +50,51 @@
     );
     builder.addProguardConfiguration(additionalKeepRules, Origin.unknown());
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    AndroidApp app = ToolHelper.runR8(builder.build());
+    AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableDevirtualization = false);
     inspection.accept(new DexInspector(app));
     assertEquals(expectedResult, runOnArt(app, mainClass));
   }
 
-  private int countInvokeInterfaceInX(DexInspector inspector) {
+  private int countInstructionInX(DexInspector inspector, Class<? extends Instruction> invoke) {
     MethodSignature signatureForX =
         new MethodSignature("x", "void", ImmutableList.of(BaseInterface.class.getCanonicalName()));
     DexCode x = inspector.clazz(Main.class).method(signatureForX).getMethod().getCode().asDexCode();
-    return (int) Arrays.stream(x.instructions)
-        .filter(instruction -> instruction instanceof InvokeInterface)
-        .count();
+    return (int) filterInstructionKind(x, invoke).count();
   }
 
-  private int countInvokeInterfaceInY(DexInspector inspector) {
+  private int countInstructionInY(DexInspector inspector, Class<? extends Instruction> invoke) {
     MethodSignature signatureForY =
         new MethodSignature("y", "void", ImmutableList.of(SubInterface.class.getCanonicalName()));
     DexCode y = inspector.clazz(Main.class).method(signatureForY).getMethod().getCode().asDexCode();
-    return (int) Arrays.stream(y.instructions)
-        .filter(instruction -> instruction instanceof InvokeInterface)
-        .map(instruction -> (InvokeInterface) instruction)
+    return (int) filterInstructionKind(y, invoke)
         .filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
         .count();
   }
 
-  private int countInvokeVirtualInZ(DexInspector inspector) {
+  private int countInstructionInZ(DexInspector inspector, Class<? extends Instruction> invoke) {
     MethodSignature signatureForZ =
         new MethodSignature("z", "void", ImmutableList.of(TestClass.class.getCanonicalName()));
     DexCode z = inspector.clazz(Main.class).method(signatureForZ).getMethod().getCode().asDexCode();
-    return (int) Arrays.stream(z.instructions)
-        .filter(instruction -> instruction instanceof InvokeVirtual)
-        .map(instruction -> (InvokeVirtual) instruction)
+    return (int) filterInstructionKind(z, invoke)
         .filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
         .count();
   }
 
-  private int countInvokeVirtualInZSubClass(DexInspector inspector) {
+  private int countInstructionInZSubClass(
+      DexInspector inspector, Class<? extends Instruction> invoke) {
     MethodSignature signatureForZ =
         new MethodSignature("z", "void", ImmutableList.of(SubClass.class.getCanonicalName()));
     DexCode z = inspector.clazz(Main.class).method(signatureForZ).getMethod().getCode().asDexCode();
-    return (int) Arrays.stream(z.instructions)
-        .filter(instruction -> instruction instanceof InvokeVirtual)
-        .map(instruction -> (InvokeVirtual) instruction)
+    return (int) filterInstructionKind(z, invoke)
         .filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
         .count();
   }
 
   private void noInterfaceKept(DexInspector inspector) {
     // Indirectly assert that method is inlined into x, y and z.
-    assertEquals(1, countInvokeInterfaceInX(inspector));
-    assertEquals(1, countInvokeInterfaceInY(inspector));
-    assertEquals(1, countInvokeVirtualInZ(inspector));
+    assertEquals(1, countInstructionInX(inspector, InvokeInterface.class));
+    assertEquals(1, countInstructionInY(inspector, InvokeInterface.class));
+    assertEquals(1, countInstructionInZ(inspector, InvokeVirtual.class));
   }
 
   @Test
@@ -113,11 +106,11 @@
 
   private void baseInterfaceKept(DexInspector inspector) {
     // Indirectly assert that method is not inlined into x.
-    assertEquals(3, countInvokeInterfaceInX(inspector));
+    assertEquals(3, countInstructionInX(inspector, InvokeInterface.class));
     // Indirectly assert that method is inlined into y and z.
-    assertEquals(1, countInvokeInterfaceInY(inspector));
-    assertEquals(1, countInvokeVirtualInZ(inspector));
-    assertEquals(1, countInvokeVirtualInZSubClass(inspector));
+    assertEquals(1, countInstructionInY(inspector, InvokeInterface.class));
+    assertEquals(1, countInstructionInZ(inspector, InvokeVirtual.class));
+    assertEquals(1, countInstructionInZSubClass(inspector, InvokeVirtual.class));
   }
 
   @Test
@@ -133,11 +126,11 @@
 
   private void subInterfaceKept(DexInspector inspector) {
     // Indirectly assert that method is not inlined into x or y.
-    assertEquals(3, countInvokeInterfaceInX(inspector));
-    assertEquals(3, countInvokeInterfaceInY(inspector));
+    assertEquals(3, countInstructionInX(inspector, InvokeInterface.class));
+    assertEquals(3, countInstructionInY(inspector, InvokeInterface.class));
     // Indirectly assert that method is inlined into z.
-    assertEquals(1, countInvokeVirtualInZ(inspector));
-    assertEquals(1, countInvokeVirtualInZSubClass(inspector));
+    assertEquals(1, countInstructionInZ(inspector, InvokeVirtual.class));
+    assertEquals(1, countInstructionInZSubClass(inspector, InvokeVirtual.class));
   }
 
   @Test
@@ -155,10 +148,10 @@
 
   private void classKept(DexInspector inspector) {
     // Indirectly assert that method is not inlined into x, y or z.
-    assertEquals(3, countInvokeInterfaceInX(inspector));
-    assertEquals(3, countInvokeInterfaceInY(inspector));
-    assertEquals(3, countInvokeVirtualInZ(inspector));
-    assertEquals(3, countInvokeVirtualInZSubClass(inspector));
+    assertEquals(3, countInstructionInX(inspector, InvokeInterface.class));
+    assertEquals(3, countInstructionInY(inspector, InvokeInterface.class));
+    assertEquals(3, countInstructionInZ(inspector, InvokeVirtual.class));
+    assertEquals(3, countInstructionInZSubClass(inspector, InvokeVirtual.class));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 64ea9b4..5088aeb 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.code.ConstWideHigh16;
 import com.android.tools.r8.code.DivInt;
 import com.android.tools.r8.code.DivInt2Addr;
+import com.android.tools.r8.code.Goto;
 import com.android.tools.r8.code.InvokeStatic;
 import com.android.tools.r8.code.InvokeVirtual;
 import com.android.tools.r8.code.MoveResult;
@@ -1244,7 +1245,7 @@
     assertTrue(code.instructions[1] instanceof InvokeStatic);
     assertTrue(code.instructions[2] instanceof MoveResult);
     assertTrue(code.instructions[3] instanceof DivInt2Addr);
-    assertTrue(code.instructions[4] instanceof Return);
+    assertTrue(code.instructions[4] instanceof Goto);
     assertTrue(code.instructions[5] instanceof Const4);
     assertTrue(code.instructions[6] instanceof Return);
     InvokeStatic invoke = (InvokeStatic) code.instructions[1];
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
diff --git a/third_party/proguard/README.google b/third_party/proguard/README.google
index 4dc91ad..4f85601 100644
--- a/third_party/proguard/README.google
+++ b/third_party/proguard/README.google
@@ -1,7 +1,8 @@
 URL: https://sourceforge.net/projects/proguard/files/proguard/5.2/
-Version: 5.2.1
+URL: https://sourceforge.net/projects/proguard/files/proguard/6.0/
+Version: 5.2.1, 6.0
 License: GPL
-License File: docs/license.html
+License File: proguard5.2.1/docs/license.html, proguard6.0/docs/license.html
 
 Description:
 ProGuard Java Optimizer and Obfuscator
diff --git a/third_party/proguard/proguard6.0.tar.gz.sha1 b/third_party/proguard/proguard6.0.tar.gz.sha1
new file mode 100644
index 0000000..4596bc8
--- /dev/null
+++ b/third_party/proguard/proguard6.0.tar.gz.sha1
@@ -0,0 +1 @@
+57d0702f38196c81ff506d2e34a4a5569c3af583
\ No newline at end of file