Merge "Use non-zero exit code if run_on_app.py fails"
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index 81e79e6..09bc9cb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -364,7 +364,7 @@
       castInstruction.setPosition(invoke.getPosition());
 
       // Splice in the check cast operation.
-      if (entryBlock.canThrow() || invokeBlock.hasCatchHandlers()) {
+      if (entryBlock.canThrow()) {
         // Since the cast-instruction may also fail we need to create a new block for the cast.
         //
         // Note that the downcast of the receiver is made at the call site, so we need to copy the
@@ -410,13 +410,7 @@
 
     BasicBlock inlineExit = null;
     ImmutableList<BasicBlock> normalExits = inlinee.computeNormalExitBlocks();
-    if (normalExits.isEmpty()) {
-      assert inlineeCanThrow;
-      // TODO(sgjesse): Remove this restriction (see b/64432527).
-      assert !invokeBlock.hasCatchHandlers();
-      blocksToRemove.addAll(
-          invokePredecessor.unlink(invokeBlock, new DominatorTree(code)));
-    } else {
+    if (!normalExits.isEmpty()) {
       // Ensure and locate the single return instruction of the inlinee.
       InstructionListIterator inlineeIterator = ensureSingleReturnInstruction(inlinee, normalExits);
 
@@ -475,6 +469,25 @@
       appendCatchHandlers(code, invokeBlock, inlinee, blocksIterator);
     }
 
+    // If there are no normal exists, then unlink the invoke block and all the blocks that it
+    // dominates. This must be done after the catch handlers have been appended to the inlinee,
+    // since the catch handlers are dominated by the inline block until then (meaning that the
+    // catch handlers would otherwise be removed although they are not actually dead).
+    if (normalExits.isEmpty()) {
+      assert inlineeCanThrow;
+      blocksToRemove.addAll(invokePredecessor.unlink(invokeBlock, new DominatorTree(code)));
+    }
+
+    // Position the iterator after the invoke block.
+    blocksIterator.next();
+    assert IteratorUtils.peekPrevious(blocksIterator) == invokeBlock;
+
+    // Check that the successor of the invoke block is still to be processed,
+    final BasicBlock finalInvokeSuccessor = invokeSuccessor;
+    assert invokeSuccessor == invokeBlock
+        || IteratorUtils.anyRemainingMatch(
+            blocksIterator, remaining -> remaining == finalInvokeSuccessor);
+
     return invokeSuccessor;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 5f62c6c..8e91a62 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -192,8 +192,6 @@
   private boolean inPrelude;
   private Int2ObjectMap<DebugLocalInfo> incomingLocals;
   private Int2ObjectMap<DebugLocalInfo> outgoingLocals;
-  private Int2ReferenceMap<Int2ObjectMap<DebugLocalInfo>> definitelyLiveIncomingLocals =
-      new Int2ReferenceOpenHashMap<>();
   private Int2ReferenceMap<CfState.Snapshot> incomingState = new Int2ReferenceOpenHashMap<>();
   private final CanonicalPositions canonicalPositions;
 
@@ -371,38 +369,21 @@
     if (predecessorOffset == IRBuilder.INITIAL_BLOCK_OFFSET) {
       return;
     }
-    if (currentInstructionIndex != predecessorOffset) {
-      // If transfer is not still in the same block, then update the state to that of the successor.
-      // The builder's lookup of local variables relies on this state for starting locals.
-      currentInstructionIndex = successorOffset;
-      state.reset(incomingState.get(currentInstructionIndex), false);
-      setLocalVariableLists();
-      // The transfer has not yet taken place, so the current position is that of the predecessor.
-      int positionOffset = predecessorOffset;
-      List<CfInstruction> instructions = code.getInstructions();
-      CfInstruction instruction = instructions.get(positionOffset);
-      while (0 < positionOffset && !(instruction instanceof CfPosition)) {
-        instruction = instructions.get(--positionOffset);
-      }
-      if (instruction instanceof CfPosition) {
-        CfPosition position = (CfPosition) instruction;
-        state.setPosition(getCanonicalPosition(position.getPosition()));
-      } else {
-        state.setPosition(canonicalPositions.getPreamblePosition());
-      }
-    }
+    // The transfer has not yet taken place, so the current position is that of the predecessor.
+    state.setPosition(getCanonicalDebugPositionAtOffset(predecessorOffset));
+
     // Manually compute the local variable change for the block transfer.
-    LocalVariableList atSource = getLocalVariables(predecessorOffset);
-    LocalVariableList atTarget = getLocalVariables(successorOffset);
+    Int2ObjectMap<DebugLocalInfo> atSource = getLocalVariables(predecessorOffset).locals;
+    Int2ObjectMap<DebugLocalInfo> atTarget = getLocalVariables(successorOffset).locals;
     if (!isExceptional) {
-      for (Entry<DebugLocalInfo> entry : atSource.locals.int2ObjectEntrySet()) {
-        if (atTarget.locals.get(entry.getIntKey()) != entry.getValue()) {
+      for (Entry<DebugLocalInfo> entry : atSource.int2ObjectEntrySet()) {
+        if (atTarget.get(entry.getIntKey()) != entry.getValue()) {
           builder.addDebugLocalEnd(entry.getIntKey(), entry.getValue());
         }
       }
     }
-    for (Entry<DebugLocalInfo> entry : atTarget.locals.int2ObjectEntrySet()) {
-      if (atSource.locals.get(entry.getIntKey()) != entry.getValue()) {
+    for (Entry<DebugLocalInfo> entry : atTarget.int2ObjectEntrySet()) {
+      if (atSource.get(entry.getIntKey()) != entry.getValue()) {
         builder.addDebugLocalStart(entry.getIntKey(), entry.getValue());
       }
     }
@@ -562,33 +543,15 @@
 
   private void setLocalVariableLists() {
     incomingLocals = getLocalVariables(currentInstructionIndex).locals;
-    CfInstruction currentInstruction = code.getInstructions().get(currentInstructionIndex);
     if (inPrelude) {
       outgoingLocals = incomingLocals;
       return;
     }
-    if (currentInstruction.isReturn() || currentInstruction instanceof CfThrow) {
-      outgoingLocals = emptyMap();
-      return;
-    }
-    if (isControlFlow(currentInstruction)) {
-      // We need to read all locals that are not live on all successors to ensure liveness.
-      // Determine outgoingLocals as the intersection of all successors' locals.
-      outgoingLocals = null;
-      int[] targets = getTargets(currentInstructionIndex);
-      for (int target : targets) {
-        Int2ObjectMap<DebugLocalInfo> locals = getLocalVariables(target).locals;
-        outgoingLocals = intersectMaps(outgoingLocals, locals);
-      }
-      assert outgoingLocals != null;
-      // Pass outgoingLocals to all successors.
-      for (int target : targets) {
-        Int2ObjectMap<DebugLocalInfo> existing = definitelyLiveIncomingLocals.get(target);
-        definitelyLiveIncomingLocals.put(target, intersectMaps(existing, outgoingLocals));
-      }
-    } else {
-      outgoingLocals = getLocalVariables(currentInstructionIndex + 1).locals;
-    }
+    CfInstruction currentInstruction = code.getInstructions().get(currentInstructionIndex);
+    outgoingLocals =
+        !isControlFlow(currentInstruction)
+            ? getLocalVariables(currentInstructionIndex + 1).locals
+            : emptyMap();
   }
 
   private boolean localsChanged() {
@@ -613,27 +576,6 @@
     }
   }
 
-  private Int2ObjectMap<DebugLocalInfo> intersectMaps(
-      Int2ObjectMap<DebugLocalInfo> existing, Int2ObjectMap<DebugLocalInfo> update) {
-    assert update != null;
-    if (existing == null) {
-      return update;
-    }
-    if (existing.size() > update.size()) {
-      return intersectMaps(update, existing);
-    }
-    if (existing.equals(update)) {
-      return existing;
-    }
-    Int2ObjectOpenHashMap<DebugLocalInfo> result = new Int2ObjectOpenHashMap<>();
-    for (Entry<DebugLocalInfo> local : existing.int2ObjectEntrySet()) {
-      if (local.getValue().equals(update.get(local.getIntKey()))) {
-        result.put(local.getIntKey(), local.getValue());
-      }
-    }
-    return result;
-  }
-
   private boolean isControlFlow(CfInstruction currentInstruction) {
     return currentInstruction.isReturn()
         || currentInstruction.getTarget() != null
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 5c4d581..8fe972b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -706,14 +706,6 @@
     addInstruction(new Argument(value));
   }
 
-  private void addDebugLocalWrite(ValueType type, int dest, Value in) {
-    assert options.debug;
-    Value out = writeRegister(dest, type, ThrowingInfo.NO_THROW);
-    DebugLocalWrite write = new DebugLocalWrite(out, in);
-    assert !write.instructionTypeCanThrow();
-    addInstruction(write);
-  }
-
   private Value getIncomingLocalValue(int register, DebugLocalInfo local) {
     assert options.debug;
     assert local != null;
@@ -729,26 +721,26 @@
   }
 
   public void addDebugLocalStart(int register, DebugLocalInfo local) {
+    assert local != null;
     if (!options.debug) {
       return;
     }
-    assert local != null;
-    assert local == getOutgoingLocal(register) :
-        "local-start mismatch: " + local + " != " + getOutgoingLocal(register)
-            + " at " + currentInstructionOffset
-            + " for source\n" + source.toString();
-    // TODO(b/111251032): Here we lookup a value with type based on debug info. That's just wrong!
-    ValueType valueType = ValueType.fromDexType(local.type);
-    Value incomingValue = readRegisterIgnoreLocal(register, valueType, local);
     // If the local was not introduced by the previous instruction, start it here.
+    Value incomingValue = getIncomingLocalValue(register, local);
     if (incomingValue.getLocalInfo() != local
         || currentBlock.isEmpty()
         || currentBlock.getInstructions().getLast().outValue() != incomingValue) {
-      addDebugLocalWrite(ValueType.fromDexType(local.type), register, incomingValue);
+      // Note that the write register must not lookup outgoing local information and the local is
+      // never considered clobbered by a start (if the in value has local info it must have been
+      // marked ended elsewhere).
+      Value out = writeRegister(register, incomingValue.outType(), ThrowingInfo.NO_THROW, local);
+      DebugLocalWrite write = new DebugLocalWrite(out, incomingValue);
+      addInstruction(write);
     }
   }
 
   public void addDebugLocalEnd(int register, DebugLocalInfo local) {
+    assert local != null;
     if (!options.debug) {
       return;
     }
@@ -951,7 +943,8 @@
       // If the move is writing to a different local we must construct a new value.
       DebugLocalInfo destLocal = getOutgoingLocal(dest);
       if (destLocal != null && destLocal != in.getLocalInfo()) {
-        addDebugLocalWrite(type, dest, in);
+        Value out = writeRegister(dest, type, ThrowingInfo.NO_THROW);
+        addInstruction(new DebugLocalWrite(out, in));
         return;
       }
     }
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 e528a90..1039817 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
@@ -476,6 +476,7 @@
         || successorOffset == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
       return;
     }
+    // The transfer has not yet taken place, so the current position is that of the predecessor.
     currentPosition = getCanonicalDebugPositionAtOffset(predecessorOffset);
 
     LocalChangeAtOffset localChange = state.getLocalChange(predecessorOffset, successorOffset);
@@ -484,12 +485,8 @@
         builder.addDebugLocalEnd(toClose.slot.register, toClose.info);
       }
     }
-    List<Local> localsToOpen = localChange.getLocalsToOpen();
-    if (!localsToOpen.isEmpty()) {
-      state.restoreState(successorOffset);
-      for (Local toOpen : localsToOpen) {
-        builder.addDebugLocalStart(toOpen.slot.register, toOpen.info);
-      }
+    for (Local toOpen : localChange.getLocalsToOpen()) {
+      builder.addDebugLocalStart(toOpen.slot.register, toOpen.info);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 998ebe9..823de39 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.IteratorUtils;
 import java.util.BitSet;
 import java.util.List;
 import java.util.ListIterator;
@@ -366,21 +367,29 @@
   }
 
   @Override
-  public ListIterator<BasicBlock> updateTypeInformationIfNeeded(IRCode inlinee,
-      ListIterator<BasicBlock> blockIterator, BasicBlock block, BasicBlock invokeSuccessor) {
+  public void updateTypeInformationIfNeeded(
+      IRCode inlinee, ListIterator<BasicBlock> blockIterator, BasicBlock block) {
     if (inliner.options.enableNonNullTracking) {
-      // Move the cursor back to where the inlinee blocks are added.
-      blockIterator = code.blocks.listIterator(code.blocks.indexOf(block));
+      BasicBlock state = IteratorUtils.peekNext(blockIterator);
+      // Move the cursor back to where the first inlinee block was added.
+      while (blockIterator.hasPrevious() && blockIterator.previous() != block) {
+        // Do nothing.
+      }
+      assert IteratorUtils.peekNext(blockIterator) == block;
+
       // Kick off the tracker to add non-null IRs only to the inlinee blocks.
-      new NonNullTracker()
-          .addNonNullInPart(code, blockIterator, inlinee.blocks::contains);
-      // Move the cursor forward to where the inlinee blocks end.
-      blockIterator = code.blocks.listIterator(code.blocks.indexOf(invokeSuccessor));
+      new NonNullTracker().addNonNullInPart(code, blockIterator, inlinee.blocks::contains);
+      assert !blockIterator.hasNext();
+
+      // Restore the old state of the iterator.
+      while (blockIterator.hasPrevious() && blockIterator.previous() != state) {
+        // Do nothing.
+      }
+      assert IteratorUtils.peekNext(blockIterator) == state;
     }
     // Update type env for inlined blocks.
     typeEnvironment.analyzeBlocks(inlinee.topologicallySortedBlocks());
     // TODO(b/69964136): need a test where refined env in inlinee affects the caller.
-    return blockIterator;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index 85f7a39..07100fa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -70,10 +70,8 @@
   }
 
   @Override
-  public ListIterator<BasicBlock> updateTypeInformationIfNeeded(IRCode inlinee,
-      ListIterator<BasicBlock> blockIterator, BasicBlock block, BasicBlock invokeSuccessor) {
-    return blockIterator;
-  }
+  public void updateTypeInformationIfNeeded(
+      IRCode inlinee, ListIterator<BasicBlock> blockIterator, BasicBlock block) {}
 
   @Override
   public boolean exceededAllowance() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 106d537..491269e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -591,11 +591,6 @@
                 result.buildInliningIR(code.valueNumberGenerator,
                     appInfo, converter.getGraphLense(), options, invokePosition);
             if (inlinee != null) {
-              // TODO(64432527): Get rid of this additional check by improved inlining.
-              if (block.hasCatchHandlers() && inlinee.computeNormalExitBlocks().isEmpty()) {
-                continue;
-              }
-
               // If this code did not go through the full pipeline, apply inlining to make sure
               // that force inline targets get processed.
               strategy.ensureMethodProcessed(target, inlinee);
@@ -611,10 +606,8 @@
               iterator.previous();
               strategy.markInlined(inlinee);
               if (!strategy.exceededAllowance() || result.ignoreInstructionBudget()) {
-                BasicBlock invokeSuccessor =
-                    iterator.inlineInvoke(code, inlinee, blockIterator, blocksToRemove, downcast);
-                blockIterator = strategy.
-                    updateTypeInformationIfNeeded(inlinee, blockIterator, block, invokeSuccessor);
+                iterator.inlineInvoke(code, inlinee, blockIterator, blocksToRemove, downcast);
+                strategy.updateTypeInformationIfNeeded(inlinee, blockIterator, block);
 
                 // If we inlined the invoke from a bridge method, it is no longer a bridge method.
                 if (method.accessFlags.isBridge()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 129e9cb..de657de 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -190,8 +190,7 @@
   }
 
   public ConstraintWithTarget forMoveException() {
-    // TODO(64432527): Revisit this constraint.
-    return ConstraintWithTarget.NEVER;
+    return ConstraintWithTarget.ALWAYS;
   }
 
   public ConstraintWithTarget forNewArrayEmpty(DexType type, DexType invocationContext) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
index 2443d11..90554f6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -20,8 +20,8 @@
 
   boolean isValidTarget(InvokeMethod invoke, DexEncodedMethod target, IRCode inlinee);
 
-  ListIterator<BasicBlock> updateTypeInformationIfNeeded(IRCode inlinee,
-      ListIterator<BasicBlock> blockIterator, BasicBlock block, BasicBlock invokeSuccessor);
+  void updateTypeInformationIfNeeded(
+      IRCode inlinee, ListIterator<BasicBlock> blockIterator, BasicBlock block);
 
   DexType getReceiverTypeIfKnown(InvokeMethod invoke);
 }
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index c6729e8..97f1228 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.MethodSignatureRelaxedEquivalence;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
@@ -90,11 +91,15 @@
  */
 class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> {
 
-  private final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
+  private final Equivalence<DexMethod> equivalence;
   private final Map<DexCallSite, DexString> callSiteRenaming = new IdentityHashMap<>();
 
   MethodNameMinifier(AppInfoWithSubtyping appInfo, RootSet rootSet, InternalOptions options) {
     super(appInfo, rootSet, options);
+    equivalence =
+        overloadAggressively
+            ? MethodSignatureEquivalence.get()
+            : new MethodSignatureRelaxedEquivalence(appInfo);
   }
 
   @Override
@@ -337,10 +342,12 @@
   }
 
   private void addStatesToGlobalMapForMethod(
-      DexEncodedMethod method, Set<NamingState<DexProto, ?>> collectedStates,
+      DexEncodedMethod method,
+      Set<NamingState<DexProto, ?>> collectedStates,
       Map<Wrapper<DexMethod>, Set<NamingState<DexProto, ?>>> globalStateMap,
       Map<Wrapper<DexMethod>, Set<DexMethod>> sourceMethodsMap,
-      Map<Wrapper<DexMethod>, NamingState<DexProto, ?>> originStates, DexType originInterface) {
+      Map<Wrapper<DexMethod>, NamingState<DexProto, ?>> originStates,
+      DexType originInterface) {
     Wrapper<DexMethod> key = equivalence.wrap(method.method);
     Set<NamingState<DexProto, ?>> stateSet =
         globalStateMap.computeIfAbsent(key, k -> new HashSet<>());
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
index 3cc3245..d0a126a 100644
--- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils;
 
 import java.util.ListIterator;
+import java.util.function.Predicate;
 
 public class IteratorUtils {
   public static <T> T peekPrevious(ListIterator<T> iterator) {
@@ -20,4 +21,25 @@
     assert previous == next;
     return next;
   }
+
+  public static <T> boolean allRemainingMatch(ListIterator<T> iterator, Predicate<T> predicate) {
+    return !anyRemainingMatch(iterator, remaining -> !predicate.test(remaining));
+  }
+
+  public static <T> boolean anyRemainingMatch(ListIterator<T> iterator, Predicate<T> predicate) {
+    T state = peekNext(iterator);
+    boolean result = false;
+    while (iterator.hasNext()) {
+      T item = iterator.next();
+      if (predicate.test(item)) {
+        result = true;
+        break;
+      }
+    }
+    while (iterator.hasPrevious() && iterator.previous() != state) {
+      // Restore the state of the iterator.
+    }
+    assert peekNext(iterator) == state;
+    return result;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/MethodSignatureRelaxedEquivalence.java b/src/main/java/com/android/tools/r8/utils/MethodSignatureRelaxedEquivalence.java
new file mode 100644
index 0000000..74d2d98
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/MethodSignatureRelaxedEquivalence.java
@@ -0,0 +1,36 @@
+// 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.utils;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexMethod;
+import com.google.common.base.Equivalence;
+
+/**
+ * Implements an equivalence on {@link DexMethod} that does not take the holder into account but
+ * allow covariant return type: a method in a sub type can return a narrower type.
+ */
+public class MethodSignatureRelaxedEquivalence extends Equivalence<DexMethod> {
+
+  private final AppInfo appInfo;
+
+  public MethodSignatureRelaxedEquivalence(AppInfo appInfo) {
+    this.appInfo = appInfo;
+  }
+
+  @Override
+  protected boolean doEquivalent(DexMethod a, DexMethod b) {
+    return a.name.equals(b.name) && a.proto.parameters.equals(b.proto.parameters)
+        && (a.proto.returnType.equals(b.proto.returnType)
+            || (a.proto.returnType.isStrictSubtypeOf(b.proto.returnType, appInfo)
+                && a.getHolder().isStrictSubtypeOf(b.getHolder(), appInfo))
+            || (b.proto.returnType.isStrictSubtypeOf(a.proto.returnType, appInfo)
+                && b.getHolder().isStrictSubtypeOf(a.getHolder(), appInfo)));
+  }
+
+  @Override
+  protected int doHash(DexMethod method) {
+    return method.name.hashCode() * 31 + method.proto.parameters.hashCode();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 180a5f8..d78f94b 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -985,27 +985,28 @@
               TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4)))
           .build();
 
-  public static List<String> requireInliningToBeDisabled = ImmutableList.of(
-      // Test for a specific stack trace that gets destroyed by inlining.
-      "492-checker-inline-invoke-interface",
-      "493-checker-inline-invoke-interface",
-      "488-checker-inline-recursive-calls",
-      "487-checker-inline-calls",
-      "122-npe",
-      "141-class-unload",
+  public static List<String> requireInliningToBeDisabled =
+      ImmutableList.of(
+          // Test for a specific stack trace that gets destroyed by inlining.
+          "008-exceptions",
+          "492-checker-inline-invoke-interface",
+          "493-checker-inline-invoke-interface",
+          "488-checker-inline-recursive-calls",
+          "487-checker-inline-calls",
+          "122-npe",
+          "141-class-unload",
 
-      // Calls some internal art methods that cannot tolerate inlining.
-      "466-get-live-vreg",
+          // Calls some internal art methods that cannot tolerate inlining.
+          "466-get-live-vreg",
 
-      // Requires a certain call pattern to surface an Art bug.
-      "534-checker-bce-deoptimization",
+          // Requires a certain call pattern to surface an Art bug.
+          "534-checker-bce-deoptimization",
 
-      // Requires something to be allocated in a method so that it goes out of scope.
-      "059-finalizer-throw",
+          // Requires something to be allocated in a method so that it goes out of scope.
+          "059-finalizer-throw",
 
-      // Has tests in submethods, which we should not inline.
-      "625-checker-licm-regressions"
-  );
+          // Has tests in submethods, which we should not inline.
+          "625-checker-licm-regressions");
 
   private static List<String> requireClassInliningToBeDisabled = ImmutableList.of(
       // Test is registered to be failing (failingRunWithArt), it fails only on 4.0.4
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
index dc94a71..4451910 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
@@ -25,10 +25,16 @@
 
     List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
     for (String test : tests) {
-      fullTestList.add(makeTest(Input.JAVAC, CompilerUnderTest.D8, CompilationMode.DEBUG, test));
-      fullTestList.add(makeTest(Input.JAVAC, CompilerUnderTest.D8, CompilationMode.RELEASE, test));
+      fullTestList.add(
+          makeTest(Input.JAVAC, CompilerUnderTest.D8, CompilationMode.DEBUG, test, Output.DEX));
+      fullTestList.add(
+          makeTest(Input.JAVAC, CompilerUnderTest.D8, CompilationMode.RELEASE, test, Output.DEX));
       fullTestList.add(makeTest(Input.DX, CompilerUnderTest.R8, CompilationMode.DEBUG, test));
       fullTestList.add(makeTest(Input.DX, CompilerUnderTest.R8, CompilationMode.RELEASE, test));
+      fullTestList.add(
+          makeTest(Input.JAVAC, CompilerUnderTest.R8, CompilationMode.DEBUG, test, Output.CF));
+      fullTestList.add(
+          makeTest(Input.JAVAC, CompilerUnderTest.R8, CompilationMode.RELEASE, test, Output.CF));
     }
     return fullTestList;
   }
diff --git a/src/test/java/com/android/tools/r8/debug/PostIncrementTestRunner.java b/src/test/java/com/android/tools/r8/debug/PostIncrementTestRunner.java
index 4cfb71a..6ddc9db 100644
--- a/src/test/java/com/android/tools/r8/debug/PostIncrementTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/PostIncrementTestRunner.java
@@ -5,18 +5,24 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderOrEqualThan;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
 import java.util.stream.Stream;
 import org.junit.Assume;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 // See b/80385846
+@RunWith(VmTestRunner.class)
 public class PostIncrementTestRunner extends DebugTestBase {
 
   private static final Class CLASS = PostIncrementTest.class;
   private static final String NAME = CLASS.getCanonicalName();
 
   @Test
+  @IgnoreIfVmOlderOrEqualThan(Version.V5_1_1)
   public void test() throws Exception {
     Assume.assumeTrue("Older runtimes cause some kind of debug streaming issues",
         ToolHelper.getDexVm().isNewerThan(DexVm.ART_5_1_1_HOST));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index dd7756b..8c5a738 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -90,7 +90,7 @@
     return Paths.get(temp.getRoot().getCanonicalPath(), DEFAULT_DEX_FILENAME);
   }
 
-  private String getGeneratedProguardMap() throws IOException {
+  private String getGeneratedProguardMap() {
     Path mapFile = temp.getRoot().toPath().resolve(DEFAULT_MAP_FILENAME);
     if (Files.exists(mapFile)) {
       return mapFile.toAbsolutePath().toString();
@@ -116,9 +116,14 @@
       commandBuilder.addProguardConfiguration(
           ImmutableList.of("-allowaccessmodification"), Origin.unknown());
     }
-    ToolHelper.runR8(commandBuilder.build(), o -> {
-      o.enableMinification = minification;
-    });
+    ToolHelper.runR8(
+        commandBuilder.build(),
+        o -> {
+          // Disable class inlining to prevent that the instantiation of Nullability is removed, and
+          // that the class is therefore made abstract.
+          o.enableClassInlining = false;
+          o.enableMinification = minification;
+        });
     String artOutput =
         ToolHelper.runArtNoVerificationErrors(out + "/classes.dex", "inlining.Inlining");
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerTest.java
index 5cdaac8..95343df 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerTest.java
@@ -9,10 +9,12 @@
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.ir.optimize.inliner.exceptionhandling.ExceptionHandlingTestClass;
 import com.android.tools.r8.ir.optimize.inliner.interfaces.InterfaceTargetsTestClass;
 import com.android.tools.r8.ir.optimize.inliner.interfaces.InterfaceTargetsTestClass.IfaceA;
 import com.android.tools.r8.ir.optimize.inliner.interfaces.InterfaceTargetsTestClass.IfaceB;
@@ -20,12 +22,15 @@
 import com.android.tools.r8.ir.optimize.inliner.interfaces.InterfaceTargetsTestClass.IfaceD;
 import com.android.tools.r8.ir.optimize.inliner.interfaces.InterfaceTargetsTestClass.IfaceNoImpl;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
+import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.junit.Test;
@@ -33,6 +38,30 @@
 
 @RunWith(VmTestRunner.class)
 public class InlinerTest extends TestBase {
+
+  @Test
+  public void testExceptionHandling() throws Exception {
+    String className = ExceptionHandlingTestClass.class.getName();
+    AndroidApp inputApp = readClasses(ExceptionHandlingTestClass.class);
+    List<String> proguardConfig =
+        ImmutableList.of(
+            "-keep public class " + className + "{",
+            "  public static void main(...);",
+            "}",
+            "-forceinline public class " + className + "{",
+            "  private static void inlinee*(...);",
+            "}",
+            "-neverinline public class " + className + "{",
+            "  private static void *Test(...);",
+            "}");
+    R8Command.Builder commandBuilder =
+        ToolHelper.prepareR8CommandBuilder(inputApp)
+            .addProguardConfiguration(proguardConfig, Origin.unknown());
+    ToolHelper.allowTestProguardOptions(commandBuilder);
+    AndroidApp outputApp = ToolHelper.runR8(commandBuilder.build(), this::configure);
+    assertEquals(runOnJava(ExceptionHandlingTestClass.class), runOnArt(outputApp, className));
+  }
+
   @Test
   public void testInterfacesWithoutTargets() throws Exception {
     byte[][] classes = {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/exceptionhandling/ExceptionHandlingTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/exceptionhandling/ExceptionHandlingTestClass.java
new file mode 100644
index 0000000..cda0856
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/exceptionhandling/ExceptionHandlingTestClass.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner.exceptionhandling;
+
+public class ExceptionHandlingTestClass {
+
+  private static boolean FALSE;
+
+  // -keep
+  public static void main(String[] args) {
+    FALSE = args == null;
+    try {
+      methodWithoutCatchHandlersTest(1);
+      System.out.println("Test succeeded: methodWithoutCatchHandlersTest(1)");
+    } catch (Exception e) {
+      System.out.println("Test failed: methodWithoutCatchHandlersTest(1)");
+    }
+    try {
+      methodWithoutCatchHandlersTest(2);
+      System.out.println("Test failed: methodWithoutCatchHandlersTest(2)");
+    } catch (Exception e) {
+      System.out.println("Test succeeded: methodWithoutCatchHandlersTest(2)");
+    }
+    try {
+      methodWithoutCatchHandlersTest(3);
+      System.out.println("Test failed: methodWithoutCatchHandlersTest(3)");
+    } catch (Exception e) {
+      System.out.println("Test succeeded: methodWithoutCatchHandlersTest(3)");
+    }
+
+    methodWithCatchHandlersTest();
+  }
+
+  // -neverinline
+  private static void methodWithoutCatchHandlersTest(int i) {
+    switch (i) {
+      case 1:
+        inlineeWithNormalExitThatDoesNotThrow();
+        break;
+
+      case 2:
+        inlineeWithNormalExitThatThrows();
+        break;
+
+      case 3:
+        inlineeWithoutNormalExit();
+        break;
+    }
+  }
+
+  // -neverinline
+  private static void methodWithCatchHandlersTest() {
+    try {
+      inlineeWithNormalExitThatDoesNotThrow();
+      System.out.println("Test succeeded: methodWithCatchHandlersTest(1)");
+    } catch (Exception e) {
+
+    }
+    try {
+      inlineeWithNormalExitThatThrows();
+    } catch (Exception e) {
+      System.out.println("Test succeeded: methodWithCatchHandlersTest(2)");
+    }
+    try {
+      inlineeWithoutNormalExit();
+    } catch (Exception e) {
+      System.out.println("Test succeeded: methodWithCatchHandlersTest(3)");
+    }
+  }
+
+  // -forceinline
+  private static void inlineeWithNormalExitThatDoesNotThrow() {
+    if (FALSE) {
+      throw new RuntimeException();
+    }
+  }
+
+  // -forceinline
+  private static void inlineeWithNormalExitThatThrows() {
+    if (!FALSE) {
+      throw new RuntimeException();
+    }
+  }
+
+  // -forceinline
+  private static void inlineeWithoutNormalExit() {
+    throw new RuntimeException();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
index e43e94c..b8c4b47 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
@@ -270,42 +270,44 @@
   @Test
   public void testTrivialKs() throws Exception {
     final String mainClassName = "lambdas_kstyle_trivial.MainKt";
-    runTest("lambdas_kstyle_trivial", mainClassName, optionsModifier, (app) -> {
-      Verifier verifier = new Verifier(app);
-      String pkg = "lambdas_kstyle_trivial";
+    runTest(
+        "lambdas_kstyle_trivial",
+        mainClassName,
+        optionsModifier,
+        (app) -> {
+          Verifier verifier = new Verifier(app);
+          String pkg = "lambdas_kstyle_trivial";
 
-      verifier.assertLambdaGroups(
-          allowAccessModification ?
-              new Group[]{
-                  kstyle("", 0, 4),
-                  kstyle("", 1, 8),
-                  kstyle("", 2, 2), // -\
-                  kstyle("", 2, 5), // - 3 groups different by main method
-                  kstyle("", 2, 4), // -/
-                  kstyle("", 3, 2),
-                  kstyle("", 22, 2)} :
-              new Group[]{
-                  kstyle(pkg, 0, 2),
-                  kstyle(pkg, 1, 4),
-                  kstyle(pkg, 2, 5), // - 2 groups different by main method
-                  kstyle(pkg, 2, 4), // -/
-                  kstyle(pkg, 3, 2),
-                  kstyle(pkg, 22, 2),
-                  kstyle(pkg + "/inner", 0, 2),
-                  kstyle(pkg + "/inner", 1, 4)}
-      );
+          verifier.assertLambdaGroups(
+              allowAccessModification
+                  ? new Group[] {
+                    kstyle("", 0, 4),
+                    kstyle("", 1, 9),
+                    kstyle("", 2, 2), // -\
+                    kstyle("", 2, 5), // - 3 groups different by main method
+                    kstyle("", 2, 4), // -/
+                    kstyle("", 3, 2),
+                    kstyle("", 22, 2)
+                  }
+                  : new Group[] {
+                    kstyle(pkg, 0, 2),
+                    kstyle(pkg, 1, 5),
+                    kstyle(pkg, 2, 5), // - 2 groups different by main method
+                    kstyle(pkg, 2, 4), // -/
+                    kstyle(pkg, 3, 2),
+                    kstyle(pkg, 22, 2),
+                    kstyle(pkg + "/inner", 0, 2),
+                    kstyle(pkg + "/inner", 1, 4)
+                  });
 
-      verifier.assertLambdas(
-          allowAccessModification ?
-              new Lambda[]{
-                  new Lambda(pkg, "MainKt$testStateless$6", 1) /* Banned for limited inlining */} :
-              new Lambda[]{
-                  new Lambda(pkg, "MainKt$testStateless$6", 1), /* Banned for limited inlining */
-                  new Lambda(pkg, "MainKt$testStateless$8", 2),
-                  new Lambda(pkg + "/inner", "InnerKt$testInnerStateless$7", 2)}
-
-      );
-    });
+          verifier.assertLambdas(
+              allowAccessModification
+                  ? new Lambda[] {}
+                  : new Lambda[] {
+                    new Lambda(pkg, "MainKt$testStateless$8", 2),
+                    new Lambda(pkg + "/inner", "InnerKt$testInnerStateless$7", 2)
+                  });
+        });
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java
index 87b9487..3eca971 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java
@@ -31,7 +31,7 @@
 public class KotlinxMetadataExtensionsServiceTest extends TestBase {
 
   private void forkR8_kstyle_trivial(boolean allowAccessModification) throws Exception {
-    if  (!isRunR8Jar()) {
+    if (!isRunR8Jar()) {
       return;
     }
     Path working = temp.getRoot().toPath();
@@ -64,35 +64,34 @@
     Verifier verifier = new Verifier(inspector);
     String pkg = "lambdas_kstyle_trivial";
     verifier.assertLambdaGroups(
-        allowAccessModification ?
-            new Group[]{
-                kstyle("", 0, 4),
-                kstyle("", 1, 8),
-                kstyle("", 2, 2), // -\
-                kstyle("", 2, 5), // - 3 groups different by main method
-                kstyle("", 2, 4), // -/
-                kstyle("", 3, 2),
-                kstyle("", 22, 2)} :
-            new Group[]{
-                kstyle(pkg, 0, 2),
-                kstyle(pkg, 1, 4),
-                kstyle(pkg, 2, 5), // - 2 groups different by main method
-                kstyle(pkg, 2, 4), // -/
-                kstyle(pkg, 3, 2),
-                kstyle(pkg, 22, 2),
-                kstyle(pkg + "/inner", 0, 2),
-                kstyle(pkg + "/inner", 1, 4)}
-    );
+        allowAccessModification
+            ? new Group[] {
+              kstyle("", 0, 4),
+              kstyle("", 1, 9),
+              kstyle("", 2, 2), // -\
+              kstyle("", 2, 5), // - 3 groups different by main method
+              kstyle("", 2, 4), // -/
+              kstyle("", 3, 2),
+              kstyle("", 22, 2)
+            }
+            : new Group[] {
+              kstyle(pkg, 0, 2),
+              kstyle(pkg, 1, 5),
+              kstyle(pkg, 2, 5), // - 2 groups different by main method
+              kstyle(pkg, 2, 4), // -/
+              kstyle(pkg, 3, 2),
+              kstyle(pkg, 22, 2),
+              kstyle(pkg + "/inner", 0, 2),
+              kstyle(pkg + "/inner", 1, 4)
+            });
 
     verifier.assertLambdas(
-        allowAccessModification ?
-            new Lambda[]{
-                new Lambda(pkg, "MainKt$testStateless$6", 1) /* Banned for limited inlining */} :
-            new Lambda[]{
-                new Lambda(pkg, "MainKt$testStateless$6", 1), /* Banned for limited inlining */
-                new Lambda(pkg, "MainKt$testStateless$8", 2),
-                new Lambda(pkg + "/inner", "InnerKt$testInnerStateless$7", 2)}
-    );
+        allowAccessModification
+            ? new Lambda[] {}
+            : new Lambda[] {
+              new Lambda(pkg, "MainKt$testStateless$8", 2),
+              new Lambda(pkg + "/inner", "InnerKt$testInnerStateless$7", 2)
+            });
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java b/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java
index 6c6e69d..4903304 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java
@@ -11,19 +11,24 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.code.InvokeVirtual;
-import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 // Base -> X:
 class Base {
@@ -47,10 +52,22 @@
   }
 }
 
+@RunWith(Parameterized.class)
 public class CompositionalLenseTest extends TestBase {
   private final static List<Class> CLASSES =
       ImmutableList.of(Base.class, Sub.class, TestMain.class);
 
+  private Backend backend;
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public CompositionalLenseTest(Backend backend) {
+    this.backend = backend;
+  }
+
   @Test
   public void test() throws Exception {
     Path mapPath = temp.newFile("test-mapping.txt").toPath();
@@ -63,26 +80,38 @@
     FileUtils.writeTextFile(mapPath, pgMap);
 
     AndroidApp app = readClasses(CLASSES);
-    R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app);
-    builder.addProguardConfiguration(
-        ImmutableList.of(
-            keepMainProguardConfiguration(TestMain.class),
-            "-applymapping " + mapPath,
-            "-dontobfuscate"),  // to use the renamed names in test-mapping.txt
-        Origin.unknown());
+    R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app, emptyConsumer(backend));
+    builder
+        .addProguardConfiguration(
+            ImmutableList.of(
+                keepMainProguardConfiguration(TestMain.class),
+                "-applymapping " + mapPath,
+                "-dontobfuscate"), // to use the renamed names in test-mapping.txt
+            Origin.unknown())
+        .addLibraryFiles(runtimeJar(backend));
     AndroidApp processedApp = ToolHelper.runR8(builder.build(), options -> {
       options.enableInlining = false;
       options.enableClassMerging = false;
     });
-    CodeInspector codeInspector = new CodeInspector(processedApp);
+    CodeInspector codeInspector = new CodeInspector(processedApp, o -> o.enableCfFrontend = true);
     ClassSubject classSubject = codeInspector.clazz(TestMain.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(CodeInspector.MAIN);
     assertThat(methodSubject, isPresent());
-    DexCode dexCode = methodSubject.getMethod().getCode().asDexCode();
-    assertTrue(dexCode.instructions[2] instanceof InvokeVirtual);
-    InvokeVirtual invoke = (InvokeVirtual) dexCode.instructions[2];
-    DexMethod invokedMethod = invoke.getMethod();
+    Iterator<InstructionSubject> iterator = methodSubject.iterateInstructions();
+
+    InstructionSubject instruction = null;
+    boolean found = false;
+    while (iterator.hasNext()) {
+      instruction = iterator.next();
+      if (instruction.isInvokeVirtual()) {
+        found = true;
+        break;
+      }
+    }
+    assertTrue(found);
+
+    DexMethod invokedMethod = ((InvokeInstructionSubject) instruction).invokedMethod();
     assertEquals("bar", invokedMethod.name.toString());
     assertEquals("X", invokedMethod.getHolder().getName());
   }
diff --git a/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeInSubInterfaceTest.java
index 6602d0c..20f46b4 100644
--- a/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeInSubInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeInSubInterfaceTest.java
@@ -7,15 +7,20 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
+import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
 import java.util.List;
-import org.junit.Ignore;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 interface SuperInterface {
   Super foo();
@@ -42,14 +47,14 @@
 class SuperImplementer implements SuperInterface {
   @Override
   public Super foo() {
-    return new Super();
+    return new Sub();
   }
 }
 
-class SubImplementer implements SubInterface {
+class SubImplementer extends SuperImplementer implements SubInterface {
   @Override
   public Sub foo() {
-    return new Sub();
+    return (Sub) super.foo();
   }
 }
 
@@ -57,19 +62,22 @@
   public static void main(String[] args) {
     SubImplementer subImplementer = new SubImplementer();
     Super sup = subImplementer.foo();
-    System.out.println(sup.bar());
+    System.out.print(sup.bar());
   }
 }
 
+@RunWith(VmTestRunner.class)
 public class CovariantReturnTypeInSubInterfaceTest extends TestBase {
 
-  @Ignore("b/112185748")
-  @Test
-  public void test() throws Exception {
+  private void test(boolean overloadAggressively) throws Exception {
+    String mainName = TestMain.class.getCanonicalName();
+    String aggressive =
+        overloadAggressively ? "-overloadaggressively" : "# Not overload aggressively";
     List<String> config = ImmutableList.of(
         "-printmapping",
         "-useuniqueclassmembernames",
-        "-keep class " + TestMain.class.getCanonicalName() + " {",
+        aggressive,
+        "-keep class " + mainName + " {",
         "  public void main(...);",
         "}",
         "-keep,allowobfuscation class **.Super* {",
@@ -88,7 +96,10 @@
         SubImplementer.class,
         TestMain.class
     );
-    AndroidApp processedApp = compileWithR8(app, String.join(System.lineSeparator(), config));
+    AndroidApp processedApp =
+        compileWithR8(app, String.join(System.lineSeparator(), config), options -> {
+          options.enableInlining = false;
+        });
     CodeInspector inspector = new CodeInspector(processedApp);
     ClassSubject superInterface = inspector.clazz(SuperInterface.class);
     assertThat(superInterface, isRenamed());
@@ -101,6 +112,24 @@
         Sub.class.getCanonicalName(), "foo", ImmutableList.of());
     assertThat(foo2, isRenamed());
     assertEquals(foo1.getFinalName(), foo2.getFinalName());
+
+    ProcessResult javaResult = ToolHelper.runJava(ToolHelper.getClassPathForTests(), mainName);
+    assertEquals(0, javaResult.exitCode);
+    Path outDex = temp.getRoot().toPath().resolve("dex.zip");
+    processedApp.writeToZip(outDex, OutputMode.DexIndexed);
+    ProcessResult artResult = ToolHelper.runArtNoVerificationErrorsRaw(outDex.toString(), mainName);
+    assertEquals(0, artResult.exitCode);
+    assertEquals(javaResult.stdout, artResult.stdout);
+  }
+
+  @Test
+  public void test_notAggressively() throws Exception {
+    test(false);
+  }
+
+  @Test
+  public void test_aggressively() throws Exception {
+    test(true);
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
index e1fa3d0..ef6e0c8 100644
--- a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
@@ -14,12 +14,15 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.origin.Origin;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.List;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
+@RunWith(VmTestRunner.class)
 public class InterfaceRenamingTestRunner extends TestBase {
   static final Class CLASS = InterfaceRenamingTest.class;
   static final Class[] CLASSES = InterfaceRenamingTest.CLASSES;
diff --git a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
index 02bca3a..56dce3a 100644
--- a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
@@ -16,13 +16,16 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import java.io.IOException;
 import java.nio.file.Path;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
+@RunWith(VmTestRunner.class)
 public class LambdaRenamingTestRunner extends TestBase {
   static final Class CLASS = LambdaRenamingTest.class;
   static final Class[] CLASSES = LambdaRenamingTest.CLASSES;
diff --git a/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java b/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
index 01b0a17..86852ef 100644
--- a/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
+++ b/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
@@ -7,28 +7,53 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.OffOrAuto;
 import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
 import org.junit.Assert;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class Regress63935662 extends TestBase {
 
+  private Backend backend;
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public Regress63935662(Backend backend) {
+    this.backend = backend;
+  }
+
   void run(AndroidApp app, Class mainClass) throws Exception {
     Path proguardConfig =
         writeTextToTempFile(keepMainProguardConfiguration(mainClass, true, false));
-    R8Command command =
-        ToolHelper.prepareR8CommandBuilder(app)
-            .addProguardConfigurationFiles(proguardConfig)
-            .setMinApiLevel(AndroidApiLevel.L.getLevel())
-            .build();
+    R8Command.Builder builder =
+        ToolHelper.prepareR8CommandBuilder(app, emptyConsumer(backend))
+            .addLibraryFiles(runtimeJar(backend))
+            .addProguardConfigurationFiles(proguardConfig);
+    if (backend == Backend.DEX) {
+      builder.setMinApiLevel(AndroidApiLevel.L.getLevel());
+    }
     String resultFromJava = runOnJava(mainClass);
-    app = ToolHelper.runR8(command, options -> options.interfaceMethodDesugaring = OffOrAuto.Auto);
-    String resultFromArt = runOnArt(app, mainClass);
-    Assert.assertEquals(resultFromJava, resultFromArt);
+    app =
+        ToolHelper.runR8(
+            builder.build(), options -> options.interfaceMethodDesugaring = OffOrAuto.Auto);
+    String result;
+    if (backend == Backend.DEX) {
+      result = runOnArt(app, mainClass);
+    } else {
+      assert backend == Backend.CF;
+      result = runOnJava(app, mainClass);
+    }
+    Assert.assertEquals(resultFromJava, result);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
index b2bcb4e..ee99b38 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.rewrite.switchmaps;
 
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
@@ -13,12 +12,28 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class RewriteSwitchMapsTest extends TestBase {
 
+  private Backend backend;
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public RewriteSwitchMapsTest(Backend backend) {
+    this.backend = backend;
+  }
+
   private static final String JAR_FILE = "switchmaps.jar";
   private static final String SWITCHMAP_CLASS_NAME = "switchmaps.Switches$1";
   private static final List<String> PG_CONFIG = ImmutableList.of(
@@ -37,15 +52,16 @@
   }
 
   private void run(CompilationMode compilationMode) throws Exception {
-    R8Command command = R8Command.builder()
-        .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR).resolve(JAR_FILE))
-        .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
-        .addProguardConfiguration(PG_CONFIG, Origin.unknown())
-        .setMode(compilationMode)
-        .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
-        .build();
+    R8Command command =
+        R8Command.builder()
+            .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR).resolve(JAR_FILE))
+            .addLibraryFiles(runtimeJar(backend))
+            .addProguardConfiguration(PG_CONFIG, Origin.unknown())
+            .setMode(compilationMode)
+            .setProgramConsumer(emptyConsumer(backend))
+            .build();
     AndroidApp result = ToolHelper.runR8(command);
-    CodeInspector inspector = new CodeInspector(result);
+    CodeInspector inspector = new CodeInspector(result, o -> o.enableCfFrontend = true);
     Assert.assertFalse(inspector.clazz(SWITCHMAP_CLASS_NAME).isPresent());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/B112290098.java b/src/test/java/com/android/tools/r8/shaking/B112290098.java
index 502da76..e7784cc 100644
--- a/src/test/java/com/android/tools/r8/shaking/B112290098.java
+++ b/src/test/java/com/android/tools/r8/shaking/B112290098.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.AndroidApp;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class B112290098 extends TestBase {
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java b/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
index 4081836..d02fa5b 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
@@ -62,11 +62,10 @@
   @Parameters(name = "shrinker: {0} inlineMethod: {1}")
   public static Collection<Object[]> data() {
     // We don't run this on Proguard, as triggering inlining in Proguard is out of our control.
-    // TODO(b/64432527) Add configuration {Shrinker.R8_CF, true} when fixed. For now we fail to
-    // inline because of the exception handler.
     return ImmutableList.of(
         new Object[] {Shrinker.R8, true},
         new Object[] {Shrinker.R8, false},
+        new Object[] {Shrinker.R8_CF, true},
         new Object[] {Shrinker.R8_CF, false});
   }