Merge "Ensure consistent remove() behaviour after basic block split"
diff --git a/build.gradle b/build.gradle
index 9a48a4d..63513ce 100644
--- a/build.gradle
+++ b/build.gradle
@@ -114,7 +114,7 @@
 
 dependencies {
     compile 'net.sf.jopt-simple:jopt-simple:4.6'
-    compile 'com.googlecode.json-simple:json-simple:1.1.1'
+    compile 'com.googlecode.json-simple:json-simple:1.1'
     compile group: 'com.google.guava', name: 'guava', version: '19.0'
     compile group: 'it.unimi.dsi', name: 'fastutil', version: '7.2.0'
     compile group: 'org.apache.commons', name: 'commons-compress', version: '1.12'
@@ -326,12 +326,15 @@
     // In order to build without dependencies, pass the exclude_deps property using:
     // gradle -Pexclude_deps R8
     if (!project.hasProperty('exclude_deps')) {
-        // Relocating dependencies to avoid conflicts.
-        relocate 'com.google', 'com.android.tools.r8.com.google'
+        // Relocating dependencies to avoid conflicts. Keep this as precise as possible
+        // to avoid rewriting unrelated strings.
+        relocate 'com.google.common', 'com.android.tools.r8.com.google.common'
+        relocate 'com.google.thirdparty', 'com.android.tools.r8.com.google.thirdparty'
         relocate 'joptsimple', 'com.android.tools.r8.joptsimple'
-        relocate 'org', 'com.android.tools.r8.org'
+        relocate 'org.apache.commons', 'com.android.tools.r8.org.apache.commons'
+        relocate 'org.objectweb.asm', 'com.android.tools.r8.org.objectweb.asm'
+        relocate 'org.json.simple', 'com.android.tools.r8.org.json.simple'
         relocate 'it.unimi.dsi.fastutil', 'com.android.tools.r8.it.unimi.dsi.fastutil'
-        relocate 'junit', 'com.android.tools.r8.junit'
         // Also include dependencies
         configurations = [project.configurations.compile]
     }
@@ -348,12 +351,15 @@
     // In order to build without dependencies, pass the exclude_deps property using:
     // gradle -Pexclude_deps D8
     if (!project.hasProperty('exclude_deps')) {
-        // Relocating dependencies to avoid conflicts.
-        relocate 'com.google', 'com.android.tools.r8.com.google'
+        // Relocating dependencies to avoid conflicts. Keep this as precise as possible
+        // to avoid rewriting unrelated strings.
+        relocate 'com.google.common', 'com.android.tools.r8.com.google.common'
+        relocate 'com.google.thirdparty', 'com.android.tools.r8.com.google.thirdparty'
         relocate 'joptsimple', 'com.android.tools.r8.joptsimple'
-        relocate 'org', 'com.android.tools.r8.org'
+        relocate 'org.apache.commons', 'com.android.tools.r8.org.apache.commons'
+        relocate 'org.objectweb.asm', 'com.android.tools.r8.org.objectweb.asm'
+        relocate 'org.json.simple', 'com.android.tools.r8.org.json.simple'
         relocate 'it.unimi.dsi.fastutil', 'com.android.tools.r8.it.unimi.dsi.fastutil'
-        relocate 'junit', 'com.android.tools.r8.junit'
         // Also include dependencies
         configurations = [project.configurations.compile]
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
index 8a001ae..49adcb4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
@@ -59,26 +59,26 @@
       int left = leftValue().getConstInstruction().asConstNumber().getIntValue();
       int right = rightValue().getConstInstruction().asConstNumber().getIntValue();
       int result = foldIntegers(left, right);
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.INT, value, result);
     } else if (type == NumericType.LONG) {
       long left = leftValue().getConstInstruction().asConstNumber().getLongValue();
       long right = rightValue().getConstInstruction().asConstNumber().getLongValue();
       long result = foldLongs(left, right);
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.LONG, value, result);
     } else if (type == NumericType.FLOAT) {
       float left = leftValue().getConstInstruction().asConstNumber().getFloatValue();
       float right = rightValue().getConstInstruction().asConstNumber().getFloatValue();
       float result = foldFloat(left, right);
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.FLOAT, value, Float.floatToIntBits(result));
     } else {
       assert type == NumericType.DOUBLE;
       double left = leftValue().getConstInstruction().asConstNumber().getDoubleValue();
       double right = rightValue().getConstInstruction().asConstNumber().getDoubleValue();
       double result = foldDouble(left, right);
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.DOUBLE, value, Double.doubleToLongBits(result));
     }
   }
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 2863df6..c9e26b4 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
@@ -582,10 +582,6 @@
           for (Value value : instruction.getDebugValues()) {
             value.removeDebugUser(instruction);
           }
-          Value previousLocalValue = instruction.getPreviousLocalValue();
-          if (previousLocalValue != null) {
-            previousLocalValue.removeDebugUser(instruction);
-          }
         }
       }
     }
@@ -1178,7 +1174,7 @@
       // Remove the move-exception instruction.
       move = entry().asMoveException();
       position = move.getPosition();
-      assert move.getPreviousLocalValue() == null;
+      assert move.getDebugValues().isEmpty();
       getInstructions().remove(0);
     }
     // Create new predecessor blocks.
@@ -1192,8 +1188,7 @@
       BasicBlock newBlock = new BasicBlock();
       newPredecessors.add(newBlock);
       if (hasMoveException) {
-        Value value = new Value(
-            valueNumberGenerator.next(), MoveType.OBJECT, move.getDebugInfo());
+        Value value = new Value(valueNumberGenerator.next(), MoveType.OBJECT, move.getLocalInfo());
         values.add(value);
         MoveException newMove = new MoveException(value);
         newBlock.add(newMove);
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 1f1ebe2..f5bc204 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
@@ -115,10 +115,6 @@
     for (Value value : current.getDebugValues()) {
       value.removeDebugUser(current);
     }
-    Value previousLocalValue = current.getPreviousLocalValue();
-    if (previousLocalValue != null) {
-      previousLocalValue.removeDebugUser(current);
-    }
     listIterator.remove();
     current = null;
   }
@@ -348,7 +344,7 @@
     assert invoke.inValues().size() == arguments.size();
     for (int i = 0; i < invoke.inValues().size(); i++) {
       // TODO(zerny): Support inlining in --debug mode.
-      assert arguments.get(i).getDebugInfo() == null;
+      assert arguments.get(i).getLocalInfo() == null;
       if ((i == 0) && (downcast != null)) {
         Value invokeValue = invoke.inValues().get(0);
         Value receiverValue = arguments.get(0);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Cmp.java b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
index 9b54353..aeb0a51 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Cmp.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
@@ -180,7 +180,7 @@
       }
     }
     assert result == -1 || result == 0 || result == 1;
-    Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+    Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
     return new ConstNumber(ConstType.INT, value, result);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index e2d41b7..661a602 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -33,10 +33,7 @@
 
   public static ConstNumber copyOf(IRCode code, ConstNumber original) {
     Value newValue =
-        new Value(
-            code.valueNumberGenerator.next(),
-            original.outType(),
-            original.getDebugInfo());
+        new Value(code.valueNumberGenerator.next(), original.outType(), original.getLocalInfo());
     return new ConstNumber(original.type, newValue, original.getRawValue());
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 824d75c..21ed16e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
@@ -155,14 +156,11 @@
         values.add(phi);
         for (Value value : phi.getOperands()) {
           values.add(value);
-          if (value.isPhi()) {
-            Phi phiOperand = value.asPhi();
-            assert phiOperand.getBlock().getPhis().contains(phiOperand);
-            assert phiOperand.uniquePhiUsers().contains(phi);
-          } else {
-            Instruction definition = value.definition;
-            assert definition.outValue() == value;
-          }
+          assert value.uniquePhiUsers().contains(phi);
+        }
+        for (Value value : phi.getDebugValues()) {
+          values.add(value);
+          value.debugPhiUsers().contains(phi);
         }
       }
       for (Instruction instruction : block.getInstructions()) {
@@ -171,33 +169,41 @@
         if (outValue != null) {
           values.add(outValue);
           assert outValue.definition == instruction;
-          Value previousLocalValue = outValue.getPreviousLocalValue();
-          if (previousLocalValue != null) {
-            values.add(previousLocalValue);
-            assert previousLocalValue.debugUsers().contains(instruction);
-          }
         }
         for (Value value : instruction.inValues()) {
           values.add(value);
           assert value.uniqueUsers().contains(instruction);
-          if (value.isPhi()) {
-            Phi phi = value.asPhi();
-            assert phi.getBlock().getPhis().contains(phi);
-          } else {
-            Instruction definition = value.definition;
-            assert definition.outValue() == value;
-          }
+        }
+        for (Value value : instruction.getDebugValues()) {
+          values.add(value);
+          assert value.debugUsers().contains(instruction);
         }
       }
     }
 
     for (Value value : values) {
+      assert verifyValue(value);
       assert consistentValueUses(value);
     }
 
     return true;
   }
 
+  private boolean verifyValue(Value value) {
+    assert value.isPhi() ? verifyPhi(value.asPhi()) : verifyDefinition(value);
+    return true;
+  }
+
+  private boolean verifyPhi(Phi phi) {
+    assert phi.getBlock().getPhis().contains(phi);
+    return true;
+  }
+
+  private boolean verifyDefinition(Value value) {
+    assert value.definition.outValue() == value;
+    return true;
+  }
+
   private boolean consistentValueUses(Value value) {
     for (Instruction user : value.uniqueUsers()) {
       assert user.inValues().contains(value);
@@ -206,10 +212,13 @@
       assert phiUser.getOperands().contains(value);
       assert phiUser.getBlock().getPhis().contains(phiUser);
     }
-    if (value.debugUsers() != null) {
+    if (value.getLocalInfo() != null) {
       for (Instruction debugUser : value.debugUsers()) {
-        assert debugUser.getPreviousLocalValue() == value
-            || debugUser.getDebugValues().contains(value);
+        assert debugUser.getDebugValues().contains(value);
+      }
+      for (Phi phiUser : value.debugPhiUsers()) {
+        assert verifyPhi(phiUser);
+        assert phiUser.getDebugValues().contains(value);
       }
     }
     return true;
@@ -367,8 +376,8 @@
     return arguments;
   }
 
-  public Value createValue(MoveType moveType, Value.DebugInfo debugInfo) {
-    return new Value(valueNumberGenerator.next(), moveType, debugInfo);
+  public Value createValue(MoveType moveType, DebugLocalInfo local) {
+    return new Value(valueNumberGenerator.next(), moveType, local);
   }
 
   public Value createValue(MoveType moveType) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index e33c796..13325d8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Value.DebugInfo;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -65,10 +64,6 @@
     outValue = value;
     if (outValue != null) {
       outValue.definition = this;
-      Value previousLocalValue = getPreviousLocalValue();
-      if (previousLocalValue != null) {
-        previousLocalValue.addDebugUser(this);
-      }
     }
   }
 
@@ -107,27 +102,29 @@
     }
   }
 
-  public void replaceDebugPhi(Phi phi, Value value) {
-    if (debugValues != null) {
-      for (int i = 0; i < debugValues.size(); i++) {
-        if (phi == debugValues.get(i)) {
-          if (value.getLocalInfo() == null) {
-            debugValues.remove(i);
-          } else {
-            debugValues.set(i, value);
-            value.addDebugUser(this);
-          }
+  // Similar to Phi::replaceTrivialPhi, removal can cause a concurrent modification error.
+  // TODO(ager): Consider unifying with other replace methods and avoid the C.M.E.
+  public boolean replaceDebugValue(Value oldValue, Value newValue) {
+    if (debugValues == null) {
+      return false;
+    }
+    int found = -1;
+    for (int i = 0; i < debugValues.size(); i++) {
+      if (oldValue == debugValues.get(i)) {
+        assert found == -1;
+        found = i;
+        if (newValue.getLocalInfo() != null) {
+          // TODO(zerny): Insert a write if replacing a phi with different debug-local info.
+          debugValues.set(i, newValue);
+          newValue.addDebugUser(this);
         }
       }
     }
-    if (phi == getPreviousLocalValue()) {
-      if (value.getDebugInfo() == null) {
-        replacePreviousLocalValue(null);
-      } else {
-        replacePreviousLocalValue(value);
-        value.addDebugUser(this);
-      }
+    if (found >= 0 && newValue.getLocalInfo() == null) {
+      // TODO(zerny): Insert a write if replacing a phi with associated debug-local info.
+      debugValues.remove(found);
     }
+    return found >= 0;
   }
 
   /**
@@ -323,26 +320,14 @@
 
   public abstract int maxOutValueRegister();
 
-  public DebugInfo getDebugInfo() {
-    return outValue == null ? null : outValue.getDebugInfo();
-  }
-
   public DebugLocalInfo getLocalInfo() {
     return outValue == null ? null : outValue.getLocalInfo();
   }
 
-  public Value getPreviousLocalValue() {
-    return outValue == null ? null : outValue.getPreviousLocalValue();
-  }
-
   public List<Value> getDebugValues() {
     return debugValues != null ? debugValues : ImmutableList.of();
   }
 
-  public void replacePreviousLocalValue(Value value) {
-    outValue.replacePreviousLocalValue(value);
-  }
-
   public boolean isArrayGet() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
index 52a0f55..770278a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
@@ -37,7 +37,7 @@
       int left = leftValue().getConstInstruction().asConstNumber().getIntValue();
       int right = rightValue().getConstInstruction().asConstNumber().getIntValue();
       int result = foldIntegers(left, right);
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.INT, value, result);
     } else {
       assert type == NumericType.LONG;
@@ -50,7 +50,7 @@
         right = rightValue().getConstInstruction().asConstNumber().getLongValue();
       }
       long result = foldLongs(left, right);
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.LONG, value, result);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Neg.java b/src/main/java/com/android/tools/r8/ir/code/Neg.java
index 59022de..f858d70 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Neg.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Neg.java
@@ -31,20 +31,20 @@
     assert canBeFolded();
     if (type == NumericType.INT) {
       int result = -source().getConstInstruction().asConstNumber().getIntValue();
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.INT, value, result);
     } else if (type == NumericType.LONG) {
       long result = -source().getConstInstruction().asConstNumber().getLongValue();
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.LONG, value, result);
     } else if (type == NumericType.FLOAT) {
       float result = -source().getConstInstruction().asConstNumber().getFloatValue();
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.FLOAT, value, Float.floatToIntBits(result));
     } else {
       assert type == NumericType.DOUBLE;
       double result = -source().getConstInstruction().asConstNumber().getDoubleValue();
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.DOUBLE, value, Double.doubleToLongBits(result));
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Not.java b/src/main/java/com/android/tools/r8/ir/code/Not.java
index cae6118..4c42b80 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Not.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Not.java
@@ -27,12 +27,12 @@
     assert canBeFolded();
     if (type == NumericType.INT) {
       int result = ~(source().getConstInstruction().asConstNumber().getIntValue());
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.INT, value, result);
     } else {
       assert type == NumericType.LONG;
       long result = ~source().getConstInstruction().asConstNumber().getLongValue();
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.LONG, value, result);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 7e05dc9..185d2fd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -20,6 +21,7 @@
 
   private final BasicBlock block;
   private final List<Value> operands = new ArrayList<>();
+  private List<Value> debugValues = null;
 
   // Trivial phis are eliminated during IR construction. When a trivial phi is eliminated
   // we need to update all references to it. A phi can be referenced from phis, instructions
@@ -36,7 +38,7 @@
   private MoveType outType = null;
 
   public Phi(int number, BasicBlock block, MoveType type, DebugLocalInfo local) {
-    super(number, type, local == null ? null : new DebugInfo(local, null));
+    super(number, type, local);
     this.block = block;
     block.addPhi(this);
   }
@@ -96,6 +98,15 @@
     removeTrivialPhi();
   }
 
+  public void addDebugValue(Value value) {
+    assert value.getLocalInfo() != null;
+    if (debugValues == null) {
+      debugValues = new ArrayList<>();
+    }
+    debugValues.add(value);
+    value.addDebugPhiUser(this);
+  }
+
   private void throwUndefinedValueError() {
     throw new CompilationError(
         "Undefined value encountered during compilation. "
@@ -157,6 +168,20 @@
     }
   }
 
+  private void replaceTrivialDebugPhi(Value current, Value newValue) {
+    if (debugValues == null) {
+      return;
+    }
+    assert current.getLocalInfo() != null;
+    assert current.getLocalInfo() == newValue.getLocalInfo();
+    for (int i = 0; i < debugValues.size(); i++) {
+      if (debugValues.get(i) == current) {
+        debugValues.set(i, newValue);
+        newValue.addDebugPhiUser(this);
+      }
+    }
+  }
+
   public boolean isTrivialPhi() {
     Value same = null;
     for (Value op : operands) {
@@ -202,8 +227,15 @@
       user.replaceTrivialPhi(this, same);
     }
     if (debugUsers() != null) {
+      List<Instruction> removed = new ArrayList<>();
       for (Instruction user : debugUsers()) {
-        user.replaceDebugPhi(this, same);
+        if (user.replaceDebugValue(this, same)) {
+          removed.add(user);
+        }
+      }
+      removed.forEach(this::removeDebugUser);
+      for (Phi user : debugPhiUsers()) {
+        user.replaceTrivialDebugPhi(this, same);
       }
     }
     // If IR construction is taking place, update the definition users.
@@ -316,4 +348,8 @@
   public boolean needsRegister() {
     return true;
   }
+
+  public List<Value> getDebugValues() {
+    return debugValues != null ? debugValues : ImmutableList.of();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 44bd9d4..78cb4a5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -18,39 +18,17 @@
 
 public class Value {
 
-  /**
-   * Immutable view of the debug info associated with an SSA value.
-   *
-   * Used during IR building and to construct replacement values.
-   */
-  public static class DebugInfo {
-    private final DebugLocalInfo local;
-    private final Value previousLocalValue;
-
-    public DebugInfo(DebugLocalInfo local, Value previousLocalValue) {
-      assert local != null;
-      this.local = local;
-      this.previousLocalValue = previousLocalValue;
-    }
-  }
-
-  // Actual internal data for the debug information of locals.
+  // Lazily allocated internal data for the debug information of locals.
   // This is wrapped in a class to avoid multiple pointers in the value structure.
   private static class DebugData {
     final DebugLocalInfo local;
-    Value previousLocalValue;
-    Set<Instruction> debugUsers = new HashSet<>();
+    Set<Instruction> users = new HashSet<>();
+    Set<Phi> phiUsers = new HashSet<>();
     List<Instruction> localStarts = new ArrayList<>();
     List<Instruction> localEnds = new ArrayList<>();
 
-    DebugData(DebugInfo info) {
-      this(info.local, info.previousLocalValue);
-    }
-
-    DebugData(DebugLocalInfo local, Value previousLocalValue) {
-      assert previousLocalValue == null || !previousLocalValue.isUninitializedLocal();
+    DebugData(DebugLocalInfo local) {
       this.local = local;
-      this.previousLocalValue = previousLocalValue;
     }
   }
 
@@ -73,10 +51,10 @@
   private LongInterval valueRange;
   private final DebugData debugData;
 
-  public Value(int number, MoveType type, DebugInfo debugInfo) {
+  public Value(int number, MoveType type, DebugLocalInfo local) {
     this.number = number;
     this.type = type;
-    this.debugData = debugInfo == null ? null : new DebugData(debugInfo);
+    this.debugData = local == null ? null : new DebugData(local);
   }
 
   public boolean isFixedRegisterValue() {
@@ -95,26 +73,10 @@
     return type.requiredRegisters();
   }
 
-  public DebugInfo getDebugInfo() {
-    return debugData == null ? null : new DebugInfo(debugData.local, debugData.previousLocalValue);
-  }
-
   public DebugLocalInfo getLocalInfo() {
     return debugData == null ? null : debugData.local;
   }
 
-  public Value getPreviousLocalValue() {
-    return debugData == null ? null : debugData.previousLocalValue;
-  }
-
-  public void replacePreviousLocalValue(Value value) {
-    if (value == null || value.isUninitializedLocal()) {
-      debugData.previousLocalValue = null;
-    } else {
-      debugData.previousLocalValue = value;
-    }
-  }
-
   public List<Instruction> getDebugLocalStarts() {
     return debugData.localStarts;
   }
@@ -189,10 +151,11 @@
   }
 
   public Set<Instruction> debugUsers() {
-    if (debugData == null) {
-      return null;
-    }
-    return Collections.unmodifiableSet(debugData.debugUsers);
+    return debugData == null ? null : Collections.unmodifiableSet(debugData.users);
+  }
+
+  public Set<Phi> debugPhiUsers() {
+    return debugData == null ? null : Collections.unmodifiableSet(debugData.phiUsers);
   }
 
   public int numberOfUsers() {
@@ -211,18 +174,30 @@
     return uniquePhiUsers().size();
   }
 
+  public int numberOfAllNonDebugUsers() {
+    return numberOfUsers() + numberOfPhiUsers();
+  }
+
   public int numberOfDebugUsers() {
-    return debugData == null ? 0 : debugData.debugUsers.size();
+    return debugData == null ? 0 : debugData.users.size();
+  }
+
+  public int numberOfDebugPhiUsers() {
+    return debugData == null ? 0 : debugData.phiUsers.size();
+  }
+
+  public int numberOfAllDebugUsers() {
+    return numberOfDebugUsers() + numberOfDebugPhiUsers();
   }
 
   public int numberOfAllUsers() {
-    return numberOfUsers() + numberOfPhiUsers() + numberOfDebugUsers();
+    return numberOfAllNonDebugUsers() + numberOfAllDebugUsers();
   }
 
   public boolean isUsed() {
     return !users.isEmpty()
         || !phiUsers.isEmpty()
-        || ((debugData != null) && !debugData.debugUsers.isEmpty());
+        || ((debugData != null) && !debugData.users.isEmpty());
   }
 
   public boolean usedInMonitorOperation() {
@@ -250,7 +225,7 @@
     phiUsers.clear();
     uniquePhiUsers = null;
     if (debugData != null) {
-      debugData.debugUsers.clear();
+      debugData.users.clear();
     }
   }
 
@@ -268,7 +243,14 @@
     if (isUninitializedLocal()) {
       return;
     }
-    debugData.debugUsers.add(user);
+    debugData.users.add(user);
+  }
+
+  public void addDebugPhiUser(Phi user) {
+    if (isUninitializedLocal()) {
+      return;
+    }
+    debugData.phiUsers.add(user);
   }
 
   public boolean isUninitializedLocal() {
@@ -280,7 +262,11 @@
   }
 
   public void removeDebugUser(Instruction user) {
-    debugData.debugUsers.remove(user);
+    debugData.users.remove(user);
+  }
+
+  public void removeDebugPhiUser(Phi user) {
+    debugData.phiUsers.remove(user);
   }
 
   public boolean hasUsersInfo() {
@@ -293,7 +279,8 @@
     phiUsers = null;
     uniquePhiUsers = null;
     if (debugData != null) {
-      debugData.debugUsers = null;
+      debugData.users = null;
+      debugData.phiUsers = null;
     }
   }
 
@@ -328,10 +315,15 @@
           }
           return v;
         });
-        if (user.getPreviousLocalValue() == this) {
-          newValue.addDebugUser(user);
-          user.replacePreviousLocalValue(newValue);
-        }
+      }
+      for (Phi user : debugPhiUsers()) {
+        user.getDebugValues().replaceAll(v -> {
+          if (v == this) {
+            newValue.addDebugPhiUser(user);
+            return newValue;
+          }
+          return v;
+        });
       }
     }
     clearUsers();
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 976020c2..1f29d26 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
@@ -75,7 +75,6 @@
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Ushr;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.Value.DebugInfo;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.utils.InternalOptions;
@@ -269,6 +268,7 @@
   private final InternalOptions options;
 
   // Pending local changes.
+  private Value previousLocalValue = null;
   private List<Value> debugLocalStarts = new ArrayList<>();
   private List<Value> debugLocalReads = new ArrayList<>();
   private List<Value> debugLocalEnds = new ArrayList<>();
@@ -489,16 +489,14 @@
 
   public void addThisArgument(int register) {
     DebugLocalInfo local = getCurrentLocal(register);
-    DebugInfo info = local == null ? null : new DebugInfo(local, null);
-    Value value = writeRegister(register, MoveType.OBJECT, ThrowingInfo.NO_THROW, info);
+    Value value = writeRegister(register, MoveType.OBJECT, ThrowingInfo.NO_THROW, local);
     addInstruction(new Argument(value));
     value.markAsThis();
   }
 
   public void addNonThisArgument(int register, MoveType moveType) {
     DebugLocalInfo local = getCurrentLocal(register);
-    DebugInfo info = local == null ? null : new DebugInfo(local, null);
-    Value value = writeRegister(register, moveType, ThrowingInfo.NO_THROW, info);
+    Value value = writeRegister(register, moveType, ThrowingInfo.NO_THROW, local);
     addInstruction(new Argument(value));
   }
 
@@ -1096,7 +1094,7 @@
 
   public void addMoveException(int dest) {
     Value out = writeRegister(dest, MoveType.OBJECT, ThrowingInfo.NO_THROW);
-    assert out.getDebugInfo() == null;
+    assert out.getLocalInfo() == null;
     MoveException instruction = new MoveException(out);
     assert !instruction.instructionTypeCanThrow();
     if (!currentBlock.getInstructions().isEmpty()) {
@@ -1501,21 +1499,18 @@
 
   // This special write register is needed when changing the scoping of a local variable.
   // See addDebugLocalStart and addDebugLocalEnd.
-  private Value writeRegister(int register, MoveType type, ThrowingInfo throwing, DebugInfo info) {
+  private Value writeRegister(
+      int register, MoveType type, ThrowingInfo throwing, DebugLocalInfo local) {
     checkRegister(register);
-    Value value = new Value(valueNumberGenerator.next(), type, info);
+    Value value = new Value(valueNumberGenerator.next(), type, local);
     currentBlock.writeCurrentDefinition(register, value, throwing);
     return value;
   }
 
   public Value writeRegister(int register, MoveType type, ThrowingInfo throwing) {
     DebugLocalInfo local = getCurrentLocal(register);
-    DebugInfo info = null;
-    if (local != null) {
-      Value previousLocal = readRegisterIgnoreLocal(register, type);
-      info = new DebugInfo(local, previousLocal.getLocalInfo() != local ? null : previousLocal);
-    }
-    return writeRegister(register, type, throwing, info);
+    previousLocalValue = local == null ? null : readRegisterIgnoreLocal(register, type);
+    return writeRegister(register, type, throwing, local);
   }
 
   public Value writeNumericRegister(int register, NumericType type, ThrowingInfo throwing) {
@@ -1596,6 +1591,12 @@
     if (!options.debug) {
       return;
     }
+    // Add a use if this instruction is overwriting a previous value of the same local.
+    if (previousLocalValue != null && previousLocalValue.getLocalInfo() == ir.getLocalInfo()) {
+      assert ir.outValue() != null;
+      ir.addDebugValue(previousLocalValue);
+    }
+    previousLocalValue = null;
     if (debugLocalStarts.isEmpty() && debugLocalReads.isEmpty() && debugLocalEnds.isEmpty()) {
       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 1b31a57..1aed78f 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
@@ -260,7 +260,8 @@
   @Override
   public void buildPrelude(IRBuilder builder) {
     // Record types for arguments.
-    Map<Integer, MoveType> initializedLocals = recordArgumentTypes();
+    Int2ReferenceMap<MoveType> argumentLocals = recordArgumentTypes();
+    Int2ReferenceMap<MoveType> initializedLocals = new Int2ReferenceOpenHashMap<>(argumentLocals);
     // Initialize all non-argument locals to ensure safe insertion of debug-local instructions.
     for (Object o : node.localVariables) {
       LocalVariableNode local = (LocalVariableNode) o;
@@ -294,7 +295,11 @@
     }
     // Add debug information for all locals at the initial label.
     if (initialLabel != null) {
-      state.openLocals(initialLabel);
+      for (Local local : state.openLocals(initialLabel)) {
+        if (!argumentLocals.containsKey(local.slot.register)) {
+          builder.addDebugLocalStart(local.slot.register, local.info);
+        }
+      }
     }
     // Build the actual argument instructions now that type and debug information is known
     // for arguments.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index a7c40d1..71de2b6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -51,7 +51,7 @@
     if (insn.outValue() == null) {
       return null;
     } else {
-      return code.createValue(insn.outType(), insn.getDebugInfo());
+      return code.createValue(insn.outType(), insn.getLocalInfo());
     }
   }
 
@@ -105,7 +105,7 @@
             // Fix up the return type if needed.
             if (actualTarget.proto.returnType != invokedMethod.proto.returnType
                 && newInvoke.outValue() != null) {
-              Value newValue = code.createValue(newInvoke.outType(), invoke.getDebugInfo());
+              Value newValue = code.createValue(newInvoke.outType(), invoke.getLocalInfo());
               newInvoke.outValue().replaceUsers(newValue);
               CheckCast cast = new CheckCast(
                   newValue,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index 3a1902e..50f224c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -61,10 +61,6 @@
     for (Value debugValue : instruction.getDebugValues()) {
       updateWorklist(worklist, debugValue);
     }
-    Value previousLocalValue = instruction.getPreviousLocalValue();
-    if (previousLocalValue != null) {
-      updateWorklist(worklist, previousLocalValue);
-    }
   }
 
   private static void removeDeadPhis(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index a34c8cb..241106c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -75,7 +75,7 @@
     MoveType moveType = instruction.outValue().outType();
     if (rule != null && rule.hasReturnValue() && rule.getReturnValue().isSingleValue()) {
       assert moveType != MoveType.OBJECT;
-      Value value = code.createValue(moveType, instruction.getDebugInfo());
+      Value value = code.createValue(moveType, instruction.getLocalInfo());
       replacement = new ConstNumber(
           ConstType.fromMoveType(moveType), value, rule.getReturnValue().getSingleValue());
     }
@@ -84,7 +84,7 @@
       DexField field = rule.getReturnValue().getField();
       DexEncodedField staticField = appInfo.lookupStaticTarget(field.clazz, field);
       if (staticField != null) {
-        Value value = code.createValue(moveType, instruction.getDebugInfo());
+        Value value = code.createValue(moveType, instruction.getLocalInfo());
         replacement = staticField.staticValue.asConstInstruction(false, value);
       } else {
         throw new CompilationError(field.clazz.toSourceString() + "." + field.name.toString() +
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 460fe92..6cd453b 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -1841,6 +1841,8 @@
         int predIndex = succ.getPredecessors().indexOf(block);
         for (Phi phi : succ.getPhis()) {
           live.add(phi.getOperand(predIndex));
+          assert phi.getDebugValues().stream().allMatch(Value::needsRegister);
+          live.addAll(phi.getDebugValues());
         }
       }
       ListIterator<Instruction> iterator =
@@ -1855,17 +1857,8 @@
             live.add(use);
           }
         }
-        if (options.debug) {
-          for (Value use : instruction.getDebugValues()) {
-            assert use.needsRegister();
-            live.add(use);
-          }
-          Value use = instruction.getPreviousLocalValue();
-          if (use != null) {
-            assert use.needsRegister();
-            live.add(use);
-          }
-        }
+        assert instruction.getDebugValues().stream().allMatch(Value::needsRegister);
+        live.addAll(instruction.getDebugValues());
       }
       for (Phi phi : block.getPhis()) {
         live.remove(phi);
@@ -1942,6 +1935,8 @@
         for (Phi phi : successor.getPhis()) {
           live.remove(phi);
           phiOperands.add(phi.getOperand(successor.getPredecessors().indexOf(block)));
+          assert phi.getDebugValues().stream().allMatch(Value::needsRegister);
+          phiOperands.addAll(phi.getDebugValues());
         }
       }
       live.addAll(phiOperands);
@@ -1990,14 +1985,6 @@
               addLiveRange(use, block, number);
             }
           }
-          Value use = instruction.getPreviousLocalValue();
-          if (use != null) {
-            assert use.needsRegister();
-            if (!live.contains(use)) {
-              live.add(use);
-              addLiveRange(use, block, number);
-            }
-          }
         }
       }
     }
diff --git a/src/test/examples/shaking17/AbstractProgramClass.java b/src/test/examples/shaking17/AbstractProgramClass.java
new file mode 100644
index 0000000..d3b696b
--- /dev/null
+++ b/src/test/examples/shaking17/AbstractProgramClass.java
@@ -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.
+package shaking17;
+
+public abstract class AbstractProgramClass extends shakinglib.AbstractLibraryClass {
+
+  public abstract int abstractMethod();
+}
diff --git a/src/test/examples/shaking17/Shaking.java b/src/test/examples/shaking17/Shaking.java
new file mode 100644
index 0000000..78cc495
--- /dev/null
+++ b/src/test/examples/shaking17/Shaking.java
@@ -0,0 +1,16 @@
+// 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.
+package shaking17;
+
+public class Shaking {
+
+  public static void main(String[] args) {
+    Subclass subclass = new Subclass();
+    callTheMethod(subclass);
+  }
+
+  private static void callTheMethod(AbstractProgramClass abstractProgramClass) {
+    System.out.print(abstractProgramClass.abstractMethod());
+  }
+}
diff --git a/src/test/examples/shaking17/Subclass.java b/src/test/examples/shaking17/Subclass.java
new file mode 100644
index 0000000..237dca9
--- /dev/null
+++ b/src/test/examples/shaking17/Subclass.java
@@ -0,0 +1,12 @@
+// 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.
+package shaking17;
+
+public class Subclass extends AbstractProgramClass {
+
+  @Override
+  public int abstractMethod() {
+    return 42;
+  }
+}
diff --git a/src/test/examples/shaking17/keep-rules.txt b/src/test/examples/shaking17/keep-rules.txt
new file mode 100644
index 0000000..27dd8f3
--- /dev/null
+++ b/src/test/examples/shaking17/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 shaking17.Shaking {
+  public static void main(...);
+}
diff --git a/src/test/examples/shakinglib/AbstractLibraryClass.java b/src/test/examples/shakinglib/AbstractLibraryClass.java
new file mode 100644
index 0000000..614b53e
--- /dev/null
+++ b/src/test/examples/shakinglib/AbstractLibraryClass.java
@@ -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.
+package shakinglib;
+
+public abstract class AbstractLibraryClass {
+
+  public abstract int abstractMethod();
+}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index f88ea49..74e4e9b 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.internal;
 
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
index efa812a..8c72ddd 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.internal;
 
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.CompilationMode;
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 08e90c0..6eebf6b 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.regalloc;
 
-import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.ConstNumber;
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
index 671698b..813ea1b 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
-import static junit.framework.Assert.assertNull;
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.fail;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.errors.CompilationError;
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
index 87d350c..4710e69 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
-import static junit.framework.Assert.assertNull;
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.fail;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.errors.CompilationError;
 import java.util.Arrays;
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
index e1ace71..cbf40a2 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
@@ -3,11 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
-import static junit.framework.Assert.assertNull;
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.fail;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
-import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.errors.CompilationError;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
diff --git a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
index be28b38..0ab229f 100644
--- a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 51b6ab5..ab6d6e2 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -478,6 +478,12 @@
     inspector.forAllClasses((clazz) -> checkClassAndMemberInDictionary(clazz));
   }
 
+  private static void abstractMethodRemains(DexInspector inspector) {
+    ClassSubject programClass = inspector.clazz("shaking17.AbstractProgramClass");
+    Assert.assertTrue(programClass.isPresent());
+    Assert.assertTrue(
+        programClass.method("int", "abstractMethod", Collections.emptyList()).isPresent());
+  }
 
   private static void assumenosideeffects1CheckOutput(String output1, String output2) {
     Assert.assertEquals("noSideEffectVoid\nnoSideEffectInt\n", output1);
@@ -620,6 +626,7 @@
             "shaking14",
             "shaking15",
             "shaking16",
+            "shaking17",
             "inlining",
             "minification",
             "minifygeneric",
@@ -684,22 +691,17 @@
         .put("shaking11:keep-rules.txt", TreeShakingTest::shaking11OnlyOneClassKept);
     inspections
         .put("shaking11:keep-rules-keep-method.txt", TreeShakingTest::shaking11BothMethodsKept);
-    inspections
-        .put("shaking12:keep-rules.txt",
+    inspections.put("shaking12:keep-rules.txt",
             TreeShakingTest::shaking12OnlyInstantiatedClassesHaveConstructors);
-    inspections
-        .put("shaking13:keep-rules.txt",
+    inspections.put("shaking13:keep-rules.txt",
             TreeShakingTest::shaking13EnsureFieldWritesCorrect);
-    inspections
-        .put("shaking14:keep-rules.txt",
+    inspections.put("shaking14:keep-rules.txt",
             TreeShakingTest::shaking14EnsureRightStaticMethodsLive);
-    inspections.put("shaking15:keep-rules.txt",
-        TreeShakingTest::shaking15testDictionary);
-    inspections
-        .put("annotationremoval:keep-rules.txt",
+    inspections.put("shaking15:keep-rules.txt", TreeShakingTest::shaking15testDictionary);
+    inspections.put("shaking17:keep-rules.txt", TreeShakingTest::abstractMethodRemains);
+    inspections.put("annotationremoval:keep-rules.txt",
             TreeShakingTest::annotationRemovalHasNoInnerClassAnnotations);
-    inspections
-        .put("annotationremoval:keep-rules-keep-innerannotation.txt",
+    inspections.put("annotationremoval:keep-rules-keep-innerannotation.txt",
             TreeShakingTest::annotationRemovalHasAllInnerClassAnnotations);
     inspections
         .put("simpleproto1:keep-rules.txt", TreeShakingTest::simpleproto1UnusedFieldIsGone);
diff --git a/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java b/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java
index 53ee6dd..f47025a 100644
--- a/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java
+++ b/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java
@@ -5,9 +5,8 @@
 package com.android.tools.r8.smali;
 
 import java.lang.reflect.Method;
-import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.stream.Collectors;
 
 public class CheckSwitchInTestClass {
   public static void main(String[] args) throws Exception {
@@ -16,10 +15,10 @@
     Method method = test.getMethod("test", int.class);
 
     // Get keys and default value from arguments.
-    List<Integer> keys = Arrays.stream(Arrays.copyOfRange(args, 0, args.length - 1))
-        .map(Integer::parseInt)
-        .sorted()
-        .collect(Collectors.toList());
+    List<Integer> keys = new ArrayList<>();
+    for (int i = 0; i < args.length - 1; i++) {
+      keys.add(Integer.parseInt(args[i]));
+    }
     int defaultValue = Integer.parseInt(args[args.length - 1]);
 
     // Run over all keys and test a small interval around each.
diff --git a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
index e4ae46c..c256920 100644
--- a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
@@ -534,4 +534,4 @@
     runConvertCasesToIf(ImmutableList.of(
         Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2);
   }
-}
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/utils/R8InliningTest.java b/src/test/java/com/android/tools/r8/utils/R8InliningTest.java
index 7f3c3e1..7af5708 100644
--- a/src/test/java/com/android/tools/r8/utils/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/utils/R8InliningTest.java
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
-import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.R8Command;