Merge "Add tests for -if rules with <n> wildcards."
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 28bf331..39d916a 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -9,6 +9,8 @@
 import com.android.tools.r8.cf.code.CfBinop;
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstMethodHandle;
+import com.android.tools.r8.cf.code.CfConstMethodType;
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
@@ -20,6 +22,7 @@
 import com.android.tools.r8.cf.code.CfInstanceOf;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfMonitor;
@@ -226,6 +229,13 @@
     appendMethod(invoke.getMethod());
   }
 
+  public void print(CfInvokeDynamic invoke) {
+    indent();
+    builder.append(opcodeName(Opcodes.INVOKEDYNAMIC)).append(' ');
+    builder.append(invoke.getCallSite().methodName);
+    builder.append(invoke.getCallSite().methodProto.toDescriptorString());
+  }
+
   public void print(CfFrame frame) {
     StringBuilder builder = new StringBuilder("frame: [");
     String separator = "";
@@ -442,6 +452,18 @@
     }
   }
 
+  public void print(CfConstMethodHandle handle) {
+    indent();
+    builder.append("ldc ");
+    builder.append(handle.getHandle().toString());
+  }
+
+  public void print(CfConstMethodType type) {
+    indent();
+    builder.append("ldc ");
+    builder.append(type.getType().toString());
+  }
+
   private String getLabel(CfLabel label) {
     return labels != null ? labels.get(label) : "L?";
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
new file mode 100644
index 0000000..b63b4d5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -0,0 +1,31 @@
+// 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.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.DexMethodHandle;
+import org.objectweb.asm.MethodVisitor;
+
+public class CfConstMethodHandle extends CfInstruction {
+
+  private DexMethodHandle handle;
+
+  public CfConstMethodHandle(DexMethodHandle handle) {
+    this.handle = handle;
+  }
+
+  public DexMethodHandle getHandle() {
+    return handle;
+  }
+
+  @Override
+  public void write(MethodVisitor visitor) {
+    visitor.visitLdcInsn(handle.toAsmHandle());
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
new file mode 100644
index 0000000..8ec1545
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -0,0 +1,32 @@
+// 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.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.DexProto;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
+
+public class CfConstMethodType extends CfInstruction {
+
+  private DexProto type;
+
+  public CfConstMethodType(DexProto type) {
+    this.type = type;
+  }
+
+  public DexProto getType() {
+    return type;
+  }
+
+  @Override
+  public void write(MethodVisitor visitor) {
+    visitor.visitLdcInsn(Type.getType(type.toDescriptorString()));
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 5a07602..86903fe 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -14,7 +14,7 @@
   private final int opcode;
 
   public CfInvoke(int opcode, DexMethod method) {
-    assert Opcodes.INVOKEVIRTUAL <= opcode && opcode <= Opcodes.INVOKEDYNAMIC;
+    assert Opcodes.INVOKEVIRTUAL <= opcode && opcode <= Opcodes.INVOKEINTERFACE;
     this.opcode = opcode;
     this.method = method;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
new file mode 100644
index 0000000..43e6133
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -0,0 +1,79 @@
+// 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.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueDouble;
+import com.android.tools.r8.graph.DexValue.DexValueFloat;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueLong;
+import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueMethodType;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import java.util.List;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
+
+public class CfInvokeDynamic extends CfInstruction {
+
+  private final DexCallSite callSite;
+
+  public CfInvokeDynamic(DexCallSite callSite) {
+    this.callSite = callSite;
+  }
+
+  @Override
+  public void write(MethodVisitor visitor) {
+    DexMethodHandle bootstrapMethod = callSite.bootstrapMethod;
+    List<DexValue> bootstrapArgs = callSite.bootstrapArgs;
+    Object[] bsmArgs = new Object[bootstrapArgs.size()];
+    for (int i = 0; i < bootstrapArgs.size(); i++) {
+      bsmArgs[i] = decodeBootstrapArgument(bootstrapArgs.get(i));
+    }
+    Handle bsmHandle = bootstrapMethod.toAsmHandle();
+    visitor.visitInvokeDynamicInsn(
+        callSite.methodName.toString(),
+        callSite.methodProto.toDescriptorString(),
+        bsmHandle,
+        bsmArgs);
+  }
+
+  private Object decodeBootstrapArgument(DexValue dexValue) {
+    if (dexValue instanceof DexValueInt) {
+      return ((DexValueInt) dexValue).getValue();
+    } else if (dexValue instanceof DexValueLong) {
+      return ((DexValueLong) dexValue).getValue();
+    } else if (dexValue instanceof DexValueFloat) {
+      return ((DexValueFloat) dexValue).getValue();
+    } else if (dexValue instanceof DexValueDouble) {
+      return ((DexValueDouble) dexValue).getValue();
+    } else if (dexValue instanceof DexValueString) {
+      return ((DexValueString) dexValue).getValue();
+    } else if (dexValue instanceof DexValueType) {
+      return Type.getType(((DexValueType) dexValue).value.toDescriptorString());
+    } else if (dexValue instanceof DexValueMethodType) {
+      return Type.getMethodType(((DexValueMethodType) dexValue).value.toDescriptorString());
+    } else if (dexValue instanceof DexValueMethodHandle) {
+      return ((DexValueMethodHandle) dexValue).value.toAsmHandle();
+    } else {
+      throw new Unreachable(
+          "Unsupported bootstrap argument of type " + dexValue.getClass().getSimpleName());
+    }
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  public DexCallSite getCallSite() {
+    return callSite;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
index 12ea053..ae8251d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -4,7 +4,10 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Opcodes;
 
 public class DexMethodHandle extends IndexedDexItem implements
     PresortedComparable<DexMethodHandle> {
@@ -244,4 +247,56 @@
   public int compareTo(DexMethodHandle other) {
     return sortedCompareTo(other.getSortedIndex());
   }
+
+  public Handle toAsmHandle() {
+    String owner;
+    String name;
+    String desc;
+    boolean itf;
+    if (isMethodHandle()) {
+      DexMethod method = asMethod();
+      owner = method.holder.getInternalName();
+      name = method.name.toString();
+      desc = method.proto.toDescriptorString();
+      if (method.holder.toDescriptorString().equals("Ljava/lang/invoke/LambdaMetafactory;")) {
+        itf = false;
+      } else {
+        itf = method.holder.isInterface();
+      }
+    } else {
+      assert isFieldHandle();
+      DexField field = asField();
+      owner = field.clazz.getInternalName();
+      name = field.name.toString();
+      desc = field.type.toDescriptorString();
+      itf = field.clazz.isInterface();
+    }
+    return new Handle(getAsmTag(), owner, name, desc, itf);
+  }
+
+  private int getAsmTag() {
+    switch (type) {
+      case INVOKE_STATIC:
+        return Opcodes.H_INVOKESTATIC;
+      case INVOKE_CONSTRUCTOR:
+        return Opcodes.H_NEWINVOKESPECIAL;
+      case INVOKE_INSTANCE:
+        return Opcodes.H_INVOKEVIRTUAL;
+      case INVOKE_SUPER:
+      case INVOKE_DIRECT:
+        return Opcodes.H_INVOKESPECIAL;
+      case STATIC_GET:
+        return Opcodes.H_GETSTATIC;
+      case STATIC_PUT:
+        return Opcodes.H_PUTSTATIC;
+      case INSTANCE_GET:
+        return Opcodes.H_GETFIELD;
+      case INSTANCE_PUT:
+        return Opcodes.H_PUTFIELD;
+      case INVOKE_INTERFACE:
+        return Opcodes.H_INVOKEINTERFACE;
+      default:
+        throw new Unreachable();
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
index 5feecec..7ad397a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -3,10 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
+import com.android.tools.r8.cf.code.CfConstMethodHandle;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
 
@@ -35,6 +40,11 @@
   }
 
   @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfConstMethodHandle(methodHandle));
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asConstMethodHandle().methodHandle == methodHandle;
   }
@@ -85,4 +95,14 @@
       AppInfo appInfo, Function<Value, TypeLatticeElement> getLatticeElement) {
     return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodHandleType, false);
   }
+
+  @Override
+  public DexType computeVerificationType(TypeVerificationHelper helper) {
+    return helper.getFactory().methodHandleType;
+  }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    helper.storeOutValue(this, it);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
index b687743..01bfeb6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
@@ -3,10 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
+import com.android.tools.r8.cf.code.CfConstMethodType;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
 
@@ -35,6 +40,11 @@
   }
 
   @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfConstMethodType(methodType));
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asConstMethodType().methodType == methodType;
   }
@@ -85,4 +95,14 @@
       AppInfo appInfo, Function<Value, TypeLatticeElement> getLatticeElement) {
     return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodTypeType, false);
   }
+
+  @Override
+  public DexType computeVerificationType(TypeVerificationHelper helper) {
+    return helper.getFactory().methodTypeType;
+  }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    helper.storeOutValue(this, it);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
index d8e78e5..747f912 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
@@ -6,12 +6,14 @@
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 
 public class DebugLocalRead extends Instruction {
+  private static final String ERROR_MESSAGE = "Unexpected attempt to emit debug-local read.";
 
   public DebugLocalRead() {
     super(null);
@@ -29,7 +31,12 @@
 
   @Override
   public void buildDex(DexBuilder builder) {
-    throw new Unreachable("Unexpected attempt to emit debug-local read.");
+    throw new Unreachable(ERROR_MESSAGE);
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    throw new Unreachable(ERROR_MESSAGE);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index c47dc54..68253c6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -138,13 +138,9 @@
     return outValue.outType();
   }
 
-  public void buildDex(DexBuilder builder) {
-    throw new Unreachable("Unexpected instruction when converting to DEX: " + getInstructionName());
-  }
+  public abstract void buildDex(DexBuilder builder);
 
-  public void buildCf(CfBuilder builder) {
-    throw new Unimplemented("No support for building CF instructions for: " + getInstructionName());
-  }
+  public abstract void buildCf(CfBuilder builder);
 
   public void replaceValue(Value oldValue, Value newValue) {
     for (int i = 0; i < inValues.size(); i++) {
@@ -1021,12 +1017,11 @@
   public abstract Constraint inliningConstraint(AppInfoWithLiveness info,
       DexType invocationContext);
 
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    throw new Unimplemented("Implement load/store insertion for: " + getInstructionName());
-  }
+  public abstract void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper);
 
   public DexType computeVerificationType(TypeVerificationHelper helper) {
-    throw new Unimplemented("Implement verification type computation for: " + getInstructionName());
+    assert outValue == null || !outValue.type.isObject();
+    throw new Unreachable("Instruction without object outValue cannot compute verification type");
   }
 
   public boolean hasInvariantVerificationType() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index 38d83ed..0579362 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -3,9 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
 import com.android.tools.r8.code.InvokeCustomRange;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -70,6 +74,11 @@
   }
 
   @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfInvokeDynamic(getCallSite()));
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.isInvokeCustom() && callSite == other.asInvokeCustom().callSite;
   }
@@ -95,4 +104,30 @@
   public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
     return Constraint.NEVER;
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    // Essentially the same as InvokeMethod but with call site's method proto
+    // instead of a static called method.
+    helper.loadInValues(this, it);
+    if (getCallSite().methodProto.returnType.isVoidType()) {
+      return;
+    }
+    if (outValue == null) {
+      helper.popOutType(getCallSite().methodProto.returnType, this, it);
+    } else {
+      assert outValue.isUsed();
+      helper.storeOutValue(this, it);
+    }
+  }
+
+  @Override
+  public boolean hasInvariantVerificationType() {
+    return true;
+  }
+
+  @Override
+  public DexType computeVerificationType(TypeVerificationHelper helper) {
+    return getCallSite().methodProto.returnType;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 17ebfb5..1a567ef 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -170,11 +170,11 @@
   @Override
   public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
     helper.loadInValues(this, it);
-    if (method.proto.returnType.isVoidType()) {
+    if (getReturnType().isVoidType()) {
       return;
     }
     if (outValue == null) {
-      helper.popOutType(method.proto.returnType, this, it);
+      helper.popOutType(getReturnType(), this, it);
     } else {
       assert outValue.isUsed();
       helper.storeOutValue(this, it);
@@ -188,7 +188,7 @@
 
   @Override
   public DexType computeVerificationType(TypeVerificationHelper helper) {
-    return getInvokedMethod().proto.returnType;
+    return getReturnType();
   }
 
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index c0e1a30..718d23c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -3,19 +3,25 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokePolymorphicRange;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.conversion.JarSourceCode;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.InliningOracle;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.Collection;
 import java.util.List;
+import org.objectweb.asm.Opcodes;
 
 public class InvokePolymorphic extends InvokeMethod {
 
@@ -78,6 +84,21 @@
   }
 
   @Override
+  public void buildCf(CfBuilder builder) {
+    DexMethod dexMethod = getInvokedMethod();
+    DexItemFactory factory = builder.getFactory();
+
+    if (dexMethod.holder.getInternalName().equals(JarSourceCode.INTERNAL_NAME_METHOD_HANDLE)) {
+      DexMethod method = factory.createMethod(dexMethod.holder, getProto(), dexMethod.name);
+      builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method));
+    } else {
+      assert dexMethod.holder.getInternalName().equals(JarSourceCode.INTERNAL_NAME_VAR_HANDLE);
+      // VarHandle is new in Java 9
+      throw new Unimplemented();
+    }
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     if (!other.isInvokePolymorphic()) {
       return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index d3ef914..9e9a096 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeSuperRange;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -10,12 +11,14 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import org.objectweb.asm.Opcodes;
 
 public class InvokeSuper extends InvokeMethodWithReceiver {
 
@@ -72,6 +75,11 @@
   }
 
   @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfInvoke(Opcodes.INVOKESPECIAL, getInvokedMethod()));
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     if (!other.isInvokeSuper()) {
       return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Load.java b/src/main/java/com/android/tools/r8/ir/code/Load.java
index 91526dd..e194d32 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Load.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Load.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 
@@ -55,6 +56,11 @@
   }
 
   @Override
+  public void buildDex(DexBuilder builder) {
+    throw new Unreachable("This classfile-specific IR should not be inserted in the Dex backend.");
+  }
+
+  @Override
   public void buildCf(CfBuilder builder) {
     Value value = inValues.get(0);
     builder.add(new CfLoad(value.outType(), builder.getLocalRegister(value)));
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index 48fa364..9ee9684 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -4,16 +4,21 @@
 
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.function.Function;
 
 public class Move extends Instruction {
+  private static final String ERROR_MESSAGE =
+      "This DEX-specific instruction should not be seen in the CF backend";
 
   public Move(Value dest, Value src) {
     super(dest, src);
@@ -36,6 +41,11 @@
   }
 
   @Override
+  public void buildCf(CfBuilder builder) {
+    throw new Unreachable(ERROR_MESSAGE);
+  }
+
+  @Override
   public int maxInValueRegister() {
     return Constants.U16BIT_MAX;
   }
@@ -93,4 +103,9 @@
       AppInfo appInfo, Function<Value, TypeLatticeElement> getLatticeElement) {
     return getLatticeElement.apply(src());
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    throw new Unreachable(ERROR_MESSAGE);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index 7a7566d..799c528 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -3,10 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.code.FillArrayData;
 import com.android.tools.r8.code.FillArrayDataPayload;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -14,6 +17,8 @@
 import java.util.Arrays;
 
 public class NewArrayFilledData extends Instruction {
+  private static final String ERROR_MESSAGE =
+      "Conversion from DEX to classfile not supported for NewArrayFilledData";
 
   public final int element_width;
   public final long size;
@@ -42,6 +47,11 @@
   }
 
   @Override
+  public void buildCf(CfBuilder builder) {
+    throw new Unreachable(ERROR_MESSAGE);
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     NewArrayFilledData o = other.asNewArrayFilledData();
     return o.element_width == element_width
@@ -102,4 +112,9 @@
   public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
     return Constraint.ALWAYS;
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    throw new Unreachable(ERROR_MESSAGE);
+  }
 }
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 51ef234..8da9903 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
@@ -3,10 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -52,6 +54,11 @@
   }
 
   @Override
+  public void buildCf(CfBuilder builder) {
+    throw new Unreachable(ERROR_MESSAGE);
+  }
+
+  @Override
   public int maxInValueRegister() {
     throw new Unreachable(ERROR_MESSAGE);
   }
@@ -92,4 +99,9 @@
     }
     return l;
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    throw new Unreachable(ERROR_MESSAGE);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index c991ffc..d2e47fb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -3,10 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.code.CfPop;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -53,11 +55,21 @@
   }
 
   @Override
+  public void buildDex(DexBuilder builder) {
+    throw new Unreachable("This classfile-specific IR should not be inserted in the Dex backend.");
+  }
+
+  @Override
   public void buildCf(CfBuilder builder) {
     builder.add(new CfPop(inValues.get(0).type));
   }
 
   @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    throw new Unreachable("This IR must not be inserted before load and store insertion.");
+  }
+
+  @Override
   public boolean canBeDeadCode(IRCode code, InternalOptions options) {
     // Pop cannot be dead code as it modifies the stack height.
     return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index 0963b03..82c0eeb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -57,6 +58,11 @@
   }
 
   @Override
+  public void buildDex(DexBuilder builder) {
+    throw new Unreachable("This classfile-specific IR should not be inserted in the Dex backend.");
+  }
+
+  @Override
   public void buildCf(CfBuilder builder) {
     builder.add(new CfStore(outType(), builder.getLocalRegister(outValue)));
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 83995bd..60f9c79 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -102,6 +102,10 @@
     this.factory = factory;
   }
 
+  public DexItemFactory getFactory() {
+    return factory;
+  }
+
   public Code build(CodeRewriter rewriter, InternalOptions options, AppInfoWithSubtyping appInfo) {
     try {
       types = new TypeVerificationHelper(code, factory, appInfo).computeVerificationTypes();
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 894ba4f..26a94d2 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
@@ -141,8 +141,8 @@
       "([Ljava/lang/Object;)Z";
 
   // Various internal names.
-  static final String INTERNAL_NAME_METHOD_HANDLE = "java/lang/invoke/MethodHandle";
-  static final String INTERNAL_NAME_VAR_HANDLE = "java/lang/invoke/VarHandle";
+  public static final String INTERNAL_NAME_METHOD_HANDLE = "java/lang/invoke/MethodHandle";
+  public static final String INTERNAL_NAME_VAR_HANDLE = "java/lang/invoke/VarHandle";
 
   // Language types.
   static final Type CLASS_TYPE = Type.getObjectType("java/lang/Class");
@@ -2601,6 +2601,8 @@
                 default:
                   throw new Unreachable();
               }
+            } else {
+              throw new Unreachable();
             }
             callSiteProto = application.getProto(insn.desc);
           }
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 22a4812..5cb4ce8 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -44,58 +44,54 @@
 
   private final Reporter reporter;
 
-  private static final List<String> IGNORED_SINGLE_ARG_OPTIONS = ImmutableList
-      .of("protomapping",
-          "target"
-      );
-  private static final List<String> IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS = ImmutableList
-      .of("keepdirectories",
-          "runtype",
-          "laststageoutput"
-      );
-  private static final List<String> IGNORED_FLAG_OPTIONS = ImmutableList
-      .of("forceprocessing",
-          "dontusemixedcaseclassnames",
-          "dontpreverify",
-          "experimentalshrinkunusedprotofields",
-          "filterlibraryjarswithorginalprogramjars",
-          "dontskipnonpubliclibraryclasses",
-          "dontskipnonpubliclibraryclassmembers",
-          "invokebasemethod",
-          "android"
-      );
-  private static final List<String> IGNORED_CLASS_DESCRIPTOR_OPTIONS = ImmutableList
-      .of("isclassnamestring",
-          "whyarenotsimple"
-      );
+  private static final List<String> IGNORED_SINGLE_ARG_OPTIONS = ImmutableList.of(
+      "dontnote",
+      "protomapping",
+      "target");
 
-  private static final List<String> WARNED_SINGLE_ARG_OPTIONS = ImmutableList
-      .of("dontnote",
-          "printconfiguration",
-          // TODO -outjars (http://b/37137994) and -adaptresourcefilecontents (http://b/37139570)
-          // should be reported as errors, not just as warnings!
-          "outjars",
-          "adaptresourcefilecontents"
-      );
-  private static final List<String> WARNED_FLAG_OPTIONS = ImmutableList
-      .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"
-      );
+  private static final List<String> IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS = ImmutableList.of(
+      "keepdirectories",
+      "runtype",
+      "laststageoutput");
+
+  private static final List<String> IGNORED_FLAG_OPTIONS = ImmutableList.of(
+      "forceprocessing",
+      "dontusemixedcaseclassnames",
+      "dontpreverify",
+      "experimentalshrinkunusedprotofields",
+      "filterlibraryjarswithorginalprogramjars",
+      "dontskipnonpubliclibraryclasses",
+      "dontskipnonpubliclibraryclassmembers",
+      "invokebasemethod",
+      "android");
+
+  private static final List<String> IGNORED_CLASS_DESCRIPTOR_OPTIONS = ImmutableList.of(
+      "isclassnamestring",
+      "whyarenotsimple");
+
+  private static final List<String> WARNED_SINGLE_ARG_OPTIONS = ImmutableList.of(
+      "printconfiguration",
+      // TODO -outjars (http://b/37137994) and -adaptresourcefilecontents (http://b/37139570)
+      // should be reported as errors, not just as warnings!
+      "outjars",
+      "adaptresourcefilecontents");
+
+  private static final List<String> WARNED_FLAG_OPTIONS = ImmutableList.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.
-  private static final List<String> UNSUPPORTED_FLAG_OPTIONS = ImmutableList
-      .of("skipnonpubliclibraryclasses");
+  private static final List<String> UNSUPPORTED_FLAG_OPTIONS =
+      ImmutableList.of("skipnonpubliclibraryclasses");
 
   public ProguardConfigurationParser(
       DexItemFactory dexItemFactory, Reporter reporter) {
@@ -197,28 +193,10 @@
       }
       TextPosition optionStart = getPosition();
       expectChar('-');
-      String option;
-      if (Iterables.any(IGNORED_SINGLE_ARG_OPTIONS, this::skipOptionWithSingleArg)
-          || Iterables.any(
-              IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS, this::skipOptionWithOptionalSingleArg)
-          || Iterables.any(IGNORED_FLAG_OPTIONS, this::skipFlag)
-          || Iterables.any(IGNORED_CLASS_DESCRIPTOR_OPTIONS, this::skipOptionWithClassSpec)
-          || parseOptimizationOption()) {
+      if (parseIgnoredOption() ||
+          parseIgnoredOptionAndWarn(optionStart) ||
+          parseUnsupportedOptionAndErr(optionStart)) {
         // Intentionally left empty.
-      } 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_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,
-            getPosition(optionStart)));
       } else if (acceptString("renamesourcefileattribute")) {
         skipWhitespace();
         if (isOptionalArgumentGiven()) {
@@ -368,6 +346,41 @@
       return true;
     }
 
+    private boolean parseUnsupportedOptionAndErr(TextPosition optionStart) {
+      String option = Iterables.find(UNSUPPORTED_FLAG_OPTIONS, this::skipFlag, null);
+      if (option != null) {
+        reporter.error(new StringDiagnostic(
+            "Unsupported option: -" + option, origin, getPosition(optionStart)));
+        return true;
+      }
+      return false;
+    }
+
+    private boolean parseIgnoredOptionAndWarn(TextPosition optionStart) {
+      String option =
+          Iterables.find(WARNED_CLASS_DESCRIPTOR_OPTIONS, this::skipOptionWithClassSpec, null);
+      if (option == null) {
+        option = Iterables.find(WARNED_FLAG_OPTIONS, this::skipFlag, null);
+        if (option == null) {
+          option = Iterables.find(WARNED_SINGLE_ARG_OPTIONS, this::skipOptionWithSingleArg, null);
+          if (option == null) {
+            return false;
+          }
+        }
+      }
+      warnIgnoringOptions(option, optionStart);
+      return true;
+    }
+
+    private boolean parseIgnoredOption() {
+      return Iterables.any(IGNORED_SINGLE_ARG_OPTIONS, this::skipOptionWithSingleArg)
+          || Iterables.any(IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS,
+                           this::skipOptionWithOptionalSingleArg)
+          || Iterables.any(IGNORED_FLAG_OPTIONS, this::skipFlag)
+          || Iterables.any(IGNORED_CLASS_DESCRIPTOR_OPTIONS, this::skipOptionWithClassSpec)
+          || parseOptimizationOption();
+    }
+
     private void parseInclude() throws ProguardRuleParserException {
       TextPosition start = getPosition();
       Path included = parseFileName();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
index a88041e..b8b7780 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
@@ -36,7 +36,7 @@
   public static ProguardKeepRule buildFieldKeepRule(DexClass clazz, DexEncodedField field) {
     assert clazz.type == field.field.getHolder();
     ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
-    builder.setType(ProguardKeepRuleType.KEEP);
+    builder.setType(ProguardKeepRuleType.KEEP_CLASS_MEMBERS);
     builder.getModifiersBuilder().setAllowsObfuscation(true);
     builder.getModifiersBuilder().setAllowsOptimization(true);
     builder.getClassAccessFlags().setPublic();
@@ -59,7 +59,7 @@
   public static ProguardKeepRule buildMethodKeepRule(DexClass clazz, DexEncodedMethod method) {
     assert clazz.type == method.method.getHolder();
     ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
-    builder.setType(ProguardKeepRuleType.KEEP);
+    builder.setType(ProguardKeepRuleType.KEEP_CLASS_MEMBERS);
     builder.getModifiersBuilder().setAllowsObfuscation(true);
     builder.getModifiersBuilder().setAllowsOptimization(true);
     builder.getClassAccessFlags().setPublic();
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
index e46bef9..5b96201 100644
--- a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
+++ b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
@@ -18,11 +18,19 @@
  *
  * <p>Lines with a # prefix are ignored.
  *
- * <p>We will do most specific matching, i.e., com.google.foobar.*:feature2 com.google.*:base will
- * put everything in the com.google namespace into base, except classes in com.google.foobar that
- * will go to feature2. Class based mappings takes precedence over packages (since they are more
- * specific): com.google.A:feature2 com.google.*:base Puts A into feature2, and all other classes
- * from com.google into base.
+ * <p>We will do most specific matching, i.e.,
+ * <pre>
+ *   com.google.foobar.*:feature2
+ *   com.google.*:base
+ * </pre>
+ * will put everything in the com.google namespace into base, except classes in com.google.foobar
+ * that will go to feature2. Class based mappings takes precedence over packages (since they are
+ * more specific):
+ * <pre>
+ *   com.google.A:feature2
+ *   com.google.*:base
+ *  </pre>
+ * Puts A into feature2, and all other classes from com.google into base.
  *
  * <p>Note that this format does not allow specifying inter-module dependencies, this is simply a
  * placement tool.
@@ -32,10 +40,12 @@
   HashMap<String, String> parsedRules = new HashMap<>(); // Already parsed rules.
 
   HashSet<FeaturePredicate> mappings = new HashSet<>();
+
   Path mappingFile;
 
   static final String COMMENT = "#";
   static final String SEPARATOR = ":";
+  static final String BASE_NAME = "base";
 
   public static FeatureClassMapping fromSpecification(Path file)
       throws FeatureMappingException, IOException {
@@ -69,7 +79,7 @@
 
   private FeatureClassMapping() {}
 
-  public void addMapping(String clazz, String feature) throws FeatureMappingException {
+  private void addMapping(String clazz, String feature) throws FeatureMappingException {
     addRule(clazz, feature, 0);
   }
 
@@ -91,7 +101,7 @@
       }
     }
     if (bestMatch == null) {
-      throw new FeatureMappingException("Class: " + clazz + " is not mapped to any feature");
+      return BASE_NAME;
     }
     return bestMatch.feature;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index 48a04cd..8ce1c09 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -38,6 +38,7 @@
       errorCount++;
     }
   }
+
   public void error(String message) {
     error(new StringDiagnostic(message));
   }
diff --git a/src/test/examples/dexsplitsample/Class1.java b/src/test/examplesAndroidN/dexsplitsample/Class1.java
similarity index 100%
rename from src/test/examples/dexsplitsample/Class1.java
rename to src/test/examplesAndroidN/dexsplitsample/Class1.java
diff --git a/src/test/examples/dexsplitsample/Class2.java b/src/test/examplesAndroidN/dexsplitsample/Class2.java
similarity index 100%
rename from src/test/examples/dexsplitsample/Class2.java
rename to src/test/examplesAndroidN/dexsplitsample/Class2.java
diff --git a/src/test/examples/dexsplitsample/Class3.java b/src/test/examplesAndroidN/dexsplitsample/Class3.java
similarity index 100%
rename from src/test/examples/dexsplitsample/Class3.java
rename to src/test/examplesAndroidN/dexsplitsample/Class3.java
diff --git a/src/test/examplesAndroidN/dexsplitsample/Class4.java b/src/test/examplesAndroidN/dexsplitsample/Class4.java
new file mode 100644
index 0000000..5b1607f
--- /dev/null
+++ b/src/test/examplesAndroidN/dexsplitsample/Class4.java
@@ -0,0 +1,24 @@
+// 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 dexsplitsample;
+
+public class Class4 {
+  public static void main(String[] args) {
+    new Class4().createLambda();
+    System.out.println("Class4");
+  }
+
+  private void useLambda(LambdaInterface toInvokeOn) {
+    toInvokeOn.foo(42);
+  }
+
+  private void createLambda() {
+    useLambda((a) -> { return a + 2;});
+  }
+
+  interface LambdaInterface {
+    int foo(int a);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 2202332..f63f4f2 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -443,7 +443,12 @@
   private static final String ANGLER_BOOT_IMAGE = ANGLER_DIR + "/system/framework/boot.art";
 
   public static byte[] getClassAsBytes(Class clazz) throws IOException {
-    return ByteStreams.toByteArray(clazz.getResourceAsStream(clazz.getSimpleName() + ".class"));
+    String s = clazz.getSimpleName() + ".class";
+    Class outer = clazz.getEnclosingClass();
+    if (outer != null) {
+      s = outer.getSimpleName() + '$' + s;
+    }
+    return ByteStreams.toByteArray(clazz.getResourceAsStream(s));
   }
 
   public static String getArtDir(DexVm version) {
@@ -883,13 +888,15 @@
     return runJavaNoVerify(path, main);
   }
 
-  public static ProcessResult runJava(Path classpath, String mainClass) throws IOException {
-    return runJava(ImmutableList.of(classpath), mainClass);
+  public static ProcessResult runJava(Path classpath, String... args) throws IOException {
+    return runJava(ImmutableList.of(classpath), args);
   }
 
-  public static ProcessResult runJava(List<Path> classpath, String mainClass) throws IOException {
+  public static ProcessResult runJava(List<Path> classpath, String... args) throws IOException {
     String cp = classpath.stream().map(Path::toString).collect(Collectors.joining(PATH_SEPARATOR));
-    ProcessBuilder builder = new ProcessBuilder(getJavaExecutable(), "-cp", cp, mainClass);
+    List<String> cmdline = new ArrayList<String>(Arrays.asList(getJavaExecutable(), "-cp", cp));
+    cmdline.addAll(Arrays.asList(args));
+    ProcessBuilder builder = new ProcessBuilder(cmdline);
     return runProcess(builder);
   }
 
@@ -958,6 +965,11 @@
     return runArtRaw(Collections.singletonList(file), mainClass, null);
   }
 
+  public static ProcessResult runArtRaw(
+      String file, String mainClass, Consumer<ArtCommandBuilder> extras) throws IOException {
+    return runArtRaw(Collections.singletonList(file), mainClass, extras);
+  }
+
   public static ProcessResult runArtRaw(List<String> files, String mainClass,
       Consumer<ArtCommandBuilder> extras)
       throws IOException {
diff --git a/src/test/java/com/android/tools/r8/cf/LambdaTest.java b/src/test/java/com/android/tools/r8/cf/LambdaTest.java
new file mode 100644
index 0000000..983f1b7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/LambdaTest.java
@@ -0,0 +1,17 @@
+// 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 java.util.function.Function;
+
+public class LambdaTest {
+  public static void main(String[] args) {
+    twicePrint(args.length, i -> i + 21);
+  }
+
+  private static void twicePrint(int v, Function<Integer, Integer> f) {
+    System.out.println(f.andThen(f).apply(v));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
new file mode 100644
index 0000000..21d0292
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
@@ -0,0 +1,118 @@
+// 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 static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+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.AndroidApp;
+import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.DexInspector;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.InvokeDynamicInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+
+public class LambdaTestRunner {
+
+  private static final Class<?> CLASS = LambdaTest.class;
+  private static final String METHOD = "main";
+
+  @Rule
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Test
+  public void test() throws Exception {
+    // Test that the InvokeDynamic instruction in LambdaTest.main()
+    // is not modified by the R8 compilation.
+    // First, extract the InvokeDynamic instruction from the input class.
+    byte[] inputClass = ToolHelper.getClassAsBytes(CLASS);
+    int opcode = Opcodes.INVOKEDYNAMIC;
+    InvokeDynamicInsnNode insnInput = findFirstInMethod(inputClass, opcode);
+    // Compile with R8 and extract the InvokeDynamic instruction from the output class.
+    AndroidAppConsumers appBuilder = new AndroidAppConsumers();
+    Path outPath = temp.getRoot().toPath().resolve("out.jar");
+    R8.run(
+        R8Command.builder()
+            .setMode(CompilationMode.DEBUG)
+            .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+            .setProgramConsumer(appBuilder.wrapClassFileConsumer(new ArchiveConsumer(outPath)))
+            .addClassProgramData(inputClass, Origin.unknown())
+            .build());
+    AndroidApp app = appBuilder.build();
+    InvokeDynamicInsnNode insnOutput = findFirstInMethod(app, opcode);
+    // Check that the InvokeDynamic instruction is not modified.
+    assertEquals(insnInput.name, insnOutput.name);
+    assertEquals(insnInput.desc, insnOutput.desc);
+    assertEquals(insnInput.bsm, insnOutput.bsm);
+    assertArrayEquals(insnInput.bsmArgs, insnOutput.bsmArgs);
+    // Check that execution gives the same output.
+    ProcessResult inputResult =
+        ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getName());
+    ProcessResult outputResult = ToolHelper.runJava(outPath, CLASS.getName());
+    assertEquals(inputResult.toString(), outputResult.toString());
+  }
+
+  private InvokeDynamicInsnNode findFirstInMethod(AndroidApp app, int opcode) throws Exception {
+    String returnType = "void";
+    DexInspector inspector = new DexInspector(app);
+    List<String> args = Collections.singletonList(String[].class.getTypeName());
+    DexEncodedMethod method = inspector.clazz(CLASS).method(returnType, METHOD, args).getMethod();
+    JarCode jarCode = method.getCode().asJarCode();
+    MethodNode outputMethod = jarCode.getNode();
+    return (InvokeDynamicInsnNode) findFirstInstruction(outputMethod, opcode);
+  }
+
+  private InvokeDynamicInsnNode findFirstInMethod(byte[] clazz, int opcode) {
+    MethodNode[] method = {null};
+    new ClassReader(clazz)
+        .accept(
+            new ClassNode(Opcodes.ASM6) {
+              @Override
+              public MethodVisitor visitMethod(
+                  int access, String name, String desc, String signature, String[] exceptions) {
+                if (name.equals(METHOD)) {
+                  method[0] = new MethodNode(access, name, desc, signature, exceptions);
+                  return method[0];
+                } else {
+                  return null;
+                }
+              }
+            },
+            0);
+    return (InvokeDynamicInsnNode) findFirstInstruction(method[0], opcode);
+  }
+
+  private AbstractInsnNode findFirstInstruction(MethodNode node, int opcode) {
+    assert node != null;
+    InsnList asmInsns = node.instructions;
+    for (ListIterator<AbstractInsnNode> it = asmInsns.iterator(); it.hasNext(); ) {
+      AbstractInsnNode insn = it.next();
+      if (insn.getOpcode() == opcode) {
+        return insn;
+      }
+    }
+    throw new RuntimeException("Instruction not found");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java b/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java
new file mode 100644
index 0000000..c80047a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java
@@ -0,0 +1,189 @@
+// 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.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import org.objectweb.asm.*;
+
+// The method MethodHandleDump.transform() translates methods in MethodHandleTest that look like
+//     MethodType.methodType(TYPES)
+// and
+//     MethodHandles.lookup().findKIND(FOO.class, "METHOD", TYPE)
+// into LDC instructions.
+// This is necessary since there is no Java syntax that compiles to
+// LDC of a constant method handle or constant method type.
+//
+// The method dumpD() dumps a class equivalent to MethodHandleTest.D
+// that uses an LDC instruction instead of MethodHandles.lookup().findSpecial().
+// The LDC instruction loads an InvokeSpecial constant method handle to a C method,
+// so this LDC instruction must be in a subclass of C, and not directly on MethodHandleTest.
+public class MethodHandleDump implements Opcodes {
+
+  private static final Type viType = Type.getMethodType(Type.VOID_TYPE, Type.INT_TYPE);
+  private static final Type jiType = Type.getMethodType(Type.LONG_TYPE, Type.INT_TYPE);
+  private static final Type vicType =
+      Type.getMethodType(Type.VOID_TYPE, Type.INT_TYPE, Type.CHAR_TYPE);
+  private static final Type jicType =
+      Type.getMethodType(Type.LONG_TYPE, Type.INT_TYPE, Type.CHAR_TYPE);
+  private static final String cDesc = "com/android/tools/r8/cf/MethodHandleTest$C";
+  private static final String iDesc = "com/android/tools/r8/cf/MethodHandleTest$I";
+  private static final String viDesc = viType.getDescriptor();
+  private static final String jiDesc = jiType.getDescriptor();
+  private static final String vicDesc = vicType.getDescriptor();
+  private static final String jicDesc = jicType.getDescriptor();
+  private static final String intDesc = Type.INT_TYPE.getDescriptor();
+
+  public static byte[] transform(byte[] input) throws Exception {
+    ImmutableMap.Builder<String, Type> typesBuilder = ImmutableMap.builder();
+    ImmutableMap<String, Type> types =
+        typesBuilder
+            .put("viType", viType)
+            .put("jiType", jiType)
+            .put("vicType", vicType)
+            .put("jicType", jicType)
+            .build();
+
+    Builder<String, Handle> methodsBuilder = ImmutableMap.builder();
+    methodsBuilder
+        .put("scviMethod", new Handle(H_INVOKESTATIC, cDesc, "svi", viDesc, false))
+        .put("scjiMethod", new Handle(H_INVOKESTATIC, cDesc, "sji", jiDesc, false))
+        .put("scvicMethod", new Handle(H_INVOKESTATIC, cDesc, "svic", vicDesc, false))
+        .put("scjicMethod", new Handle(H_INVOKESTATIC, cDesc, "sjic", jicDesc, false))
+        .put("vcviMethod", new Handle(H_INVOKEVIRTUAL, cDesc, "vvi", viDesc, false))
+        .put("vcjiMethod", new Handle(H_INVOKEVIRTUAL, cDesc, "vji", jiDesc, false))
+        .put("vcvicMethod", new Handle(H_INVOKEVIRTUAL, cDesc, "vvic", vicDesc, false))
+        .put("vcjicMethod", new Handle(H_INVOKEVIRTUAL, cDesc, "vjic", jicDesc, false))
+        .put("siviMethod", new Handle(H_INVOKESTATIC, iDesc, "svi", viDesc, true))
+        .put("sijiMethod", new Handle(H_INVOKESTATIC, iDesc, "sji", jiDesc, true))
+        .put("sivicMethod", new Handle(H_INVOKESTATIC, iDesc, "svic", vicDesc, true))
+        .put("sijicMethod", new Handle(H_INVOKESTATIC, iDesc, "sjic", jicDesc, true))
+        .put("diviMethod", new Handle(H_INVOKEINTERFACE, iDesc, "dvi", viDesc, true))
+        .put("dijiMethod", new Handle(H_INVOKEINTERFACE, iDesc, "dji", jiDesc, true))
+        .put("divicMethod", new Handle(H_INVOKEINTERFACE, iDesc, "dvic", vicDesc, true))
+        .put("dijicMethod", new Handle(H_INVOKEINTERFACE, iDesc, "djic", jicDesc, true))
+        .put("vciSetField", new Handle(H_PUTFIELD, cDesc, "vi", intDesc, false))
+        .put("sciSetField", new Handle(H_PUTSTATIC, cDesc, "si", intDesc, false))
+        .put("vciGetField", new Handle(H_GETFIELD, cDesc, "vi", intDesc, false))
+        .put("sciGetField", new Handle(H_GETSTATIC, cDesc, "si", intDesc, false))
+        .put("iiSetField", new Handle(H_PUTSTATIC, iDesc, "ii", intDesc, true))
+        .put("iiGetField", new Handle(H_GETSTATIC, iDesc, "ii", intDesc, true))
+        .put("constructorMethod", new Handle(H_NEWINVOKESPECIAL, cDesc, "<init>", viDesc, false));
+    ImmutableMap<String, Handle> methods = methodsBuilder.build();
+    ClassReader cr = new ClassReader(input);
+    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+    cr.accept(
+        new ClassVisitor(ASM6, cw) {
+
+          @Override
+          public MethodVisitor visitMethod(
+              int access, String name, String desc, String signature, String[] exceptions) {
+            switch (desc) {
+              case "()Ljava/lang/invoke/MethodType;":
+                {
+                  Type type = types.get(name);
+                  assert type != null : name;
+                  assert access == ACC_PUBLIC + ACC_STATIC;
+                  assert signature == null;
+                  assert exceptions == null;
+                  MethodVisitor mv = cw.visitMethod(access, name, desc, null, null);
+                  mv.visitCode();
+                  mv.visitLdcInsn(type);
+                  mv.visitInsn(ARETURN);
+                  mv.visitMaxs(-1, -1);
+                  mv.visitEnd();
+                  return null;
+                }
+              case "()Ljava/lang/invoke/MethodHandle;":
+                {
+                  Handle method = methods.get(name);
+                  assert access == ACC_PUBLIC + ACC_STATIC;
+                  assert method != null : name;
+                  assert signature == null;
+                  assert exceptions == null;
+                  MethodVisitor mv = cw.visitMethod(access, name, desc, null, null);
+                  mv.visitCode();
+                  mv.visitLdcInsn(method);
+                  mv.visitInsn(ARETURN);
+                  mv.visitMaxs(-1, -1);
+                  mv.visitEnd();
+                  return null;
+                }
+              default:
+                return super.visitMethod(access, name, desc, signature, exceptions);
+            }
+          }
+        },
+        0);
+    return cw.toByteArray();
+  }
+
+  public static byte[] dumpD() throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    MethodVisitor mv;
+
+    cw.visit(
+        V1_8,
+        ACC_PUBLIC + ACC_SUPER,
+        "com/android/tools/r8/cf/MethodHandleTest$D",
+        null,
+        "com/android/tools/r8/cf/MethodHandleTest$C",
+        null);
+
+    cw.visitInnerClass(
+        "com/android/tools/r8/cf/MethodHandleTest$D",
+        "com/android/tools/r8/cf/MethodHandleTest",
+        "D",
+        ACC_PUBLIC + ACC_STATIC);
+
+    cw.visitInnerClass(
+        "com/android/tools/r8/cf/MethodHandleTest$C",
+        "com/android/tools/r8/cf/MethodHandleTest",
+        "C",
+        ACC_PUBLIC + ACC_STATIC);
+
+    cw.visitInnerClass(
+        "java/lang/invoke/MethodHandles$Lookup",
+        "java/lang/invoke/MethodHandles",
+        "Lookup",
+        ACC_PUBLIC + ACC_FINAL + ACC_STATIC);
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(
+          INVOKESPECIAL, "com/android/tools/r8/cf/MethodHandleTest$C", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    {
+      mv =
+          cw.visitMethod(
+              ACC_PUBLIC + ACC_STATIC,
+              "vcviSpecialMethod",
+              "()Ljava/lang/invoke/MethodHandle;",
+              null,
+              null);
+      mv.visitCode();
+      mv.visitLdcInsn(new Handle(H_INVOKESPECIAL, cDesc, "vvi", viDesc, false));
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(-1, -1);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "vvi", "(I)V", null, null);
+      mv.visitCode();
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(0, 2);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java
new file mode 100644
index 0000000..d42d443
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java
@@ -0,0 +1,385 @@
+// 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 java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+public class MethodHandleTest {
+
+  public static class C {
+    public C(int i) {
+      System.out.println("C " + i);
+    }
+
+    public C() {
+      System.out.println("C");
+    }
+
+    public int vi;
+    public static int si;
+
+    public static void svi(int i) {
+      System.out.println("svi " + i);
+    }
+
+    public static long sji(int i) {
+      System.out.println("sji " + i);
+      return 42L;
+    }
+
+    public static void svic(int i, char c) {
+      System.out.println("svic " + i);
+    }
+
+    public static long sjic(int i, char c) {
+      System.out.println("sjic " + i);
+      return 42L;
+    }
+
+    public void vvi(int i) {
+      System.out.println("vvi " + i);
+    }
+
+    public long vji(int i) {
+      System.out.println("vji " + i);
+      return 42L;
+    }
+
+    public void vvic(int i, char c) {
+      System.out.println("vvic " + i);
+    }
+
+    public long vjic(int i, char c) {
+      System.out.println("vjic " + i);
+      return 42L;
+    }
+  }
+
+  public static class D extends C {
+    public static MethodHandle vcviSpecialMethod() {
+      try {
+        return MethodHandles.lookup().findSpecial(C.class, "vvi", viType(), D.class);
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    public void vvi(int i) {
+      // Overridden to output nothing.
+    }
+  }
+
+  public interface I {
+    int ii = 42;
+
+    static void svi(int i) {
+      System.out.println("svi " + i);
+    }
+
+    static long sji(int i) {
+      System.out.println("sji " + i);
+      return 42L;
+    }
+
+    static void svic(int i, char c) {
+      System.out.println("svic " + i);
+    }
+
+    static long sjic(int i, char c) {
+      System.out.println("sjic " + i);
+      return 42L;
+    }
+
+    default void dvi(int i) {
+      System.out.println("dvi " + i);
+    }
+
+    default long dji(int i) {
+      System.out.println("dji " + i);
+      return 42L;
+    }
+
+    default void dvic(int i, char c) {
+      System.out.println("dvic " + i);
+    }
+
+    default long djic(int i, char c) {
+      System.out.println("djic " + i);
+      return 42L;
+    }
+  }
+
+  public static class Impl implements I {}
+
+  public static void main(String[] args) {
+    // When MethodHandleTestRunner invokes this program with the JVM, "fail" is passed as arg.
+    // When invoked with Art, no arg is passed since interface fields may be modified on Art.
+    String expectedResult = args[0];
+    C c = new C(42);
+    I i = new Impl();
+    try {
+      scviMethod().invoke(1);
+      assertEquals(42L, (long) scjiMethod().invoke(2));
+      scvicMethod().invoke(3, 'x');
+      assertEquals(42L, (long) scjicMethod().invoke(4, 'x'));
+      vcviMethod().invoke(c, 5);
+      assertEquals(42L, (long) vcjiMethod().invoke(c, 6));
+      vcvicMethod().invoke(c, 7, 'x');
+      assertEquals(42L, (long) vcjicMethod().invoke(c, 8, 'x'));
+      siviMethod().invoke(9);
+      assertEquals(42L, (long) sijiMethod().invoke(10));
+      sivicMethod().invoke(11, 'x');
+      assertEquals(42L, (long) sijicMethod().invoke(12, 'x'));
+      diviMethod().invoke(i, 13);
+      assertEquals(42L, (long) dijiMethod().invoke(i, 14));
+      divicMethod().invoke(i, 15, 'x');
+      assertEquals(42L, (long) dijicMethod().invoke(i, 16, 'x'));
+      vciSetField().invoke(c, 17);
+      assertEquals(17, (int) vciGetField().invoke(c));
+      sciSetField().invoke(18);
+      assertEquals(18, (int) sciGetField().invoke());
+      String interfaceSetResult;
+      try {
+        iiSetField().invoke(19);
+        interfaceSetResult = "pass";
+      } catch (RuntimeException e) {
+        if (e.getCause() instanceof IllegalAccessException) {
+          interfaceSetResult = "exception";
+        } else {
+          throw e;
+        }
+      } catch (IllegalAccessError e) {
+        interfaceSetResult = "error";
+      }
+      if (!interfaceSetResult.equals(expectedResult)) {
+        throw new RuntimeException(
+            "Wrong outcome of iiSetField().invoke(): Expected "
+                + expectedResult
+                + " but got "
+                + interfaceSetResult);
+      }
+      assertEquals(interfaceSetResult.equals("pass") ? 19 : 42, (int) iiGetField().invoke());
+      MethodHandle methodHandle = D.vcviSpecialMethod();
+      methodHandle.invoke(new D(), 20);
+      constructorMethod().invoke(21);
+    } catch (Throwable e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private static void assertEquals(long l, long x) {
+    if (l != x) {
+      throw new AssertionError("Not equal: " + l + " != " + x);
+    }
+  }
+
+  private static void assertEquals(int l, int x) {
+    if (l != x) {
+      throw new AssertionError("Not equal: " + l + " != " + x);
+    }
+  }
+
+  public static MethodType viType() {
+    return MethodType.methodType(void.class, int.class);
+  }
+
+  public static MethodType jiType() {
+    return MethodType.methodType(long.class, int.class);
+  }
+
+  public static MethodType vicType() {
+    return MethodType.methodType(void.class, int.class, char.class);
+  }
+
+  public static MethodType jicType() {
+    return MethodType.methodType(long.class, int.class, char.class);
+  }
+
+  public static MethodHandle scviMethod() {
+    try {
+      return MethodHandles.lookup().findStatic(C.class, "svi", viType());
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle scjiMethod() {
+    try {
+      return MethodHandles.lookup().findStatic(C.class, "sji", jiType());
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle scvicMethod() {
+    try {
+      return MethodHandles.lookup().findStatic(C.class, "svic", vicType());
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle scjicMethod() {
+    try {
+      return MethodHandles.lookup().findStatic(C.class, "sjic", jicType());
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle vcviMethod() {
+    try {
+      return MethodHandles.lookup().findVirtual(C.class, "vvi", viType());
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle vcjiMethod() {
+    try {
+      return MethodHandles.lookup().findVirtual(C.class, "vji", jiType());
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle vcvicMethod() {
+    try {
+      return MethodHandles.lookup().findVirtual(C.class, "vvic", vicType());
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle vcjicMethod() {
+    try {
+      return MethodHandles.lookup().findVirtual(C.class, "vjic", jicType());
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle siviMethod() {
+    try {
+      return MethodHandles.lookup().findStatic(I.class, "svi", viType());
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle sijiMethod() {
+    try {
+      return MethodHandles.lookup().findStatic(I.class, "sji", jiType());
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle sivicMethod() {
+    try {
+      return MethodHandles.lookup().findStatic(I.class, "svic", vicType());
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle sijicMethod() {
+    try {
+      return MethodHandles.lookup().findStatic(I.class, "sjic", jicType());
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle diviMethod() {
+    try {
+      return MethodHandles.lookup().findVirtual(I.class, "dvi", viType());
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle dijiMethod() {
+    try {
+      return MethodHandles.lookup().findVirtual(I.class, "dji", jiType());
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle divicMethod() {
+    try {
+      return MethodHandles.lookup().findVirtual(I.class, "dvic", vicType());
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle dijicMethod() {
+    try {
+      return MethodHandles.lookup().findVirtual(I.class, "djic", jicType());
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle vciSetField() {
+    try {
+      return MethodHandles.lookup().findSetter(C.class, "vi", int.class);
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle sciSetField() {
+    try {
+      return MethodHandles.lookup().findStaticSetter(C.class, "si", int.class);
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle vciGetField() {
+    try {
+      return MethodHandles.lookup().findGetter(C.class, "vi", int.class);
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle sciGetField() {
+    try {
+      return MethodHandles.lookup().findStaticGetter(C.class, "si", int.class);
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle iiSetField() {
+    try {
+      return MethodHandles.lookup().findStaticSetter(I.class, "ii", int.class);
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle iiGetField() {
+    try {
+      return MethodHandles.lookup().findStaticGetter(I.class, "ii", int.class);
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static MethodHandle constructorMethod() {
+    try {
+      return MethodHandles.lookup().findConstructor(C.class, viType());
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
new file mode 100644
index 0000000..ba25396
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -0,0 +1,125 @@
+// 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 static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.nio.file.Path;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class MethodHandleTestRunner {
+  static final Class<?> CLASS = MethodHandleTest.class;
+
+  private boolean ldc = false;
+
+  @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Test
+  public void testMethodHandlesLookup() throws Exception {
+    // Run test with dynamic method lookups, i.e. using MethodHandles.lookup().find*()
+    ldc = false;
+    test();
+  }
+
+  @Test
+  public void testLdcMethodHandle() throws Exception {
+    // Run test with LDC methods, i.e. without java.lang.invoke.MethodHandles
+    ldc = true;
+    test();
+  }
+
+  private final Class[] inputClasses = {
+    MethodHandleTest.class,
+    MethodHandleTest.C.class,
+    MethodHandleTest.I.class,
+    MethodHandleTest.Impl.class,
+    MethodHandleTest.D.class,
+  };
+
+  private void test() throws Exception {
+    ProcessResult runInput = runInput();
+    Path outCf = temp.getRoot().toPath().resolve("cf.jar");
+    build(new ClassFileConsumer.ArchiveConsumer(outCf));
+    Path outDex = temp.getRoot().toPath().resolve("dex.zip");
+    build(new DexIndexedConsumer.ArchiveConsumer(outDex));
+
+    ProcessResult runCf =
+        ToolHelper.runJava(outCf, CLASS.getCanonicalName(), ldc ? "error" : "exception");
+    assertEquals(runInput.toString(), runCf.toString());
+    // TODO(mathiasr): Once we include a P runtime, change this to "P and above".
+    if (ToolHelper.getDexVm() != DexVm.ART_DEFAULT) {
+      return;
+    }
+    ProcessResult runDex =
+        ToolHelper.runArtRaw(
+            outDex.toString(),
+            CLASS.getCanonicalName(),
+            cmd -> cmd.appendProgramArgument(ldc ? "pass" : "exception"));
+    // Only compare stdout and exitCode since dex2oat prints to stderr.
+    if (runInput.exitCode != runDex.exitCode) {
+      System.out.println(runDex.stderr);
+    }
+    assertEquals(runInput.stdout, runDex.stdout);
+    assertEquals(runInput.exitCode, runDex.exitCode);
+  }
+
+  private void build(ProgramConsumer programConsumer) throws Exception {
+    // MethodHandle.invoke() only supported from Android O
+    // ConstMethodHandle only supported from Android P
+    AndroidApiLevel apiLevel = AndroidApiLevel.P;
+    Builder cfBuilder =
+        R8Command.builder()
+            .setMinApiLevel(apiLevel.getLevel())
+            .setMode(CompilationMode.DEBUG)
+            .addLibraryFiles(ToolHelper.getAndroidJar(apiLevel))
+            .setProgramConsumer(programConsumer);
+    for (Class<?> c : inputClasses) {
+      byte[] classAsBytes = getClassAsBytes(c);
+      cfBuilder.addClassProgramData(classAsBytes, Origin.unknown());
+    }
+    R8.run(cfBuilder.build());
+  }
+
+  private ProcessResult runInput() throws Exception {
+    Path out = temp.getRoot().toPath().resolve("input.jar");
+    ClassFileConsumer.ArchiveConsumer archiveConsumer = new ClassFileConsumer.ArchiveConsumer(out);
+    for (Class<?> c : inputClasses) {
+      archiveConsumer.accept(
+          getClassAsBytes(c), DescriptorUtils.javaTypeToDescriptor(c.getName()), null);
+    }
+    archiveConsumer.finished(null);
+    ProcessResult runInput = ToolHelper.runJava(out, CLASS.getName(), ldc ? "error" : "exception");
+    if (runInput.exitCode != 0) {
+      System.out.println(runInput);
+    }
+    assertEquals(0, runInput.exitCode);
+    return runInput;
+  }
+
+  private byte[] getClassAsBytes(Class<?> clazz) throws Exception {
+    if (ldc) {
+      if (clazz == MethodHandleTest.D.class) {
+        return MethodHandleDump.dumpD();
+      } else if (clazz == MethodHandleTest.class) {
+        return MethodHandleDump.transform(ToolHelper.getClassAsBytes(clazz));
+      }
+    }
+    return ToolHelper.getClassAsBytes(clazz);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
index 43e9d03..19a5ee2 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -40,11 +41,15 @@
 
 public class DexSplitterTests {
 
-  private static final String CLASS_DIR = ToolHelper.EXAMPLES_BUILD_DIR + "classes/dexsplitsample";
+  private static final String CLASS_DIR =
+      ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR + "classes/dexsplitsample";
   private static final String CLASS1_CLASS = CLASS_DIR + "/Class1.class";
   private static final String CLASS2_CLASS = CLASS_DIR + "/Class2.class";
   private static final String CLASS3_CLASS = CLASS_DIR + "/Class3.class";
   private static final String CLASS3_INNER_CLASS = CLASS_DIR + "/Class3$InnerClass.class";
+  private static final String CLASS4_CLASS = CLASS_DIR + "/Class4.class";
+  private static final String CLASS4_LAMBDA_INTERFACE = CLASS_DIR + "/Class4$LambdaInterface.class";
+
 
   @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
@@ -75,6 +80,8 @@
             .addProgramFiles(Paths.get(CLASS2_CLASS))
             .addProgramFiles(Paths.get(CLASS3_CLASS))
             .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
+            .addProgramFiles(Paths.get(CLASS4_CLASS))
+            .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE))
             .build());
 
     Path output = temp.getRoot().toPath().resolve("output");
@@ -135,6 +142,18 @@
     } catch (AssertionError assertionError) {
       // We expect this to throw since base is not in the path and Class3 depends on Class1
     }
+
+    className = "Class4";
+    builder = new ArtCommandBuilder();
+    builder.appendClasspath(feature.toString());
+    builder.setMainClass("dexsplitsample." + className);
+    try {
+      ToolHelper.runArt(builder);
+      assertFalse(true);
+    } catch (AssertionError assertionError) {
+      // We expect this to throw since base is not in the path and Class4 includes a lambda that
+      // would have been pushed to base.
+    }
   }
 
   private Path createSplitSpec() throws FileNotFoundException, UnsupportedEncodingException {
@@ -143,7 +162,8 @@
       out.write(
           "dexsplitsample.Class1:base\n"
               + "dexsplitsample.Class2:feature1\n"
-              + "dexsplitsample.Class3:feature1");
+              + "dexsplitsample.Class3:feature1\n"
+              + "dexsplitsample.Class4:feature1");
     }
     return splitSpec;
   }
@@ -159,11 +179,13 @@
   public void splitFilesFromJar()
       throws IOException, CompilationFailedException, FeatureMappingException, ResourceException,
       CompilationException, ExecutionException {
-    splitFromJars(true);
-    splitFromJars(false);
+    splitFromJars(true, true);
+    splitFromJars(false, true);
+    splitFromJars(true, false);
+    splitFromJars(false, false);
   }
 
-  private void splitFromJars(boolean useOptions)
+  private void splitFromJars(boolean useOptions, boolean explicitBase)
       throws IOException, CompilationFailedException, FeatureMappingException, ResourceException,
       ExecutionException, CompilationException {
     // Initial normal compile to create dex files.
@@ -175,6 +197,8 @@
             .addProgramFiles(Paths.get(CLASS2_CLASS))
             .addProgramFiles(Paths.get(CLASS3_CLASS))
             .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
+            .addProgramFiles(Paths.get(CLASS4_CLASS))
+            .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE))
             .build());
 
     Path output = temp.getRoot().toPath().resolve("output");
@@ -200,26 +224,38 @@
     featureStream.putNextEntry(new ZipEntry(name));
     featureStream.write(Files.readAllBytes(Paths.get(CLASS3_INNER_CLASS)));
     featureStream.closeEntry();
+    name = "dexsplitsample/Class4";
+    featureStream.putNextEntry(new ZipEntry(name));
+    featureStream.write(Files.readAllBytes(Paths.get(CLASS4_CLASS)));
+    featureStream.closeEntry();
+    name = "dexsplitsample/Class4$LambdaInterface";
+    featureStream.putNextEntry(new ZipEntry(name));
+    featureStream.write(Files.readAllBytes(Paths.get(CLASS4_LAMBDA_INTERFACE)));
+    featureStream.closeEntry();
     featureStream.close();
     if (useOptions) {
       Options options = new Options();
       options.inputArchives.add(inputZip.toString());
       options.splitBaseName = output.toString();
-      options.featureJars.add(baseJar.toString());
+      if (explicitBase) {
+        options.featureJars.add(baseJar.toString());
+      }
       options.featureJars.add(featureJar.toString());
       DexSplitter.run(options);
     } else {
-      DexSplitter.main(
-          new String[]{
-              "--input",
-              inputZip.toString(),
-              "--output",
-              output.toString(),
-              "--feature-jar",
-              baseJar.toString(),
-              "--feature-jar",
-              featureJar.toString()
-          });
+      List<String> args = Lists.newArrayList(
+          "--input",
+          inputZip.toString(),
+          "--output",
+          output.toString(),
+          "--feature-jar",
+          featureJar.toString());
+      if (explicitBase) {
+        args.add("--feature-jar");
+        args.add(baseJar.toString());
+      }
+
+      DexSplitter.main(args.toArray(new String[0]));
     }
     Path base = output.getParent().resolve("output.base.zip");
     Path feature = output.getParent().resolve("output.feature1.zip");
@@ -241,6 +277,8 @@
             .addProgramFiles(Paths.get(CLASS2_CLASS))
             .addProgramFiles(Paths.get(CLASS3_CLASS))
             .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
+            .addProgramFiles(Paths.get(CLASS4_CLASS))
+            .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE))
             .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
             .setProguardMapOutputPath(proguardMap)
             .addProguardConfiguration(getProguardConf(), null)
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 85f2eb5..1419b95 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -119,8 +119,8 @@
   protected static FieldSubject checkFieldIsPresent(ClassSubject classSubject, String fieldType,
       String fieldName) {
     FieldSubject fieldSubject = classSubject.field(fieldType, fieldName);
-    assertNotNull(fieldSubject);
-    assertTrue(fieldSubject.isPresent());
+    assertTrue("No field " + fieldName + " in " + classSubject.getOriginalName(),
+        fieldSubject.isPresent());
     return fieldSubject;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index 60ed129..df04931 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -4,12 +4,15 @@
 
 package com.android.tools.r8.kotlin;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.kotlin.TestKotlinClass.AccessorKind;
 import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.utils.AndroidApp;
@@ -19,6 +22,7 @@
 import java.nio.file.Path;
 import java.util.Collections;
 import org.junit.Assert;
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class R8KotlinAccessorTest extends AbstractR8KotlinTestBase {
@@ -39,24 +43,38 @@
           .addProperty("publicProp", JAVA_LANG_STRING, Visibility.PUBLIC)
           .addProperty("primitiveProp", "int", Visibility.PUBLIC);
 
+  private static final TestKotlinCompanionClass COMPANION_LATE_INIT_PROPERTY_CLASS =
+      new TestKotlinCompanionClass("properties.CompanionLateInitProperties")
+          .addProperty("privateLateInitProp", JAVA_LANG_STRING, Visibility.PRIVATE)
+          .addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
+          .addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
+
+  private static final TestKotlinClass PROPERTY_ACCESS_FOR_INNER_CLASS =
+      new TestKotlinClass("accessors.PropertyAccessorForInnerClass")
+          .addProperty("privateProp", JAVA_LANG_STRING, Visibility.PRIVATE)
+          .addProperty("privateLateInitProp", JAVA_LANG_STRING, Visibility.PRIVATE);
+
+  private static final TestKotlinClass PROPERTY_ACCESS_FOR_LAMBDA_CLASS =
+      new TestKotlinClass("accessors.PropertyAccessorForLambda")
+          .addProperty("property", JAVA_LANG_STRING, Visibility.PRIVATE)
+          .addProperty("indirectPropertyGetter", JAVA_LANG_STRING, Visibility.PRIVATE);
+
   @Test
   public void testCompanionProperty_primitivePropertyIsAlwaysInlined() throws Exception {
+    final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
         "companionProperties_usePrimitiveProp");
     runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassExists(dexInspector,
-          COMPANION_PROPERTY_CLASS.getOuterClassName());
+      ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
       String propertyName = "primitiveProp";
       FieldSubject fieldSubject = checkFieldIsPresent(outerClass, "int", propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
 
-      MemberNaming.MethodSignature getterAccessor =
-          new MemberNaming.MethodSignature("access$getPrimitiveProp$cp", "int",
-              Collections.emptyList());
-      MemberNaming.MethodSignature setterAccessor =
-          new MemberNaming.MethodSignature("access$setPrimitiveProp$cp", "void",
-              Collections.singletonList("int"));
+      MemberNaming.MethodSignature getterAccessor = testedClass
+          .getGetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION);
+      MemberNaming.MethodSignature setterAccessor = testedClass
+          .getSetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION);
 
       if (allowAccessModification) {
         assertTrue(fieldSubject.getField().accessFlags.isPublic());
@@ -72,22 +90,20 @@
 
   @Test
   public void testCompanionProperty_privatePropertyIsAlwaysInlined() throws Exception {
-    String mainClass = addMainToClasspath(
-        "properties.CompanionPropertiesKt", "companionProperties_usePrivateProp");
+    final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
+        "companionProperties_usePrivateProp");
     runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassExists(dexInspector,
-          COMPANION_PROPERTY_CLASS.getOuterClassName());
+      ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
       String propertyName = "privateProp";
       FieldSubject fieldSubject = checkFieldIsPresent(outerClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
 
-      MemberNaming.MethodSignature getterAccessor =
-          new MemberNaming.MethodSignature("access$getPrivateProp$cp", JAVA_LANG_STRING,
-              Collections.emptyList());
-      MemberNaming.MethodSignature setterAccessor =
-          new MemberNaming.MethodSignature("access$setPrivateProp$cp", "void",
-              Collections.singletonList(JAVA_LANG_STRING));
+      MemberNaming.MethodSignature getterAccessor = testedClass
+          .getGetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION);
+      MemberNaming.MethodSignature setterAccessor = testedClass
+          .getSetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION);
       if (allowAccessModification) {
         assertTrue(fieldSubject.getField().accessFlags.isPublic());
 
@@ -104,22 +120,20 @@
 
   @Test
   public void testCompanionProperty_internalPropertyIsAlwaysInlined() throws Exception {
-    String mainClass = addMainToClasspath(
-        "properties.CompanionPropertiesKt", "companionProperties_useInternalProp");
+    final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
+        "companionProperties_useInternalProp");
     runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassExists(dexInspector,
-          COMPANION_PROPERTY_CLASS.getOuterClassName());
+      ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
       String propertyName = "internalProp";
       FieldSubject fieldSubject = checkFieldIsPresent(outerClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
 
-      MemberNaming.MethodSignature getterAccessor =
-          new MemberNaming.MethodSignature("access$getInternalProp$cp", JAVA_LANG_STRING,
-              Collections.emptyList());
-      MemberNaming.MethodSignature setterAccessor =
-          new MemberNaming.MethodSignature("access$setInternalProp$cp", "void",
-              Collections.singletonList(JAVA_LANG_STRING));
+      MemberNaming.MethodSignature getterAccessor = testedClass
+          .getGetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION);
+      MemberNaming.MethodSignature setterAccessor = testedClass
+          .getSetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION);
 
       if (allowAccessModification) {
         assertTrue(fieldSubject.getField().accessFlags.isPublic());
@@ -135,22 +149,20 @@
 
   @Test
   public void testCompanionProperty_publicPropertyIsAlwaysInlined() throws Exception {
+    final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
         "companionProperties_usePublicProp");
     runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassExists(dexInspector,
-          COMPANION_PROPERTY_CLASS.getOuterClassName());
+      ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
       String propertyName = "publicProp";
       FieldSubject fieldSubject = checkFieldIsPresent(outerClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
 
-      MemberNaming.MethodSignature getterAccessor =
-          new MemberNaming.MethodSignature("access$getPublicProp$cp", JAVA_LANG_STRING,
-              Collections.emptyList());
-      MemberNaming.MethodSignature setterAccessor =
-          new MemberNaming.MethodSignature("access$setPublicProp$cp", "void",
-              Collections.singletonList(JAVA_LANG_STRING));
+      MemberNaming.MethodSignature getterAccessor = testedClass
+          .getGetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION);
+      MemberNaming.MethodSignature setterAccessor = testedClass
+          .getSetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION);
 
       if (allowAccessModification) {
         assertTrue(fieldSubject.getField().accessFlags.isPublic());
@@ -165,28 +177,98 @@
   }
 
   @Test
+  public void testCompanionLateInitProperty_privatePropertyIsAlwaysInlined() throws Exception {
+    final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
+        "companionLateInitProperties_usePrivateLateInitProp");
+    runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
+      String propertyName = "privateLateInitProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(outerClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getterAccessor = testedClass
+          .getGetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION);
+      MemberNaming.MethodSignature setterAccessor = testedClass
+          .getSetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION);
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+        checkMethodIsAbsent(outerClass, getterAccessor);
+        checkMethodIsAbsent(outerClass, setterAccessor);
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+        checkMethodIsPresent(outerClass, getterAccessor);
+        checkMethodIsPresent(outerClass, setterAccessor);
+      }
+    });
+  }
+
+  @Test
+  public void testCompanionLateInitProperty_internalPropertyIsAlwaysInlined() throws Exception {
+    final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
+        "companionLateInitProperties_useInternalLateInitProp");
+    runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
+      String propertyName = "internalLateInitProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(outerClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getterAccessor = testedClass
+          .getGetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION);
+      MemberNaming.MethodSignature setterAccessor = testedClass
+          .getSetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION);
+
+      assertTrue(fieldSubject.getField().accessFlags.isPublic());
+      checkMethodIsAbsent(outerClass, getterAccessor);
+      checkMethodIsAbsent(outerClass, setterAccessor);
+    });
+  }
+
+  @Test
+  public void testCompanionLateInitProperty_publicPropertyIsAlwaysInlined() throws Exception {
+    final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
+        "companionLateInitProperties_usePublicLateInitProp");
+    runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
+      String propertyName = "publicLateInitProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(outerClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getterAccessor = testedClass
+          .getGetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION);
+      MemberNaming.MethodSignature setterAccessor = testedClass
+          .getSetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION);
+
+      assertTrue(fieldSubject.getField().accessFlags.isPublic());
+      checkMethodIsAbsent(outerClass, getterAccessor);
+      checkMethodIsAbsent(outerClass, setterAccessor);
+    });
+  }
+
+  @Test
   public void testAccessor() throws Exception {
+    TestKotlinCompanionClass testedClass = ACCESSOR_COMPANION_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("accessors.AccessorKt",
-        "accessor_accessCompanionPrivate");
+        "accessor_accessPropertyFromCompanionClass");
     runTest("accessors", mainClass, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
-      TestKotlinCompanionClass testedClass = ACCESSOR_COMPANION_PROPERTY_CLASS;
-      ClassSubject outerClass = checkClassExists(dexInspector,
-          testedClass.getOuterClassName());
-      ClassSubject companionClass = checkClassExists(dexInspector,
-          testedClass.getClassName());
+      ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
+      ClassSubject companionClass = checkClassExists(dexInspector, testedClass.getClassName());
       String propertyName = "property";
       FieldSubject fieldSubject = checkFieldIsPresent(outerClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
 
       // The getter is always inlined since it just calls into the accessor.
-      MemberNaming.MethodSignature getter = testedClass
-          .getGetterForProperty(propertyName);
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
       checkMethodIsAbsent(companionClass, getter);
 
       MemberNaming.MethodSignature getterAccessor =
-          new MemberNaming.MethodSignature("access$getProperty$cp", JAVA_LANG_STRING,
-              Collections.emptyList());
+          testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION);
       if (allowAccessModification) {
         assertTrue(fieldSubject.getField().accessFlags.isPublic());
         checkMethodIsAbsent(outerClass, getterAccessor);
@@ -198,6 +280,169 @@
   }
 
   @Test
+  public void testAccessorFromPrivate() throws Exception {
+    TestKotlinCompanionClass testedClass = ACCESSOR_COMPANION_PROPERTY_CLASS;
+    String mainClass = addMainToClasspath("accessors.AccessorKt",
+        "accessor_accessPropertyFromOuterClass");
+    runTest("accessors", mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
+      ClassSubject companionClass = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "property";
+      FieldSubject fieldSubject = checkFieldIsPresent(outerClass, JAVA_LANG_STRING, propertyName);
+      assertTrue(fieldSubject.getField().accessFlags.isStatic());
+
+      // We cannot inline the getter because we don't know if NPE is preserved.
+      MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
+      checkMethodIsPresent(companionClass, getter);
+
+      // We should always inline the static accessor method.
+      MemberNaming.MethodSignature getterAccessor =
+          testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER);
+      checkMethodIsAbsent(outerClass, getterAccessor);
+
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+      }
+    });
+  }
+
+  @Test
+  public void testAccessorForInnerClassIsRemovedWhenNotUsed() throws Exception {
+    TestKotlinClass testedClass = PROPERTY_ACCESS_FOR_INNER_CLASS;
+    String mainClass = addMainToClasspath(testedClass.className + "Kt",
+        "noUseOfPropertyAccessorFromInnerClass");
+    runTest("accessors", mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject classSubject = checkClassExists(dexInspector, testedClass.getClassName());
+
+      for (String propertyName : testedClass.properties.keySet()) {
+        MemberNaming.MethodSignature getterAccessor =
+            testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER);
+        MemberNaming.MethodSignature setterAccessor =
+            testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER);
+
+        checkMethodIsAbsent(classSubject, getterAccessor);
+        checkMethodIsAbsent(classSubject, setterAccessor);
+      }
+    });
+  }
+
+  @Test
+  @Ignore("b/74103342")
+  public void testPrivatePropertyAccessorForInnerClassCanBeInlined() throws Exception {
+    TestKotlinClass testedClass = PROPERTY_ACCESS_FOR_INNER_CLASS;
+    String mainClass = addMainToClasspath(testedClass.className + "Kt",
+        "usePrivatePropertyAccessorFromInnerClass");
+    runTest("accessors", mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject classSubject = checkClassExists(dexInspector, testedClass.getClassName());
+
+      String propertyName = "privateProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(classSubject, JAVA_LANG_STRING,
+          propertyName);
+      assertFalse(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getterAccessor =
+          testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER);
+      MemberNaming.MethodSignature setterAccessor =
+          testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER);
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+        checkMethodIsAbsent(classSubject, getterAccessor);
+        checkMethodIsAbsent(classSubject, setterAccessor);
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+        checkMethodIsPresent(classSubject, getterAccessor);
+        checkMethodIsPresent(classSubject, setterAccessor);
+      }
+    });
+  }
+
+  @Test
+  @Ignore("b/74103342")
+  public void testPrivateLateInitPropertyAccessorForInnerClassCanBeInlined() throws Exception {
+    TestKotlinClass testedClass = PROPERTY_ACCESS_FOR_INNER_CLASS;
+    String mainClass = addMainToClasspath(testedClass.className + "Kt",
+        "usePrivateLateInitPropertyAccessorFromInnerClass");
+    runTest("accessors", mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject classSubject = checkClassExists(dexInspector, testedClass.getClassName());
+
+      String propertyName = "privateLateInitProp";
+      FieldSubject fieldSubject = checkFieldIsPresent(classSubject, JAVA_LANG_STRING,
+          propertyName);
+      assertFalse(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getterAccessor =
+          testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER);
+      MemberNaming.MethodSignature setterAccessor =
+          testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER);
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+        checkMethodIsAbsent(classSubject, getterAccessor);
+        checkMethodIsAbsent(classSubject, setterAccessor);
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+        checkMethodIsPresent(classSubject, getterAccessor);
+        checkMethodIsPresent(classSubject, setterAccessor);
+      }
+    });
+  }
+
+  @Test
+  @Ignore("b/74103342")
+  public void testAccessorForLambdaIsRemovedWhenNotUsed() throws Exception {
+    TestKotlinClass testedClass = PROPERTY_ACCESS_FOR_LAMBDA_CLASS;
+    String mainClass = addMainToClasspath(testedClass.className + "Kt",
+        "noUseOfPropertyAccessorFromLambda");
+    runTest("accessors", mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject classSubject = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "property";
+
+      MemberNaming.MethodSignature getterAccessor =
+          testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_LAMBDA);
+      MemberNaming.MethodSignature setterAccessor =
+          testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_LAMBDA);
+
+      checkMethodIsAbsent(classSubject, getterAccessor);
+      checkMethodIsAbsent(classSubject, setterAccessor);
+    });
+  }
+
+  @Test
+  @Ignore("b/74103342")
+  public void testAccessorForLambdaCanBeInlined() throws Exception {
+    TestKotlinClass testedClass = PROPERTY_ACCESS_FOR_LAMBDA_CLASS;
+    String mainClass = addMainToClasspath(testedClass.className + "Kt",
+        "usePropertyAccessorFromLambda");
+    runTest("accessors", mainClass, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject classSubject = checkClassExists(dexInspector, testedClass.getClassName());
+      String propertyName = "property";
+      FieldSubject fieldSubject = checkFieldIsPresent(classSubject, JAVA_LANG_STRING, propertyName);
+      assertFalse(fieldSubject.getField().accessFlags.isStatic());
+
+      MemberNaming.MethodSignature getterAccessor =
+          testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_LAMBDA);
+      MemberNaming.MethodSignature setterAccessor =
+          testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_LAMBDA);
+      if (allowAccessModification) {
+        assertTrue(fieldSubject.getField().accessFlags.isPublic());
+        checkMethodIsAbsent(classSubject, getterAccessor);
+        checkMethodIsAbsent(classSubject, setterAccessor);
+      } else {
+        assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+        checkMethodIsPresent(classSubject, getterAccessor);
+        checkMethodIsPresent(classSubject, setterAccessor);
+      }
+    });
+  }
+
+  @Test
   public void testStaticFieldAccessorWithJasmin() throws Exception {
     JasminBuilder jasminBuilder = new JasminBuilder();
     ClassBuilder classBuilder = jasminBuilder.addClass("Foo");
@@ -228,13 +473,11 @@
     if (javaResult.exitCode != 0) {
       System.err.println(javaResult.stderr);
       Assert.fail();
-    } else {
-      System.out.println(javaResult.stdout);
     }
 
     AndroidApp app = compileWithR8(jasminBuilder.build(),
         keepMainProguardConfiguration("Foo") + "\n-dontobfuscate");
     String artOutput = runOnArt(app, "Foo");
-    System.out.println(artOutput);
+    assertEquals(javaResult.stdout, artOutput);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/TestKotlinClass.java b/src/test/java/com/android/tools/r8/kotlin/TestKotlinClass.java
index d364bb8..0ec0f64 100644
--- a/src/test/java/com/android/tools/r8/kotlin/TestKotlinClass.java
+++ b/src/test/java/com/android/tools/r8/kotlin/TestKotlinClass.java
@@ -5,8 +5,10 @@
 package com.android.tools.r8.kotlin;
 
 import com.android.tools.r8.naming.MemberNaming;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -32,6 +34,22 @@
     PRIVATE;
   }
 
+  enum AccessorKind {
+    FROM_COMPANION("cp"),
+    FROM_INNER("p"),
+    FROM_LAMBDA("lp");
+
+    private final String accessorSuffix;
+
+    AccessorKind(String accessorSuffix) {
+      this.accessorSuffix = accessorSuffix;
+    }
+
+    public String getAccessorSuffix() {
+      return accessorSuffix;
+    }
+  }
+
   protected static class KotlinProperty {
     private final String name;
     private final String type;
@@ -87,34 +105,74 @@
   public MemberNaming.MethodSignature getGetterForProperty(String propertyName) {
     KotlinProperty property = getProperty(propertyName);
     String type = property.type;
-    String getterName;
-    if (propertyName.length() > 2 && propertyName.startsWith("is")
-        && (propertyName.charAt(2) == '_' || Character.isUpperCase(propertyName.charAt(2)))) {
-      // Getter for property "isAbc" is "isAbc".
-      getterName = propertyName;
-    } else {
-      // Getter for property "abc" is "getAbc".
-      getterName =
-          "get" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
-    }
+    String getterName = computeGetterName(propertyName);
     if (property.getVisibility() == Visibility.INTERNAL) {
-      // Append module name
-      getterName += "$" + KOTLIN_MODULE_NAME;
+      getterName = appendInternalSuffix(getterName);
     }
     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);
+    String setterName = computeSetterName(propertyName);
     if (property.getVisibility() == Visibility.INTERNAL) {
-      // Append module name
-      setterName += "$" + KOTLIN_MODULE_NAME;
+      setterName = appendInternalSuffix(setterName);
     }
     return new MemberNaming.MethodSignature(setterName, "void",
         Collections.singleton(property.getType()));
   }
 
+  public MemberNaming.MethodSignature getGetterAccessorForProperty(String propertyName,
+      AccessorKind accessorKind) {
+    KotlinProperty property = getProperty(propertyName);
+    String getterName = computeGetterName(propertyName);
+    // Unlike normal getter, module name is not appended for accessor method of internal property.
+    getterName = wrapWithAccessorPrefixAndSuffix(accessorKind, getterName);
+    List<String> argumentTypes;
+    if (accessorKind != AccessorKind.FROM_COMPANION) {
+      argumentTypes = ImmutableList.of(getClassName());
+    } else {
+      argumentTypes = ImmutableList.of();
+    }
+    return new MemberNaming.MethodSignature(getterName, property.type, argumentTypes);
+  }
+
+  public MemberNaming.MethodSignature getSetterAccessorForProperty(String propertyName,
+      AccessorKind accessorKind) {
+    KotlinProperty property = getProperty(propertyName);
+    String setterName = computeSetterName(propertyName);
+    // Unlike normal setter, module name is not appended for accessor method of internal property.
+    setterName = wrapWithAccessorPrefixAndSuffix(accessorKind, setterName);
+    List<String> argumentTypes;
+    if (accessorKind != AccessorKind.FROM_COMPANION) {
+      argumentTypes = ImmutableList.of(getClassName(), property.getType());
+    } else {
+      argumentTypes = ImmutableList.of(property.getType());
+    }
+    return new MemberNaming.MethodSignature(setterName, "void", argumentTypes);
+  }
+
+  private static String computeGetterName(String propertyName) {
+    if (propertyName.length() > 2 && propertyName.startsWith("is")
+        && (propertyName.charAt(2) == '_' || Character.isUpperCase(propertyName.charAt(2)))) {
+      // Getter for property "isAbc" is "isAbc".
+      return propertyName;
+    } else {
+      // Getter for property "abc" is "getAbc".
+      return "get" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
+    }
+  }
+
+  private static String computeSetterName(String propertyName) {
+    return "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
+  }
+
+  private static String appendInternalSuffix(String name) {
+    return name + "$" + KOTLIN_MODULE_NAME;
+  }
+
+  private String wrapWithAccessorPrefixAndSuffix(AccessorKind accessorKind, String methodName) {
+    return "access$" + methodName + "$" + accessorKind.getAccessorSuffix();
+  }
+
 }
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
index dce8880..2f53fd9 100644
--- a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
@@ -13,9 +13,8 @@
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.ToolHelper.DexVm.Kind;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.origin.Origin;
@@ -25,38 +24,14 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
-@RunWith(Parameterized.class)
+@RunWith(VmTestRunner.class)
 public class OverloadAggressivelyTest extends TestBase {
-  private final DexVm dexVm;
-  private final boolean overloadaggressively;
 
-  public OverloadAggressivelyTest(DexVm dexVm, boolean overloadaggressively) {
-    this.dexVm = dexVm;
-    this.overloadaggressively = overloadaggressively;
-  }
-
-  @Parameters(name = "vm: {0}, overloadaggressively: {1}")
-  public static Collection<Object[]> data() {
-    List<Object[]> testCases = new ArrayList<>();
-    for (DexVm version : DexVm.values()) {
-      if (version.getKind() == Kind.HOST) {
-        testCases.add(new Object[]{version, true});
-        testCases.add(new Object[]{version, false});
-      }
-    }
-    return testCases;
-  }
-
-  private AndroidApp runR8(AndroidApp app, Class main, Path out) throws Exception {
+  private AndroidApp runR8(AndroidApp app, Class main, Path out, boolean overloadaggressively)
+      throws Exception {
      R8Command command =
         ToolHelper.addProguardConfigurationConsumer(
             ToolHelper.prepareR8CommandBuilder(app),
@@ -75,9 +50,7 @@
     return ToolHelper.runR8(command, o -> o.enableInlining = false);
   }
 
-  @Test
-  public void fieldUpdater() throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
+  private void fieldUpdater(boolean overloadaggressively) throws Exception {
     byte[][] classes = {
         ToolHelper.getClassAsBytes(FieldUpdater.class),
         ToolHelper.getClassAsBytes(A.class),
@@ -85,7 +58,7 @@
     };
     AndroidApp originalApp = buildAndroidApp(classes);
     Path out = temp.getRoot().toPath();
-    AndroidApp processedApp = runR8(originalApp, FieldUpdater.class, out);
+    AndroidApp processedApp = runR8(originalApp, FieldUpdater.class, out, overloadaggressively);
 
     DexInspector dexInspector = new DexInspector(processedApp);
     ClassSubject a = dexInspector.clazz(A.class.getCanonicalName());
@@ -106,7 +79,7 @@
     String main = FieldUpdater.class.getCanonicalName();
     ProcessResult javaOutput = runOnJava(main, classes);
     assertEquals(0, javaOutput.exitCode);
-    ProcessResult artOutput = runOnArtRaw(processedApp, main, dexVm);
+    ProcessResult artOutput = runOnArtRaw(processedApp, main);
     // TODO(b/72858955): eventually, R8 should avoid this field resolution conflict.
     if (overloadaggressively) {
       assertNotEquals(0, artOutput.exitCode);
@@ -120,8 +93,16 @@
   }
 
   @Test
-  public void fieldResolution() throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
+  public void testFieldUpdater_aggressively() throws Exception {
+    fieldUpdater(true);
+  }
+
+  @Test
+  public void testFieldUpdater_not_aggressively() throws Exception {
+    fieldUpdater(false);
+  }
+
+  private void fieldResolution(boolean overloadaggressively) throws Exception {
     byte[][] classes = {
         ToolHelper.getClassAsBytes(FieldResolution.class),
         ToolHelper.getClassAsBytes(A.class),
@@ -129,7 +110,7 @@
     };
     AndroidApp originalApp = buildAndroidApp(classes);
     Path out = temp.getRoot().toPath();
-    AndroidApp processedApp = runR8(originalApp, FieldResolution.class, out);
+    AndroidApp processedApp = runR8(originalApp, FieldResolution.class, out, overloadaggressively);
 
     DexInspector dexInspector = new DexInspector(processedApp);
     ClassSubject a = dexInspector.clazz(A.class.getCanonicalName());
@@ -144,7 +125,7 @@
     String main = FieldResolution.class.getCanonicalName();
     ProcessResult javaOutput = runOnJava(main, classes);
     assertEquals(0, javaOutput.exitCode);
-    ProcessResult artOutput = runOnArtRaw(processedApp, main, dexVm);
+    ProcessResult artOutput = runOnArtRaw(processedApp, main);
     // TODO(b/72858955): R8 should avoid field resolution conflict even w/ -overloadaggressively.
     if (overloadaggressively) {
       assertNotEquals(0, artOutput.exitCode);
@@ -158,15 +139,23 @@
   }
 
   @Test
-  public void methodResolution() throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
+  public void testFieldResolution_aggressively() throws Exception {
+    fieldResolution(true);
+  }
+
+  @Test
+  public void testFieldResolution_not_aggressively() throws Exception {
+    fieldResolution(false);
+  }
+
+  private void methodResolution(boolean overloadaggressively) throws Exception {
     byte[][] classes = {
         ToolHelper.getClassAsBytes(MethodResolution.class),
         ToolHelper.getClassAsBytes(B.class)
     };
     AndroidApp originalApp = buildAndroidApp(classes);
     Path out = temp.getRoot().toPath();
-    AndroidApp processedApp = runR8(originalApp, MethodResolution.class, out);
+    AndroidApp processedApp = runR8(originalApp, MethodResolution.class, out, overloadaggressively);
 
     DexInspector dexInspector = new DexInspector(processedApp);
     ClassSubject b = dexInspector.clazz(B.class.getCanonicalName());
@@ -188,7 +177,7 @@
     String main = MethodResolution.class.getCanonicalName();
     ProcessResult javaOutput = runOnJava(main, classes);
     assertEquals(0, javaOutput.exitCode);
-    ProcessResult artOutput = runOnArtRaw(processedApp, main, dexVm);
+    ProcessResult artOutput = runOnArtRaw(processedApp, main);
     // TODO(b/72858955): R8 should avoid method resolution conflict even w/ -overloadaggressively.
     if (overloadaggressively) {
       assertEquals(0, artOutput.exitCode);
@@ -200,4 +189,14 @@
       // assertEquals(javaOutput.stderr.trim(), artOutput.stderr.trim());
     }
   }
+
+  @Test
+  public void testMethodResolution_aggressively() throws Exception {
+    methodResolution(true);
+  }
+
+  @Test
+  public void testMethodResolution_not_aggressively() throws Exception {
+    methodResolution(false);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
index a510cee..3e5ba7c 100644
--- a/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
@@ -8,10 +8,8 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.ToolHelper.DexVm.Kind;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.jasmin.JasminTestBase;
@@ -22,39 +20,17 @@
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
-import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
-@RunWith(Parameterized.class)
+@RunWith(VmTestRunner.class)
 public class ValidNameConflictTest extends JasminTestBase {
   private final String CLASS_NAME = "Example";
   private final String SUPER_CLASS = "Super";
   private final String ANOTHER_CLASS = "Test";
   private final String MSG = "Expected to be seen at the end.";
 
-  private final DexVm dexVm;
-
-  public ValidNameConflictTest(DexVm dexVm) {
-    this.dexVm = dexVm;
-  }
-
-  @Parameters(name = "vm: {0}")
-  public static Collection<Object> data() {
-    List<Object> testCases = new ArrayList<>();
-    for (DexVm version : DexVm.values()) {
-      if (version.getKind() == Kind.HOST) {
-        testCases.add(version);
-      }
-    }
-    return testCases;
-  }
-
   private Iterable<String> buildCodeForVisitingDeclaredMembers(
       Iterable<String> prologue, Iterable<String> argumentLoadingAndCall) {
     return Iterables.concat(
@@ -105,7 +81,6 @@
 
   @Test
   public void remainFieldNameConflict_keepRules() throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
     JasminBuilder builder = buildFieldNameConflictClassFile();
     ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
     assertEquals(0, javaOutput.exitCode);
@@ -130,14 +105,13 @@
     assertFalse(f2.isRenamed());
     assertEquals(f1.getFinalName(), f2.getFinalName());
 
-    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
 
   @Test
   public void remainFieldNameConflict_useuniqueclassmembernames() throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
     JasminBuilder builder = buildFieldNameConflictClassFile();
     ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
     assertEquals(0, javaOutput.exitCode);
@@ -159,7 +133,7 @@
     assertTrue(f2.isRenamed());
     assertEquals(f1.getFinalName(), f2.getFinalName());
 
-    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
@@ -167,7 +141,6 @@
   @Test
   public void remainFieldNameConflict_useuniqueclassmembernames_overloadaggressively()
       throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
     JasminBuilder builder = buildFieldNameConflictClassFile();
     ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
     assertEquals(0, javaOutput.exitCode);
@@ -190,14 +163,13 @@
     assertTrue(f2.isRenamed());
     assertEquals(f1.getFinalName(), f2.getFinalName());
 
-    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
 
   @Test
   public void resolveFieldNameConflict_no_options() throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
     JasminBuilder builder = buildFieldNameConflictClassFile();
     ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
     assertEquals(0, javaOutput.exitCode);
@@ -218,14 +190,13 @@
     assertTrue(f2.isRenamed());
     assertNotEquals(f1.getFinalName(), f2.getFinalName());
 
-    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
 
   @Test
   public void remainFieldNameConflict_overloadaggressively() throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
     JasminBuilder builder = buildFieldNameConflictClassFile();
     ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
     assertEquals(0, javaOutput.exitCode);
@@ -247,7 +218,7 @@
     assertTrue(f2.isRenamed());
     assertEquals(f1.getFinalName(), f2.getFinalName());
 
-    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
@@ -280,7 +251,6 @@
 
   @Test
   public void remainMethodNameConflict_keepRules() throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
     JasminBuilder builder = buildMethodNameConflictClassFile();
     ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
     assertEquals(0, javaOutput.exitCode);
@@ -304,14 +274,13 @@
     assertFalse(m2.isRenamed());
     assertEquals(m1.getFinalName(), m2.getFinalName());
 
-    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
 
   @Test
   public void remainMethodNameConflict_useuniqueclassmembernames() throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
     JasminBuilder builder = buildMethodNameConflictClassFile();
     ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
     assertEquals(0, javaOutput.exitCode);
@@ -333,7 +302,7 @@
     assertTrue(m2.isRenamed());
     assertEquals(m1.getFinalName(), m2.getFinalName());
 
-    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
@@ -341,7 +310,6 @@
   @Test
   public void remainMethodNameConflict_useuniqueclassmembernames_overloadaggressively()
       throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
     JasminBuilder builder = buildMethodNameConflictClassFile();
     ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
     assertEquals(0, javaOutput.exitCode);
@@ -364,14 +332,13 @@
     assertTrue(m2.isRenamed());
     assertEquals(m1.getFinalName(), m2.getFinalName());
 
-    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
 
   @Test
   public void resolveMethodNameConflict_no_options() throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
     JasminBuilder builder = buildMethodNameConflictClassFile();
     ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
     assertEquals(0, javaOutput.exitCode);
@@ -392,14 +359,13 @@
     assertTrue(m2.isRenamed());
     assertNotEquals(m1.getFinalName(), m2.getFinalName());
 
-    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
 
   @Test
   public void remainMethodNameConflict_overloadaggressively() throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
     JasminBuilder builder = buildMethodNameConflictClassFile();
     ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
     assertEquals(0, javaOutput.exitCode);
@@ -421,7 +387,7 @@
     assertTrue(m2.isRenamed());
     assertEquals(m1.getFinalName(), m2.getFinalName());
 
-    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
@@ -467,7 +433,6 @@
 
   @Test
   public void remainMethodNameConflictInHierarchy_keepRules() throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
     JasminBuilder builder = buildMethodNameConflictInHierarchy();
     ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
     assertEquals(0, javaOutput.exitCode);
@@ -505,14 +470,13 @@
     assertEquals(m1.getFinalName(), subM1.getFinalName());
     assertEquals(m2.getFinalName(), subM2.getFinalName());
 
-    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
 
   @Test
   public void remainMethodNameConflictInHierarchy_useuniqueclassmembernames() throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
     JasminBuilder builder = buildMethodNameConflictInHierarchy();
     ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
     assertEquals(0, javaOutput.exitCode);
@@ -548,7 +512,7 @@
     assertEquals(m1.getFinalName(), subM1.getFinalName());
     assertEquals(m2.getFinalName(), subM2.getFinalName());
 
-    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
@@ -556,7 +520,6 @@
   @Test
   public void remainMethodNameConflictInHierarchy_useuniqueclassmembernames_overloadaggressively()
       throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
     JasminBuilder builder = buildMethodNameConflictInHierarchy();
     ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
     assertEquals(0, javaOutput.exitCode);
@@ -593,14 +556,13 @@
     assertEquals(m1.getFinalName(), subM1.getFinalName());
     assertEquals(m2.getFinalName(), subM2.getFinalName());
 
-    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
 
   @Test
   public void resolveMethodNameConflictInHierarchy_no_options() throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
     JasminBuilder builder = buildMethodNameConflictInHierarchy();
     ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
     assertEquals(0, javaOutput.exitCode);
@@ -635,14 +597,13 @@
     assertEquals(m1.getFinalName(), subM1.getFinalName());
     assertEquals(m2.getFinalName(), subM2.getFinalName());
 
-    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
 
   @Test
   public void remainMethodNameConflictInHierarchy_overloadaggressively() throws Exception {
-    Assume.assumeTrue(ToolHelper.artSupported());
     JasminBuilder builder = buildMethodNameConflictInHierarchy();
     ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
     assertEquals(0, javaOutput.exitCode);
@@ -679,7 +640,7 @@
     assertEquals(m1.getFinalName(), subM1.getFinalName());
     assertEquals(m2.getFinalName(), subM2.getFinalName());
 
-    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
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 5886829..4681d7d 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
@@ -25,6 +25,7 @@
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.shaking.ProguardKeepRule;
+import com.android.tools.r8.shaking.ProguardKeepRuleType;
 import com.android.tools.r8.shaking.ProguardMemberRule;
 import com.android.tools.r8.shaking.ProguardMemberType;
 import com.android.tools.r8.shaking.forceproguardcompatibility.defaultmethods.ClassImplementingInterface;
@@ -274,6 +275,7 @@
       configuration.getRules().forEach(rule -> {
         assertTrue(rule instanceof ProguardKeepRule);
         ProguardKeepRule keepRule = (ProguardKeepRule) rule;
+        assertEquals(ProguardKeepRuleType.KEEP, keepRule.getType());
         assertTrue(keepRule.getModifiers().allowsObfuscation);
         assertTrue(keepRule.getModifiers().allowsOptimization);
         Set<ProguardMemberRule> memberRules = rule.getMemberRules();
@@ -372,6 +374,7 @@
       configuration.getRules().forEach(rule -> {
         assertTrue(rule instanceof ProguardKeepRule);
         ProguardKeepRule keepRule = (ProguardKeepRule) rule;
+        assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS, keepRule.getType());
         assertTrue(keepRule.getModifiers().allowsObfuscation);
         assertTrue(keepRule.getModifiers().allowsOptimization);
         Set<ProguardMemberRule> memberRules = rule.getMemberRules();
@@ -477,6 +480,7 @@
       configuration.getRules().forEach(rule -> {
         assertTrue(rule instanceof ProguardKeepRule);
         ProguardKeepRule keepRule = (ProguardKeepRule) rule;
+        assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS, keepRule.getType());
         assertTrue(keepRule.getModifiers().allowsObfuscation);
         assertTrue(keepRule.getModifiers().allowsOptimization);
         Set<ProguardMemberRule> memberRules = rule.getMemberRules();
diff --git a/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java b/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
index a8ead8a..eff632a 100644
--- a/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
+++ b/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
@@ -65,10 +65,30 @@
 
   @Test
   public void testCatchAllWildcards() throws Exception {
+    testBaseWildcard(true);
+    testBaseWildcard(false);
+    testNonBaseCatchAll();
+  }
+
+  private void testNonBaseCatchAll() throws FeatureMappingException {
     List<String> lines =
         ImmutableList.of(
             "com.google.Feature1:feature1",
-            "*:base",
+            "*:nonbase",
+            "com.strange.*:feature2");
+    FeatureClassMapping mapping = new FeatureClassMapping(lines);
+    assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
+    assertEquals(mapping.featureForClass("com.google.different.Feature1"), "nonbase");
+    assertEquals(mapping.featureForClass("com.strange.different.Feature1"), "feature2");
+    assertEquals(mapping.featureForClass("Feature1"), "nonbase");
+    assertEquals(mapping.featureForClass("a.b.z.A"), "nonbase");
+  }
+
+  private void testBaseWildcard(boolean explicitBase) throws FeatureMappingException {
+    List<String> lines =
+        ImmutableList.of(
+            "com.google.Feature1:feature1",
+            explicitBase ? "*:base" : "",
             "com.strange.*:feature2");
     FeatureClassMapping mapping = new FeatureClassMapping(lines);
     assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
diff --git a/src/test/kotlinR8TestResources/accessors/Accessor.kt b/src/test/kotlinR8TestResources/accessors/Accessor.kt
index e88a055..ffd0395 100644
--- a/src/test/kotlinR8TestResources/accessors/Accessor.kt
+++ b/src/test/kotlinR8TestResources/accessors/Accessor.kt
@@ -8,12 +8,20 @@
     companion object {
         private val property = "foo"
 
-        fun printProperty() {
+        fun accessPropertyFromCompanionClass() {
             println(property)
         }
     }
+
+    fun accessPropertyFromOuterClass() {
+        println(property)
+    }
 }
 
-fun accessor_accessCompanionPrivate() {
-    Accessor.printProperty()
+fun accessor_accessPropertyFromCompanionClass() {
+    Accessor.accessPropertyFromCompanionClass()
+}
+
+fun accessor_accessPropertyFromOuterClass() {
+    Accessor().accessPropertyFromOuterClass()
 }
\ No newline at end of file
diff --git a/src/test/kotlinR8TestResources/accessors/PropertyAccessorForInnerClass.kt b/src/test/kotlinR8TestResources/accessors/PropertyAccessorForInnerClass.kt
new file mode 100644
index 0000000..a2dd823
--- /dev/null
+++ b/src/test/kotlinR8TestResources/accessors/PropertyAccessorForInnerClass.kt
@@ -0,0 +1,44 @@
+// 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 accessors
+
+class PropertyAccessorForInnerClass {
+    private var privateProp = "private"
+
+    private lateinit var privateLateInitProp: String
+
+    // Causes a class initializer to be added to the class.
+    companion object {
+        public var companionProperty = "static"
+    }
+
+    inner class Inner {
+        fun accessPrivateProperty() {
+            privateProp = "bar"
+            println(privateProp)
+        }
+
+        fun accessPrivateLateInitPropertyStatus() {
+            println(::privateLateInitProp.isInitialized)
+        }
+    }
+}
+
+fun noUseOfPropertyAccessorFromInnerClass() {
+    // Create instance of class to keep them after tree shaking.
+    PropertyAccessorForInnerClass().Inner()
+}
+
+fun usePrivatePropertyAccessorFromInnerClass() {
+    // Creates a non-trivial class initializer
+    println(PropertyAccessorForInnerClass.companionProperty)
+    PropertyAccessorForInnerClass().Inner().accessPrivateProperty()
+}
+
+fun usePrivateLateInitPropertyAccessorFromInnerClass() {
+    // Creates a non-trivial class initializer
+    println(PropertyAccessorForInnerClass.companionProperty)
+    PropertyAccessorForInnerClass().Inner().accessPrivateLateInitPropertyStatus()
+}
\ No newline at end of file
diff --git a/src/test/kotlinR8TestResources/accessors/PropertyAccessorForLambda.kt b/src/test/kotlinR8TestResources/accessors/PropertyAccessorForLambda.kt
new file mode 100644
index 0000000..28bed0a
--- /dev/null
+++ b/src/test/kotlinR8TestResources/accessors/PropertyAccessorForLambda.kt
@@ -0,0 +1,33 @@
+// 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 accessors
+
+class PropertyAccessorForLambda {
+    private var property: String = "foo"
+        get() = { field }()
+        set(v) = { field = v }()
+
+    // Causes a class initializer to be added to the class.
+    companion object {
+        public var companionProperty = "static"
+    }
+
+    fun accessPropertyOfOuterClass() {
+        // Access to the property requires to go through an accessor method, respectively
+        // named "access$getProperty$lp" for getter and "access$setProperty$lp" for setter).
+        property = "bar"
+        println(property)
+    }
+}
+
+fun noUseOfPropertyAccessorFromLambda() {
+    // Create instance of class to keep them after tree shaking.
+    PropertyAccessorForLambda()
+}
+
+fun usePropertyAccessorFromLambda() {
+    PropertyAccessorForLambda.companionProperty = "fake"
+    PropertyAccessorForLambda().accessPropertyOfOuterClass()
+}
\ No newline at end of file