Merge "Annotate StringConsumer with @KeepForSubclassing"
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index 8fb77c1..ec38ea9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -32,7 +32,7 @@
   @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     Slot array = state.pop();
-    assert array.type.isObjectOrNull();
+    assert array.type.isObject();
     builder.addArrayLength(state.push(builder.getFactory().intType).register, array.register);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index c0b1a07..811a00c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -71,7 +71,7 @@
     Slot index = state.pop();
     Slot array = state.pop();
     Slot value;
-    assert array.type.isObjectOrNull();
+    assert array.type.isObject();
     ValueType memberType = ValueType.fromMemberType(type);
     if (array.preciseType != null) {
       value = state.push(array.preciseType.toArrayElementType(builder.getFactory()));
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
index f452cf1..b0a5a3c 100644
--- a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
+++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -8,8 +8,8 @@
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DexSplitterHelper;
-import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
 import com.android.tools.r8.utils.AbortException;
 import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.ExceptionUtils;
@@ -24,44 +24,14 @@
 import java.util.ArrayList;
 import java.util.List;
 
-public class DexSplitter {
+@Keep
+public final class DexSplitter {
 
   private static final String DEFAULT_OUTPUT_DIR = "output";
   private static final String DEFAULT_BASE_NAME = "base";
 
   private static final boolean PRINT_ARGS = false;
 
-  public static class Reporter implements DiagnosticsHandler {
-
-    private final DiagnosticsHandler handler = new DefaultDiagnosticsHandler();
-    private boolean reportedErrors = false;
-
-    public RuntimeException fatal(Diagnostic error) {
-      error(error);
-      throw new AbortException();
-    }
-
-    public boolean hasReportedErrors() {
-      return reportedErrors;
-    }
-
-    @Override
-    public void error(Diagnostic error) {
-      handler.error(error);
-      reportedErrors = true;
-    }
-
-    @Override
-    public void warning(Diagnostic warning) {
-      handler.warning(warning);
-    }
-
-    @Override
-    public void info(Diagnostic info) {
-      handler.info(info);
-    }
-  }
-
   public static class FeatureJar {
     private String jar;
     private String outputName;
@@ -94,8 +64,9 @@
 
   }
 
-  public static class Options {
-    private final Reporter reporter = new Reporter();
+  @Keep
+  public static final class Options {
+    private final DiagnosticsHandler diagnosticsHandler = new DefaultDiagnosticsHandler();
     private List<String> inputArchives = new ArrayList<>();
     private List<FeatureJar> featureJars = new ArrayList<>();
     private String baseOutputName = DEFAULT_BASE_NAME;
@@ -103,8 +74,8 @@
     private String featureSplitMapping;
     private String proguardMap;
 
-    public Reporter getReporter() {
-      return reporter;
+    public DiagnosticsHandler getDiagnosticsHandler() {
+      return diagnosticsHandler;
     }
 
     public String getOutput() {
@@ -143,7 +114,7 @@
       inputArchives.add(inputArchive);
     }
 
-    protected void addFeatureJar(FeatureJar featureJar) {
+    private void addFeatureJar(FeatureJar featureJar) {
       featureJars.add(featureJar);
     }
 
@@ -159,13 +130,13 @@
       return ImmutableList.copyOf(inputArchives);
     }
 
-    public ImmutableList<FeatureJar> getFeatureJars() {
+    ImmutableList<FeatureJar> getFeatureJars() {
       return ImmutableList.copyOf(featureJars);
     }
 
     // Shorthand error messages.
     public void error(String msg) {
-      reporter.error(new StringDiagnostic(msg));
+      diagnosticsHandler.error(new StringDiagnostic(msg));
     }
   }
 
@@ -231,11 +202,11 @@
       throws FeatureMappingException {
     if (options.getFeatureSplitMapping() != null) {
       return FeatureClassMapping.fromSpecification(
-          Paths.get(options.getFeatureSplitMapping()), options.getReporter());
+          Paths.get(options.getFeatureSplitMapping()), options.getDiagnosticsHandler());
     }
     assert !options.getFeatureJars().isEmpty();
-    return FeatureClassMapping.fromJarFiles(
-        options.getFeatureJars(), options.getBaseOutputName(), options.getReporter());
+    return FeatureClassMapping.Internal.fromJarFiles(
+        options.getFeatureJars(), options.getBaseOutputName(), options.getDiagnosticsHandler());
   }
 
   private static void run(String[] args)
@@ -246,16 +217,20 @@
 
   public static void run(Options options)
       throws FeatureMappingException, CompilationFailedException {
+    boolean errors = false;
     if (options.getInputArchives().isEmpty()) {
+      errors = true;
       options.error("Need at least one --input");
     }
     if (options.getFeatureSplitMapping() == null && options.getFeatureJars().isEmpty()) {
+      errors = true;
       options.error("You must supply a feature split mapping or feature jars");
     }
     if (options.getFeatureSplitMapping() != null && !options.getFeatureJars().isEmpty()) {
+      errors = true;
       options.error("You can't supply both a feature split mapping and feature jars");
     }
-    if (options.getReporter().hasReportedErrors()) {
+    if (errors) {
       throw new AbortException();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 0b85683..a721955 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -35,8 +35,8 @@
   public ArrayPut(MemberType type, Value array, Value index, Value value) {
     super(null, Arrays.asList(array, index, value));
     assert type != null;
-    assert array.type.isObjectOrNull();
-    assert index.type.isSingleOrZero();
+    assert array.type.isObject();
+    assert index.type.isSingle();
     this.type = type;
   }
 
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 f2d0974..71f1bec 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
@@ -281,7 +281,7 @@
     if (outType().isSingle() || outType().isWide()) {
       return PrimitiveTypeLatticeElement.getInstance();
     }
-    assert outType().isObjectOrNull();
+    assert outType().isObject();
     return Top.getInstance();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 8ed38ed..ededf98 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -28,8 +28,8 @@
 
   public InstancePut(MemberType type, DexField field, Value object, Value value) {
     super(type, field, null, Arrays.asList(object, value));
-    assert object().type.isObjectOrNull();
-    assert value().type.compatible(ValueType.fromDexType(field.type));
+    assert object().type.isObject();
+    assert value().type.verifyCompatible(ValueType.fromDexType(field.type));
   }
 
   public Value object() {
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 70a9e11..186839b 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
@@ -245,11 +245,7 @@
     }
     builder.append(" <- phi");
     StringUtils.append(builder, ListUtils.map(operands, Value::toString));
-    ValueType computed = computeOutType(null);
     builder.append(" : ").append(type);
-    if (type != computed) {
-      builder.append(" / ").append(computed);
-    }
     return builder.toString();
   }
 
@@ -312,56 +308,6 @@
     return true;
   }
 
-  private ValueType computeOutType(Set<Phi> active) {
-    // Go through non-phi operands first to determine if we have an operand that dictates the type.
-    for (Value operand : operands) {
-      // Since a constant zero can be either an integer or an Object (null) we skip them
-      // when computing types and rely on other operands to specify the actual type.
-      if (!operand.isPhi()) {
-        if (operand.outType() != ValueType.INT_OR_FLOAT_OR_NULL) {
-          return operand.outType();
-        }
-        assert operand.isZero();
-      }
-    }
-    // We did not find a non-phi operand that dictates the type. Recurse on phi arguments.
-    if (active == null) {
-      active = new HashSet<>();
-    }
-    active.add(this);
-    for (Value operand : operands) {
-      if (operand.isPhi() && !active.contains(operand.asPhi())) {
-        ValueType phiType = operand.asPhi().computeOutType(active);
-        if (phiType != ValueType.INT_OR_FLOAT_OR_NULL) {
-          return phiType;
-        }
-      }
-    }
-    // All operands were the constant zero or phis with out type INT_OR_FLOAT_OR_NULL.
-    return ValueType.INT_OR_FLOAT_OR_NULL;
-  }
-
-  private static boolean verifyUnknownOrCompatible(ValueType known, ValueType computed) {
-    assert computed == ValueType.INT_OR_FLOAT_OR_NULL || known.compatible(computed);
-    return true;
-  }
-
-  @Override
-  public ValueType outType() {
-    if (type != ValueType.INT_OR_FLOAT_OR_NULL) {
-      assert verifyUnknownOrCompatible(type, computeOutType(null));
-      return type;
-    }
-    // If the phi has unknown type (ie, INT_OR_FLOAT_OR_NULL) then it must be computed. This is
-    // because of the type confusion around null and constant zero. The null object can be used in
-    // a single context (if tests) and the single 0 can be used as null. If the instruction
-    // triggering the creation of a phi does not determine the type (eg, a move can be of int,
-    // float or zero/null) we need to compute the actual type based on the operands.
-    ValueType computedType = computeOutType(null);
-    assert computedType.isObjectOrSingle();
-    return computedType;
-  }
-
   @Override
   public boolean isConstant() {
     return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index 1dc0864..6d3a85b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -54,10 +54,10 @@
       int register = builder.allocatedRegister(returnValue(), getNumber());
       switch (MoveType.fromValueType(returnType)) {
         case OBJECT:
-          assert returnValue().outType().isObjectOrNull();
+          assert returnValue().outType().isObject();
           return new ReturnObject(register);
         case SINGLE:
-          assert returnValue().outType().isSingleOrZero();
+          assert returnValue().outType().isSingle();
           return new com.android.tools.r8.code.Return(register);
         case WIDE:
           assert returnValue().outType().isWide();
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueType.java b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
index e49cac6..c414342 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ValueType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
@@ -35,14 +35,6 @@
     return !isWide();
   }
 
-  public boolean isObjectOrNull() {
-    return isObject() || this == INT_OR_FLOAT_OR_NULL;
-  }
-
-  public boolean isSingleOrZero() {
-    return isSingle() || this == INT_OR_FLOAT_OR_NULL;
-  }
-
   public boolean isPreciseType() {
     return this != ValueType.INT_OR_FLOAT
         && this != ValueType.LONG_OR_DOUBLE
@@ -107,8 +99,10 @@
     throw new CompilationError("Cannot compute meet of types: " + this + " and " + other);
   }
 
-  public boolean compatible(ValueType other) {
-    return isWide() == other.isWide();
+  public boolean verifyCompatible(ValueType other) {
+    // Computing meet will throw on incompatible types.
+    assert meet(other) != null;
+    return true;
   }
 
   public int requiredRegisters() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfState.java b/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
index 2ac8e60..8174797 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
@@ -127,7 +127,7 @@
     for (int i = 0; i < current.stack.length; i++) {
       ValueType currentType = current.stack[i].getImprecise();
       ValueType updateType = update.stack[i].getImprecise();
-      if (!currentType.compatible(updateType)) {
+      if (currentType != updateType) {
         throw new CompilationError(
             "Incompatible types in stack position "
                 + i
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 9fb873c..5be54f5 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
@@ -1368,7 +1368,7 @@
 
   public void addReturn(ValueType type, int value) {
     ValueType returnType = ValueType.fromDexType(method.method.proto.returnType);
-    assert returnType.compatible(type);
+    assert returnType.verifyCompatible(type);
     Value in = readRegister(value, returnType);
     addReturn(new Return(in, returnType));
   }
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 b5f0a05..5c25fba 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
@@ -319,7 +319,6 @@
       }
       int localRegister = state.getLocalRegister(local.index, localType);
       ValueType existingLocalType = initializedLocals.get(localRegister);
-      assert existingLocalType == null || existingLocalType.compatible(localValueType);
       if (existingLocalType == null) {
         // For uninitialized entries write the local to ensure it exists in the local state.
         int writeRegister = state.writeLocal(local.index, localType);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 692ac48..9cff141 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1034,8 +1034,7 @@
                 if (argumentIndex != -1 && checkArgumentType(invoke, target.method,
                     argumentIndex)) {
                   Value argument = invoke.arguments().get(argumentIndex);
-                  assert invoke.outType().compatible(argument.outType())
-                      || (options.isGeneratingDex() && verifyCompatibleFromDex(invoke, argument));
+                  assert invoke.outType() == argument.outType();
                   invoke.outValue().replaceUsers(argument);
                   invoke.setOutValue(null);
                 }
@@ -1048,15 +1047,6 @@
     assert code.isConsistentGraph();
   }
 
-  private static boolean verifyCompatibleFromDex(Invoke invoke, Value argument) {
-    ValueType invokeType = invoke.outType();
-    ValueType argumentType = argument.outType();
-    assert argument.isZero();
-    assert (invokeType.isObject() && argumentType.isObjectOrNull())
-        || (invokeType.isSingle() && argumentType.isSingleOrZero());
-    return true;
-  }
-
   /**
    * For supporting assert javac adds the static field $assertionsDisabled to all classes which
    * have methods with assertions. This is used to support the Java VM -ea flag.
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index dc21146..78ef9e9 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -77,10 +77,10 @@
     @Override
     public void print(ReasonFormatter formatter) {
       formatter.addReason("referenced in keep rule:");
-      formatter.addMessage("  " + keepRule + " {");
+      formatter.addMessage("  " + keepRule.toShortString() + " {");
       int ruleCount = 0;
       for (ProguardMemberRule memberRule : keepRule.getMemberRules()) {
-        formatter.addMessage("    " + memberRule);
+        formatter.addMessage("    " + memberRule + ";");
         if (++ruleCount > 10) {
           formatter.addMessage("      <...>");
           break;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
index 653f0cf..8de361e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
@@ -249,7 +249,7 @@
     return result;
   }
 
-  protected StringBuilder append(StringBuilder builder) {
+  protected StringBuilder append(StringBuilder builder, boolean includeMemberRules) {
     StringUtils.appendNonEmpty(builder, "@", classAnnotation, null);
     StringUtils.appendNonEmpty(builder, "", classAccessFlags, null);
     StringUtils.appendNonEmpty(builder, "!", negatedClassAccessFlags.toString().replace(" ", " !"),
@@ -269,18 +269,27 @@
       builder.append(' ');
       builder.append(inheritanceClassName);
     }
-    builder.append(" {\n");
-    memberRules.forEach(memberRule -> {
-      builder.append("  ");
-      builder.append(memberRule);
-      builder.append(";\n");
-    });
-    builder.append("}");
+    if (includeMemberRules) {
+      builder.append(" {\n");
+      memberRules.forEach(memberRule -> {
+        builder.append("  ");
+        builder.append(memberRule);
+        builder.append(";\n");
+      });
+      builder.append("}");
+    }
     return builder;
   }
 
+  /**
+   * Short String representation without member rules.
+   */
+  public String toShortString() {
+    return append(new StringBuilder(), false).toString();
+  }
+
   @Override
   public String toString() {
-    return append(new StringBuilder()).toString();
+    return append(new StringBuilder(), true).toString();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index d4d4bcb..54b90b2 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -604,7 +604,7 @@
       builder.append('\n');
     }
     for (ProguardConfigurationRule rule : rules) {
-      rule.append(builder);
+      rule.append(builder, true);
       builder.append('\n');
     }
     return builder.toString();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index ddc55ab..ce1840f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -66,17 +66,12 @@
   }
 
   @Override
-  protected StringBuilder append(StringBuilder builder) {
+  protected StringBuilder append(StringBuilder builder, boolean includeMemberRules) {
     builder.append("-");
     builder.append(typeString());
     StringUtils.appendNonEmpty(builder, ",", modifierString(), null);
     builder.append(' ');
-    super.append(builder);
+    super.append(builder, includeMemberRules);
     return builder;
   }
-
-  @Override
-  public String toString() {
-    return append(new StringBuilder()).toString();
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
index 8e84935..31dc439 100644
--- a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
+++ b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
@@ -4,8 +4,8 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
-import com.android.tools.r8.dexsplitter.DexSplitter;
 import com.android.tools.r8.dexsplitter.DexSplitter.FeatureJar;
 import com.android.tools.r8.origin.PathOrigin;
 import java.io.IOException;
@@ -40,7 +40,7 @@
  * placement tool.
  */
 @Keep
-public class FeatureClassMapping {
+public final class FeatureClassMapping {
 
   HashMap<String, String> parsedRules = new HashMap<>(); // Already parsed rules.
   boolean usesOnlyExactMappings = true;
@@ -80,17 +80,18 @@
   }
 
   public static FeatureClassMapping fromSpecification(Path file) throws FeatureMappingException {
-    return fromSpecification(file, new DexSplitter.Reporter());
+    return fromSpecification(file, new DefaultDiagnosticsHandler());
   }
 
-  public static FeatureClassMapping fromSpecification(Path file, DexSplitter.Reporter reporter)
+  public static FeatureClassMapping fromSpecification(Path file, DiagnosticsHandler reporter)
       throws FeatureMappingException {
     FeatureClassMapping mapping = new FeatureClassMapping();
     List<String> lines = null;
     try {
       lines = FileUtils.readAllLines(file);
     } catch (IOException e) {
-      throw reporter.fatal(new ExceptionDiagnostic(e, new SpecificationOrigin(file)));
+      reporter.error(new ExceptionDiagnostic(e, new SpecificationOrigin(file)));
+      throw new AbortException();
     }
     for (int i = 0; i < lines.size(); i++) {
       String line = lines.get(i);
@@ -99,33 +100,33 @@
     return mapping;
   }
 
-  public static FeatureClassMapping fromJarFiles(List<FeatureJar> featureJars, String baseName)
-      throws FeatureMappingException {
-    return fromJarFiles(featureJars, baseName, new DexSplitter.Reporter());
-  }
+  public static class Internal {
 
-  public static FeatureClassMapping fromJarFiles(
-      List<FeatureJar> featureJars, String baseName, DexSplitter.Reporter reporter)
-      throws FeatureMappingException {
-    FeatureClassMapping mapping = new FeatureClassMapping();
-    if (mapping.baseName != null) {
-      mapping.baseName = baseName;
-    }
-    for (FeatureJar featureJar : featureJars) {
-      Path jarPath = Paths.get(featureJar.getJar());
-      ArchiveClassFileProvider provider = null;
-      try {
-        provider = new ArchiveClassFileProvider(jarPath);
-      } catch (IOException e) {
-        throw reporter.fatal(new ExceptionDiagnostic(e, new JarFileOrigin(jarPath)));
+    public static FeatureClassMapping fromJarFiles(
+        List<FeatureJar> featureJars, String baseName, DiagnosticsHandler reporter)
+        throws FeatureMappingException {
+      FeatureClassMapping mapping = new FeatureClassMapping();
+      if (mapping.baseName != null) {
+        mapping.baseName = baseName;
       }
-      for (String javaDescriptor : provider.getClassDescriptors()) {
+      for (FeatureJar featureJar : featureJars) {
+        Path jarPath = Paths.get(featureJar.getJar());
+        ArchiveClassFileProvider provider = null;
+        try {
+          provider = new ArchiveClassFileProvider(jarPath);
+        } catch (IOException e) {
+          reporter.error(new ExceptionDiagnostic(e, new JarFileOrigin(jarPath)));
+          throw new AbortException();
+        }
+        for (String javaDescriptor : provider.getClassDescriptors()) {
           String javaType = DescriptorUtils.descriptorToJavaType(javaDescriptor);
           mapping.addMapping(javaType, featureJar.getOutputName());
+        }
       }
+      assert mapping.usesOnlyExactMappings;
+      return mapping;
     }
-    assert mapping.usesOnlyExactMappings;
-    return mapping;
+
   }
 
   private FeatureClassMapping() {}
diff --git a/src/test/examples/shaking18/Base.java b/src/test/examples/shaking18/Base.java
new file mode 100644
index 0000000..f5707d1
--- /dev/null
+++ b/src/test/examples/shaking18/Base.java
@@ -0,0 +1,9 @@
+// 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 shaking18;
+
+public abstract class Base {
+
+  public abstract String getMessage();
+}
diff --git a/src/test/examples/shaking18/Derived1.java b/src/test/examples/shaking18/Derived1.java
new file mode 100644
index 0000000..c80d5e0
--- /dev/null
+++ b/src/test/examples/shaking18/Derived1.java
@@ -0,0 +1,12 @@
+// 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 shaking18;
+
+public class Derived1 extends Base {
+
+  @Override
+  public String getMessage() {
+    return "Hello from Derived1";
+  }
+}
diff --git a/src/test/examples/shaking18/Derived2.java b/src/test/examples/shaking18/Derived2.java
new file mode 100644
index 0000000..9fd8ec4
--- /dev/null
+++ b/src/test/examples/shaking18/Derived2.java
@@ -0,0 +1,12 @@
+// 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 shaking18;
+
+public class Derived2 extends Base {
+
+  @Override
+  public String getMessage() {
+    return "Hello from Derived2";
+  }
+}
diff --git a/src/test/examples/shaking18/DerivedUnused.java b/src/test/examples/shaking18/DerivedUnused.java
new file mode 100644
index 0000000..cdcf50d
--- /dev/null
+++ b/src/test/examples/shaking18/DerivedUnused.java
@@ -0,0 +1,12 @@
+// 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 shaking18;
+
+public class DerivedUnused extends Base {
+
+  @Override
+  public String getMessage() {
+    return "Hello from DerivedUnused";
+  }
+}
diff --git a/src/test/examples/shaking18/Options.java b/src/test/examples/shaking18/Options.java
new file mode 100644
index 0000000..7012340
--- /dev/null
+++ b/src/test/examples/shaking18/Options.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package shaking18;
+
+public class Options {
+  public boolean alwaysFalse = false;
+  public boolean dummy = false;
+
+  public Options() {}
+}
diff --git a/src/test/examples/shaking18/Shaking.java b/src/test/examples/shaking18/Shaking.java
new file mode 100644
index 0000000..73ba5df
--- /dev/null
+++ b/src/test/examples/shaking18/Shaking.java
@@ -0,0 +1,38 @@
+// 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 shaking18;
+
+public class Shaking {
+
+  public static void main(String[] args) {
+    int i = args.length;
+    run(i);
+  }
+
+  private static void run(int i) {
+    // Three invocations of each method to avoid inlining.
+    Options o =
+        i % 2 == 0 ? getOptions(i % 3) : i % 5 == 0 ? getOptions(i % 7) : getOptions(i % 11);
+    print(make(o, i % 13));
+    print(make(o, i % 17));
+    print(make(o, i % 19));
+  }
+
+  private static Base make(Options o, int i) {
+    if (o.alwaysFalse) {
+      return new DerivedUnused();
+    }
+    return o.dummy ? new Derived1() : new Derived2();
+  }
+
+  private static Options getOptions(int i) {
+    Options o = new Options();
+    o.dummy = i % 101 < 23;
+    return o;
+  }
+
+  private static void print(Base b) {
+    System.out.println(b.getMessage());
+  }
+}
diff --git a/src/test/examples/shaking18/keep-rules.txt b/src/test/examples/shaking18/keep-rules.txt
new file mode 100644
index 0000000..18a2ae0
--- /dev/null
+++ b/src/test/examples/shaking18/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 shaking18.Shaking {
+  public static void main(...);
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 9c8193c..ca6feed 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -595,11 +595,6 @@
           // Verifier says: can't modify final field LMain;.staticFinalField.
           .put("600-verifier-fails",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
-          // VFY: args to if-eq/if-ne must both be refs or cat1.
-          .put(
-              "134-reg-promotion",
-              TestCondition.match(
-                  TestCondition.R8DEX_COMPILER, TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // VFY: tried to get class from non-ref register.
           .put("506-verify-aput",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
new file mode 100644
index 0000000..97d9bfc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
@@ -0,0 +1,51 @@
+// 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 com.android.tools.r8.TestBase.MinifyMode;
+import com.android.tools.r8.shaking.TreeShakingTest;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TreeShaking18Test 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 TreeShaking18Test(Frontend frontend, Backend backend, MinifyMode minify) {
+    super("examples/shaking18", "shaking18.Shaking", frontend, backend, minify);
+  }
+
+  @Test
+  public void test() throws Exception {
+    runTest(
+        TreeShaking18Test::unusedRemoved,
+        null,
+        null,
+        ImmutableList.of("src/test/examples/shaking18/keep-rules.txt"));
+  }
+
+  private static void unusedRemoved(DexInspector inspector) {
+    // TODO(b/80455722): Change to assertFalse when tree-shaking detects this case.
+    Assert.assertTrue(
+        "DerivedUnused should be removed", inspector.clazz("shaking18.DerivedUnused").isPresent());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
new file mode 100644
index 0000000..1a74018
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
@@ -0,0 +1,42 @@
+// 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.whyareyoukeeping;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import org.junit.Test;
+
+class A {
+
+}
+
+public class WhyAreYouKeepingTest extends TestBase {
+  @Test
+  public void test() throws Exception {
+    String proguardConfig = String.join("\n", ImmutableList.of(
+        "-keep class " + A.class.getCanonicalName() + " { *; }",
+        "-whyareyoukeeping class " + A.class.getCanonicalName()
+    ));
+    PrintStream stdout = System.out;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    System.setOut(new PrintStream(baos));
+    compileWithR8(ImmutableList.of(A.class), proguardConfig);
+    String output = new String(baos.toByteArray(), Charset.defaultCharset());
+    System.setOut(stdout);
+    String expected = String.join(System.lineSeparator(), ImmutableList.of(
+        "com.android.tools.r8.shaking.whyareyoukeeping.A",
+        "|- is live because referenced in keep rule:",
+        "|    -keep  class com.android.tools.r8.shaking.whyareyoukeeping.A {",
+        "|      *;",
+        "|    };",
+        ""));
+    assertEquals(expected, output);
+  }
+}