Merge "Fix incorrect/flaky settings in lambda-related access relaxation tests."
diff --git a/build.gradle b/build.gradle
index b733b88..dd401d6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -517,6 +517,7 @@
 task repackageDeps(type: ShadowJar) {
     configurations = [project.configurations.compile]
     configureRelocations(it)
+    exclude { it.getRelativePath().getPathString() == "module-info.class" }
     baseName 'deps'
 }
 
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 63c1173..b7fc6b3 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.3.5-dev";
+  public static final String LABEL = "1.3.7-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 7508ea0..644f2c2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -35,6 +35,10 @@
     return position;
   }
 
+  public CfLabel getLabel() {
+    return label;
+  }
+
   @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     state.setPosition(position);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 5bed061..8ab2c1a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.ir.code.IRCode.INSTRUCTION_NUMBER_DELTA;
 
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DebugLocalInfo.PrintLevel;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -467,7 +468,7 @@
         return instruction;
       }
     }
-    return null;
+    throw new Unreachable();
   }
 
   public void clearUserInfo() {
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 00a94a9..f276cb2 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
@@ -87,6 +87,8 @@
   private List<InvokeDirect> thisInitializers;
   private Map<NewInstance, CfLabel> newInstanceLabels;
 
+  private InternalOptions options;
+
   // Internal abstraction of the stack values and height.
   private static class Stack {
     int maxHeight = 0;
@@ -123,6 +125,7 @@
       GraphLense graphLense,
       InternalOptions options,
       AppInfoWithSubtyping appInfo) {
+    this.options = options;
     computeInitializers();
     types = new TypeVerificationHelper(code, factory, appInfo).computeVerificationTypes();
     splitExceptionalBlocks();
@@ -379,7 +382,10 @@
   private void updatePositionAndLocals(Instruction instruction) {
     Position position = instruction.getPosition();
     boolean didLocalsChange = localsChanged();
-    boolean didPositionChange = position.isSome() && position != currentPosition;
+    boolean didPositionChange =
+        position.isSome()
+            && position != currentPosition
+            && (options.debug || instruction.instructionTypeCanThrow());
     if (!didLocalsChange && !didPositionChange) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index fffbc44..c6ba97d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -46,7 +46,6 @@
 import com.android.tools.r8.ir.optimize.NonNullTracker;
 import com.android.tools.r8.ir.optimize.Outliner;
 import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
-import com.android.tools.r8.ir.optimize.RedundantFieldLoadElimination;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
 import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -696,7 +695,6 @@
     codeRewriter.rewriteSwitch(code);
     codeRewriter.processMethodsNeverReturningNormally(code);
     codeRewriter.simplifyIf(code, typeEnvironment);
-    new RedundantFieldLoadElimination(code).run();
 
     if (options.testing.invertConditionals) {
       invertConditionalsForTesting(code);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
deleted file mode 100644
index 24012b1..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ /dev/null
@@ -1,193 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.optimize;
-
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.DominatorTree;
-import com.android.tools.r8.ir.code.FieldInstruction;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.Phi;
-import com.android.tools.r8.ir.code.Value;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Eliminate redundant field loads.
- *
- * <p>Simple algorithm that goes through all blocks in one pass in dominator order and propagates
- * active field sets across control-flow edges where the target has only one predecessor.
- */
-// TODO(ager): Evaluate speed/size for computing active field sets in a fixed-point computation.
-public class RedundantFieldLoadElimination {
-
-  private final IRCode code;
-  private final DominatorTree dominatorTree;
-
-  // Maps keeping track of fields that have an already loaded value at basic block entry.
-  private final HashMap<BasicBlock, HashMap<FieldAndObject, Instruction>>
-      activeInstanceFieldsAtEntry = new HashMap<>();
-  private final HashMap<BasicBlock, HashMap<DexField, Instruction>> activeStaticFieldsAtEntry =
-      new HashMap<>();
-
-  // Maps keeping track of fields with already loaded values for the current block during
-  // elimination.
-  private HashMap<FieldAndObject, Instruction> activeInstanceFields;
-  private HashMap<DexField, Instruction> activeStaticFields;
-
-  public RedundantFieldLoadElimination(IRCode code) {
-    this.code = code;
-    dominatorTree = new DominatorTree(code);
-  }
-
-  private static class FieldAndObject {
-    private final DexField field;
-    private final Value object;
-
-    private FieldAndObject(DexField field, Value receiver) {
-      this.field = field;
-      this.object = receiver;
-    }
-
-    @Override
-    public int hashCode() {
-      return field.hashCode() * 7 + object.hashCode();
-    }
-
-    @Override
-    public boolean equals(Object other) {
-      if (!(other instanceof FieldAndObject)) {
-        return false;
-      }
-      FieldAndObject o = (FieldAndObject) other;
-      return o.object == object && o.field == field;
-    }
-  }
-
-  public void run() {
-    for (BasicBlock block : dominatorTree.getSortedBlocks()) {
-      activeInstanceFields =
-          activeInstanceFieldsAtEntry.containsKey(block)
-              ? activeInstanceFieldsAtEntry.get(block)
-              : new HashMap<>();
-      activeStaticFields =
-          activeStaticFieldsAtEntry.containsKey(block)
-              ? activeStaticFieldsAtEntry.get(block)
-              : new HashMap<>();
-      InstructionListIterator it = block.listIterator();
-      while (it.hasNext()) {
-        Instruction instruction = it.next();
-        if (instruction.isFieldInstruction()) {
-          DexField field = instruction.asFieldInstruction().getField();
-          if (instruction.isInstancePut() || instruction.isStaticPut()) {
-            killActiveFields(instruction.asFieldInstruction());
-          } else if (instruction.isInstanceGet() && !instruction.outValue().hasLocalInfo()) {
-            Value object = instruction.asInstanceGet().object();
-            FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-            if (activeInstanceFields.containsKey(fieldAndObject)) {
-              Instruction active = activeInstanceFields.get(fieldAndObject);
-              eliminateRedundantRead(it, instruction, active);
-            } else {
-              activeInstanceFields.put(fieldAndObject, instruction);
-            }
-          } else if (instruction.isStaticGet() && !instruction.outValue().hasLocalInfo()) {
-            if (activeStaticFields.containsKey(field)) {
-              Instruction active = activeStaticFields.get(field);
-              eliminateRedundantRead(it, instruction, active);
-            } else {
-              // A field get on a different class can cause <clinit> to run and change static
-              // field values.
-              killActiveFields(instruction.asFieldInstruction());
-              activeStaticFields.put(field, instruction);
-            }
-          }
-        }
-        if (instruction.isMonitor() || instruction.isInvokeMethod()) {
-          activeInstanceFields.clear();
-          activeStaticFields.clear();
-        }
-      }
-      propagateActiveFieldsFrom(block);
-    }
-    assert code.isConsistentSSA();
-  }
-
-  private void propagateActiveFieldsFrom(BasicBlock block) {
-    for (BasicBlock successor : block.getSuccessors()) {
-      // Allow propagation across exceptional edges, just be careful not to propagate if the
-      // throwing instruction is a field instruction.
-      if (successor.getPredecessors().size() == 1) {
-        if (block.hasCatchSuccessor(successor)) {
-          Instruction exceptionalExit = block.exceptionalExit();
-          if (exceptionalExit != null && exceptionalExit.isFieldInstruction()) {
-            killActiveFieldsForExceptionalExit(exceptionalExit.asFieldInstruction());
-          }
-        }
-        assert !activeInstanceFieldsAtEntry.containsKey(successor);
-        activeInstanceFieldsAtEntry.put(successor, new HashMap<>(activeInstanceFields));
-        assert !activeStaticFieldsAtEntry.containsKey(successor);
-        activeStaticFieldsAtEntry.put(successor, new HashMap<>(activeStaticFields));
-      }
-    }
-  }
-
-  private void killActiveFields(FieldInstruction instruction) {
-    DexField field = instruction.getField();
-    if (instruction.isInstancePut()) {
-      // Remove all the field/object pairs that refer to this field to make sure
-      // that we are conservative.
-      List<FieldAndObject> keysToRemove = new ArrayList<>();
-      for (FieldAndObject key : activeInstanceFields.keySet()) {
-        if (key.field == field) {
-          keysToRemove.add(key);
-        }
-      }
-      keysToRemove.forEach((k) -> activeInstanceFields.remove(k));
-    } else if (instruction.isInstanceGet()) {
-      Value object = instruction.asInstanceGet().object();
-      FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-      activeInstanceFields.remove(fieldAndObject);
-    } else if (instruction.isStaticPut()) {
-      if (field.clazz != code.method.method.holder) {
-        // Accessing a static field on a different object could cause <clinit> to run which
-        // could modify any static field on any other object.
-        activeStaticFields.clear();
-      } else {
-        activeStaticFields.remove(field);
-      }
-    } else if (instruction.isStaticGet()) {
-      if (field.clazz != code.method.method.holder) {
-        // Accessing a static field on a different object could cause <clinit> to run which
-        // could modify any static field on any other object.
-        activeStaticFields.clear();
-      }
-    }
-  }
-
-  // If a field get instruction throws an exception it did not have an effect on the
-  // value of the field. Therefore, when propagating across exceptional edges for a
-  // field get instruction we have to exclude that field from the set of known
-  // field values.
-  private void killActiveFieldsForExceptionalExit(FieldInstruction instruction) {
-    DexField field = instruction.getField();
-    if (instruction.isInstanceGet()) {
-      Value object = instruction.asInstanceGet().object();
-      FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-      activeInstanceFields.remove(fieldAndObject);
-    } else if (instruction.isStaticGet()) {
-      activeStaticFields.remove(field);
-    }
-  }
-
-  private void eliminateRedundantRead(
-      InstructionListIterator it, Instruction redundant, Instruction active) {
-    redundant.outValue().replaceUsers(active.outValue());
-    it.removeOrReplaceByDebugLocalRead();
-    active.outValue().uniquePhiUsers().forEach(Phi::removeTrivialPhi);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index f10ff69..7d7baf8 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
@@ -119,17 +122,15 @@
   // PositionRemapper is a stateful function which takes a position (represented by a
   // DexDebugPositionState) and returns a remapped Position.
   private interface PositionRemapper {
-    Position createRemappedPosition(DexDebugPositionState positionState);
+    Position createRemappedPosition(
+        int line, DexString file, DexMethod method, Position callerPosition);
   }
 
   private static class IdentityPositionRemapper implements PositionRemapper {
     @Override
-    public Position createRemappedPosition(DexDebugPositionState positionState) {
-      return new Position(
-          positionState.getCurrentLine(),
-          positionState.getCurrentFile(),
-          positionState.getCurrentMethod(),
-          positionState.getCurrentCallerPosition());
+    public Position createRemappedPosition(
+        int line, DexString file, DexMethod method, Position callerPosition) {
+      return new Position(line, file, method, callerPosition);
     }
   }
 
@@ -137,13 +138,9 @@
     private int nextLineNumber = 1;
 
     @Override
-    public Position createRemappedPosition(DexDebugPositionState positionState) {
-      Position newPosition =
-          new Position(
-              nextLineNumber,
-              positionState.getCurrentFile(),
-              positionState.getCurrentMethod(),
-              null);
+    public Position createRemappedPosition(
+        int line, DexString file, DexMethod method, Position callerPosition) {
+      Position newPosition = new Position(nextLineNumber, file, method, null);
       ++nextLineNumber;
       return newPosition;
     }
@@ -252,57 +249,14 @@
         for (DexEncodedMethod method : methods) {
           List<MappedPosition> mappedPositions = new ArrayList<>();
 
-          if (doesContainPositions(method)) {
-            // Do the actual processing for each method.
-            DexCode dexCode = method.getCode().asDexCode();
-            DexDebugInfo debugInfo = dexCode.getDebugInfo();
-            List<DexDebugEvent> processedEvents = new ArrayList<>();
-
-            // Our pipeline will be:
-            // [debugInfo.events] -> eventFilter -> positionRemapper -> positionEventEmitter ->
-            // [processedEvents]
-            PositionEventEmitter positionEventEmitter =
-                new PositionEventEmitter(
-                    application.dexItemFactory, method.method, processedEvents);
-
-            EventFilter eventFilter =
-                new EventFilter(
-                    debugInfo.startLine,
-                    method.method,
-                    processedEvents::add,
-                    positionState -> {
-                      int currentLine = positionState.getCurrentLine();
-                      assert currentLine >= 0;
-                      Position position = positionRemapper.createRemappedPosition(positionState);
-                      mappedPositions.add(
-                          new MappedPosition(
-                              positionState.getCurrentMethod(),
-                              currentLine,
-                              positionState.getCurrentCallerPosition(),
-                              position.line));
-                      positionEventEmitter.emitPositionEvents(
-                          positionState.getCurrentPc(), position);
-                    });
-            for (DexDebugEvent event : debugInfo.events) {
-              event.accept(eventFilter);
+          Code code = method.getCode();
+          if (code != null) {
+            if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
+              optimizeDexCodePositions(
+                  method, application, positionRemapper, mappedPositions, identityMapping);
+            } else if (code.isCfCode() && doesContainPositions(code.asCfCode())) {
+              optimizeCfCodePositions(method, positionRemapper, mappedPositions);
             }
-
-            DexDebugInfo optimizedDebugInfo =
-                new DexDebugInfo(
-                    positionEventEmitter.getStartLine(),
-                    debugInfo.parameters,
-                    processedEvents.toArray(new DexDebugEvent[processedEvents.size()]));
-
-            // TODO(tamaskenez) Remove this as soon as we have external tests testing not only the
-            // remapping but whether the non-positional debug events remain intact.
-            if (identityMapping) {
-              assert optimizedDebugInfo.startLine == debugInfo.startLine;
-              assert optimizedDebugInfo.events.length == debugInfo.events.length;
-              for (int i = 0; i < debugInfo.events.length; ++i) {
-                assert optimizedDebugInfo.events[i].equals(debugInfo.events[i]);
-              }
-            }
-            dexCode.setDebugInfo(optimizedDebugInfo);
           }
 
           MethodSignature originalSignature = MethodSignature.fromDexMethod(method.method);
@@ -382,21 +336,33 @@
     return classNameMapperBuilder.build();
   }
 
+  private static int getMethodStartLine(DexEncodedMethod method) {
+    Code code = method.getCode();
+    if (code == null) {
+      return 0;
+    }
+    if (code.isDexCode()) {
+      DexDebugInfo dexDebugInfo = code.asDexCode().getDebugInfo();
+      return dexDebugInfo == null ? 0 : dexDebugInfo.startLine;
+    } else if (code.isCfCode()) {
+      List<CfInstruction> instructions = code.asCfCode().getInstructions();
+      for (CfInstruction instruction : instructions) {
+        if (!(instruction instanceof CfPosition)) {
+          continue;
+        }
+        return ((CfPosition) instruction).getPosition().line;
+      }
+    }
+    return 0;
+  }
+
   // Sort by startline, then DexEncodedMethod.slowCompare.
   // Use startLine = 0 if no debuginfo.
   private static void sortMethods(List<DexEncodedMethod> methods) {
     methods.sort(
         (lhs, rhs) -> {
-          Code lhsCode = lhs.getCode();
-          Code rhsCode = rhs.getCode();
-          DexCode lhsDexCode =
-              lhsCode == null || !lhsCode.isDexCode() ? null : lhsCode.asDexCode();
-          DexCode rhsDexCode =
-              rhsCode == null || !rhsCode.isDexCode() ? null : rhsCode.asDexCode();
-          DexDebugInfo lhsDebugInfo = lhsDexCode == null ? null : lhsDexCode.getDebugInfo();
-          DexDebugInfo rhsDebugInfo = rhsDexCode == null ? null : rhsDexCode.getDebugInfo();
-          int lhsStartLine = lhsDebugInfo == null ? 0 : lhsDebugInfo.startLine;
-          int rhsStartLine = rhsDebugInfo == null ? 0 : rhsDebugInfo.startLine;
+          int lhsStartLine = getMethodStartLine(lhs);
+          int rhsStartLine = getMethodStartLine(rhs);
           int startLineDiff = lhsStartLine - rhsStartLine;
           if (startLineDiff != 0) return startLineDiff;
           return DexEncodedMethod.slowCompare(lhs, rhs);
@@ -453,10 +419,19 @@
 
   private static boolean doesContainPositions(DexEncodedMethod method) {
     Code code = method.getCode();
-    if (code == null || !code.isDexCode()) {
+    if (code == null) {
       return false;
     }
-    DexDebugInfo debugInfo = code.asDexCode().getDebugInfo();
+    if (code.isDexCode()) {
+      return doesContainPositions(code.asDexCode());
+    } else if (code.isCfCode()) {
+      return doesContainPositions(code.asCfCode());
+    }
+    return false;
+  }
+
+  private static boolean doesContainPositions(DexCode dexCode) {
+    DexDebugInfo debugInfo = dexCode.getDebugInfo();
     if (debugInfo == null) {
       return false;
     }
@@ -467,4 +442,114 @@
     }
     return false;
   }
+
+  private static boolean doesContainPositions(CfCode cfCode) {
+    List<CfInstruction> instructions = cfCode.getInstructions();
+    for (CfInstruction instruction : instructions) {
+      if (instruction instanceof CfPosition) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private static void optimizeDexCodePositions(
+      DexEncodedMethod method,
+      DexApplication application,
+      PositionRemapper positionRemapper,
+      List<MappedPosition> mappedPositions,
+      boolean identityMapping) {
+    // Do the actual processing for each method.
+    DexCode dexCode = method.getCode().asDexCode();
+    DexDebugInfo debugInfo = dexCode.getDebugInfo();
+    List<DexDebugEvent> processedEvents = new ArrayList<>();
+
+    // Our pipeline will be:
+    // [debugInfo.events] -> eventFilter -> positionRemapper -> positionEventEmitter ->
+    // [processedEvents]
+    PositionEventEmitter positionEventEmitter =
+        new PositionEventEmitter(application.dexItemFactory, method.method, processedEvents);
+
+    EventFilter eventFilter =
+        new EventFilter(
+            debugInfo.startLine,
+            method.method,
+            processedEvents::add,
+            positionState -> {
+              int currentLine = positionState.getCurrentLine();
+              assert currentLine >= 0;
+              Position position =
+                  positionRemapper.createRemappedPosition(
+                      positionState.getCurrentLine(),
+                      positionState.getCurrentFile(),
+                      positionState.getCurrentMethod(),
+                      positionState.getCurrentCallerPosition());
+              mappedPositions.add(
+                  new MappedPosition(
+                      positionState.getCurrentMethod(),
+                      currentLine,
+                      positionState.getCurrentCallerPosition(),
+                      position.line));
+              positionEventEmitter.emitPositionEvents(positionState.getCurrentPc(), position);
+            });
+    for (DexDebugEvent event : debugInfo.events) {
+      event.accept(eventFilter);
+    }
+
+    DexDebugInfo optimizedDebugInfo =
+        new DexDebugInfo(
+            positionEventEmitter.getStartLine(),
+            debugInfo.parameters,
+            processedEvents.toArray(new DexDebugEvent[processedEvents.size()]));
+
+    // TODO(b/111253214) Remove this as soon as we have external tests testing not only the
+    // remapping but whether the non-positional debug events remain intact.
+    if (identityMapping) {
+      assert optimizedDebugInfo.startLine == debugInfo.startLine;
+      assert optimizedDebugInfo.events.length == debugInfo.events.length;
+      for (int i = 0; i < debugInfo.events.length; ++i) {
+        assert optimizedDebugInfo.events[i].equals(debugInfo.events[i]);
+      }
+    }
+    dexCode.setDebugInfo(optimizedDebugInfo);
+  }
+
+  private static void optimizeCfCodePositions(
+      DexEncodedMethod method,
+      PositionRemapper positionRemapper,
+      List<MappedPosition> mappedPositions) {
+    // Do the actual processing for each method.
+    CfCode oldCode = method.getCode().asCfCode();
+    List<CfInstruction> oldInstructions = oldCode.getInstructions();
+    List<CfInstruction> newInstructions = new ArrayList<>(oldInstructions.size());
+    for (int i = 0; i < oldInstructions.size(); ++i) {
+      CfInstruction oldInstruction = oldInstructions.get(i);
+      CfInstruction newInstruction;
+      if (oldInstruction instanceof CfPosition) {
+        CfPosition cfPosition = (CfPosition) oldInstruction;
+        Position oldPosition = cfPosition.getPosition();
+        Position newPosition =
+            positionRemapper.createRemappedPosition(
+                oldPosition.line, oldPosition.file, oldPosition.method, oldPosition.callerPosition);
+        mappedPositions.add(
+            new MappedPosition(
+                oldPosition.method,
+                oldPosition.line,
+                oldPosition.callerPosition,
+                newPosition.line));
+        newInstruction = new CfPosition(cfPosition.getLabel(), newPosition);
+      } else {
+        newInstruction = oldInstruction;
+      }
+      newInstructions.add(newInstruction);
+    }
+    method.setCode(
+        new CfCode(
+            oldCode.getMethod(),
+            oldCode.getMaxStack(),
+            oldCode.getMaxLocals(),
+            newInstructions,
+            oldCode.getTryCatchRanges(),
+            oldCode.getLocalVariables()));
+  }
 }
diff --git a/src/test/examples/shaking19/Shaking.java b/src/test/examples/shaking19/Shaking.java
new file mode 100644
index 0000000..da8de41
--- /dev/null
+++ b/src/test/examples/shaking19/Shaking.java
@@ -0,0 +1,28 @@
+// 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 shaking19;
+
+public class Shaking {
+
+  public static void main(String[] args) {
+    A obj = new B();
+    obj.m();
+  }
+
+  public static class A {
+
+    // Since A is never instantiated and B overrides method m(), this is dead code.
+    public void m() {
+      System.out.println("In A.m()");
+    }
+  }
+
+  public static class B extends A {
+
+    @Override
+    public void m() {
+      System.out.println("In B.m()");
+    }
+  }
+}
diff --git a/src/test/examples/shaking19/keep-rules.txt b/src/test/examples/shaking19/keep-rules.txt
new file mode 100644
index 0000000..cbc332e
--- /dev/null
+++ b/src/test/examples/shaking19/keep-rules.txt
@@ -0,0 +1,9 @@
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class shaking19.Shaking {
+  public static void main(...);
+}
diff --git a/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java b/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java
deleted file mode 100644
index 781d2b4..0000000
--- a/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java
+++ /dev/null
@@ -1,57 +0,0 @@
-// 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 uninitializedfinal;
-
-// Test that leaks an instance before its final field has been initialized to a thread that
-// reads that field. This tests that redundant field load elimination does not eliminate
-// field reads (even of final fields) that cross a monitor operation.
-public class UninitializedFinalFieldLeak {
-
-  public static class PollingThread extends Thread {
-    public int result = 0;
-    UninitializedFinalFieldLeak f;
-
-    PollingThread(UninitializedFinalFieldLeak f) {
-      this.f = f;
-    }
-
-    // Read the field a number of times. Then lock on the object to await field initialization.
-    public void run() {
-      result += f.i;
-      result += f.i;
-      result += f.i;
-      f.threadReadsDone = true;
-      synchronized (f) {
-        result += f.i;
-      }
-      // The right result is 42. Reading the uninitialized 0 three times and then
-      // reading the initialized value. It is safe to remove the two redundant loads
-      // before the monitor operation.
-      System.out.println(result);
-    }
-  }
-
-  public final int i;
-  public volatile boolean threadReadsDone = false;
-
-  public UninitializedFinalFieldLeak() throws InterruptedException {
-    // Leak the object to a thread and start the thread with the lock on the object taken.
-    // Then allow the other thread to run and read the uninitialized field.
-    // Finally, initialize the field and release the lock.
-    PollingThread t = new PollingThread(this);
-    synchronized (this) {
-      t.start();
-      while (!threadReadsDone) {
-        Thread.yield();
-      }
-      i = 42;
-    }
-    t.join();
-  }
-
-  public static void main(String[] args) throws InterruptedException {
-    new UninitializedFinalFieldLeak();
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 221d427..36f7594 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -45,11 +45,9 @@
         "instancevariable.InstanceVariable",
         "instanceofstring.InstanceofString",
         "invoke.Invoke",
-        "invokeempty.InvokeEmpty",
         "jumbostring.JumboString",
         "loadconst.LoadConst",
         "loop.UdpServer",
-        "nestedtrycatches.NestedTryCatches",
         "newarray.NewArray",
         "regalloc.RegAlloc",
         "returns.Returns",
@@ -60,7 +58,9 @@
         "throwing.Throwing",
         "trivial.Trivial",
         "trycatch.TryCatch",
+        "nestedtrycatches.NestedTryCatches",
         "trycatchmany.TryCatchMany",
+        "invokeempty.InvokeEmpty",
         "regress.Regress",
         "regress2.Regress2",
         "regress_37726195.Regress",
@@ -82,7 +82,6 @@
         "enclosingmethod_proguarded.Main",
         "interfaceinlining.Main",
         "switchmaps.Switches",
-        "uninitializedfinal.UninitializedFinalFieldLeak",
     };
 
     List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
diff --git a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
index d944a75..6e1c05f 100644
--- a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -7,12 +7,18 @@
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.debug.DebugTestConfig.RuntimeKind;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
+import java.util.Collection;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 /** Tests source file and line numbers on inlined methods. */
+@RunWith(Parameterized.class)
 public class LineNumberOptimizationTest extends DebugTestBase {
 
   private static final int[] ORIGINAL_LINE_NUMBERS = {20, 7, 8, 28, 8, 20, 21, 12, 21, 22, 16, 22};
@@ -26,25 +32,53 @@
   private static final String FILE2 = CLASS2 + ".java";
   private static final String MAIN_SIGNATURE = "([Ljava/lang/String;)V";
 
+  private RuntimeKind runtimeKind;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static Collection<Object[]> setup() {
+    return ImmutableList.of(
+        new Object[] {"CF", RuntimeKind.CF}, new Object[] {"DEX", RuntimeKind.DEX});
+  }
+
+  public LineNumberOptimizationTest(String name, RuntimeKind runtimeKind) {
+    this.runtimeKind = runtimeKind;
+  }
+
   private static DebugTestConfig makeConfig(
       LineNumberOptimization lineNumberOptimization,
       boolean writeProguardMap,
-      boolean dontOptimizeByEnablingDebug)
+      boolean dontOptimizeByEnablingDebug,
+      RuntimeKind runtimeKind)
       throws Exception {
-    AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
     Path outdir = temp.newFolder().toPath();
     Path outjar = outdir.resolve("r8_compiled.jar");
-    Path proguardMapPath = writeProguardMap ? outdir.resolve("proguard.map") : null;
+
     R8Command.Builder builder =
         R8Command.builder()
             .addProgramFiles(DEBUGGEE_JAR)
-            .setMinApiLevel(minSdk.getLevel())
-            .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
-            .setMode(dontOptimizeByEnablingDebug ? CompilationMode.DEBUG : CompilationMode.RELEASE)
-            .setOutput(outjar, OutputMode.DexIndexed);
-    if (proguardMapPath != null) {
-      builder.setProguardMapOutputPath(proguardMapPath);
+            .setMode(dontOptimizeByEnablingDebug ? CompilationMode.DEBUG : CompilationMode.RELEASE);
+    DebugTestConfig config = null;
+
+    if (runtimeKind == RuntimeKind.CF) {
+      builder.setOutput(outjar, OutputMode.ClassFile);
+      config = new CfDebugTestConfig(outjar);
+    } else {
+      assert (runtimeKind == RuntimeKind.DEX);
+      AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
+      builder
+          .setMinApiLevel(minSdk.getLevel())
+          .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
+          .setOutput(outjar, OutputMode.DexIndexed);
+      config = new D8DebugTestConfig();
     }
+
+    config.addPaths(outjar);
+    if (writeProguardMap) {
+      Path proguardMapPath = outdir.resolve("proguard.map");
+      builder.setProguardMapOutputPath(proguardMapPath);
+      config.setProguardMap(proguardMapPath);
+    }
+
     ToolHelper.runR8(
         builder.build(),
         options -> {
@@ -53,46 +87,52 @@
           }
           options.enableInlining = false;
         });
-    DebugTestConfig config = new D8DebugTestConfig();
-    config.addPaths(outjar);
-    config.setProguardMap(proguardMapPath);
+
     return config;
   }
 
   @Test
   public void testIdentityCompilation() throws Throwable {
     // Compilation will fail if the identity translation does.
-    makeConfig(LineNumberOptimization.IDENTITY_MAPPING, true, false);
+    makeConfig(LineNumberOptimization.IDENTITY_MAPPING, true, false, runtimeKind);
   }
 
   @Test
   public void testNotOptimized() throws Throwable {
-    testRelease(makeConfig(LineNumberOptimization.OFF, false, false), ORIGINAL_LINE_NUMBERS);
+    testRelease(
+        makeConfig(LineNumberOptimization.OFF, false, false, runtimeKind), ORIGINAL_LINE_NUMBERS);
   }
 
   @Test
   public void testNotOptimizedWithMap() throws Throwable {
-    testRelease(makeConfig(LineNumberOptimization.OFF, true, false), ORIGINAL_LINE_NUMBERS);
+    testRelease(
+        makeConfig(LineNumberOptimization.OFF, true, false, runtimeKind), ORIGINAL_LINE_NUMBERS);
   }
 
   @Test
   public void testNotOptimizedByEnablingDebug() throws Throwable {
-    testDebug(makeConfig(LineNumberOptimization.OFF, false, true), ORIGINAL_LINE_NUMBERS_DEBUG);
+    testDebug(
+        makeConfig(LineNumberOptimization.OFF, false, true, runtimeKind),
+        ORIGINAL_LINE_NUMBERS_DEBUG);
   }
 
   @Test
   public void testNotOptimizedByEnablingDebugWithMap() throws Throwable {
-    testDebug(makeConfig(LineNumberOptimization.OFF, true, true), ORIGINAL_LINE_NUMBERS_DEBUG);
+    testDebug(
+        makeConfig(LineNumberOptimization.OFF, true, true, runtimeKind),
+        ORIGINAL_LINE_NUMBERS_DEBUG);
   }
 
   @Test
   public void testOptimized() throws Throwable {
-    testRelease(makeConfig(LineNumberOptimization.ON, false, false), OPTIMIZED_LINE_NUMBERS);
+    testRelease(
+        makeConfig(LineNumberOptimization.ON, false, false, runtimeKind), OPTIMIZED_LINE_NUMBERS);
   }
 
   @Test
   public void testOptimizedWithMap() throws Throwable {
-    testRelease(makeConfig(LineNumberOptimization.ON, true, false), ORIGINAL_LINE_NUMBERS);
+    testRelease(
+        makeConfig(LineNumberOptimization.ON, true, false, runtimeKind), ORIGINAL_LINE_NUMBERS);
   }
 
   private void testDebug(DebugTestConfig config, int[] lineNumbers) throws Throwable {
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
new file mode 100644
index 0000000..e5be477
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
@@ -0,0 +1,62 @@
+// 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.shaking.examples;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase.MinifyMode;
+import com.android.tools.r8.shaking.TreeShakingTest;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TreeShaking19Test extends TreeShakingTest {
+
+  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  public static Collection<Object[]> data() {
+    List<Object[]> parameters = new ArrayList<>();
+    for (MinifyMode minify : MinifyMode.values()) {
+      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
+      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
+      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
+    }
+    return parameters;
+  }
+
+  public TreeShaking19Test(Frontend frontend, Backend backend, MinifyMode minify) {
+    super("examples/shaking19", "shaking19.Shaking", frontend, backend, minify);
+  }
+
+  @Ignore("b/111199171")
+  @Test
+  public void test() throws Exception {
+    runTest(
+        TreeShaking19Test::unusedRemoved,
+        null,
+        null,
+        ImmutableList.of("src/test/examples/shaking19/keep-rules.txt"),
+        // Disable vertical class merging to prevent A from being merged into B.
+        opt -> opt.enableClassMerging = false);
+  }
+
+  private static void unusedRemoved(DexInspector inspector) {
+    ClassSubject clazz = inspector.clazz("shaking19.Shaking$A");
+    assertThat(clazz, isPresent());
+
+    MethodSubject method = clazz.method("void", "m", ImmutableList.of());
+    assertThat(method, not(isPresent()));
+  }
+}