Version 1.4.26

Cherry-pick: Don't try to look for a class named "null" when the type name is null
CL: https://r8-review.googlesource.com/c/r8/+/33025/

Cherry-pick: Update testing framework
CL: https://r8-review.googlesource.com/c/r8/+/33027/

Cherry-pick: Improve configuration parser error
CL: https://r8-review.googlesource.com/c/r8/+/33030/

Cherry-pick: Add handling of -checkdiscard in the stand alone main dex list generator
CL: https://r8-review.googlesource.com/c/r8/+/32887/

Cherry-pick: Update tests of -checkdiscard in the standalone main dex list generator
CL: https://r8-review.googlesource.com/c/r8/+/33061/

Bug: 116774422
Bug: 122823789
Change-Id: I556173d07fbf43ed28b6345b504752f8249f6c78
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 48c2cde..14dd1ae 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.shaking.DiscardedChecker;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexClasses;
@@ -72,6 +73,11 @@
         options.mainDexListConsumer.accept(String.join("\n", result), options.reporter);
       }
 
+      if (!mainDexRootSet.checkDiscarded.isEmpty()) {
+        new DiscardedChecker(
+                mainDexRootSet, mainDexClasses.getClasses(), appView.appInfo(), options)
+            .run();
+      }
       // Print -whyareyoukeeping results if any.
       if (whyAreYouKeepingConsumer != null) {
         for (DexDefinition definition : mainDexRootSet.reasonAsked) {
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 6c27fb1..128245f 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.4.25";
+  public static final String LABEL = "1.4.26";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 04e6516..c203270 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -214,9 +214,13 @@
     triggerDelayedParsingIfNeccessary();
     node.instructions.accept(
         new JarRegisterEffectsVisitor(method.getHolder(), registry, application));
-    node.tryCatchBlocks.forEach(tryCatchBlockNode ->
+    for (TryCatchBlockNode tryCatchBlockNode : node.tryCatchBlocks) {
+      // Exception type can be null for "catch all" used for try/finally.
+      if (tryCatchBlockNode.type != null) {
         registry.registerTypeReference(application.getTypeFromDescriptor(
-            DescriptorUtils.getDescriptorFromClassBinaryName(tryCatchBlockNode.type))));
+            DescriptorUtils.getDescriptorFromClassBinaryName(tryCatchBlockNode.type)));
+      }
+    }
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 47d3743..1b74e3b 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -401,13 +401,22 @@
             && (unknownOption.equals("forceinline") || unknownOption.equals("neverinline"))) {
           devMessage = ", this option needs to be turned on explicitly if used for tests.";
         }
-        reporter.error(new StringDiagnostic(
-            "Unknown option \"-" + unknownOption + "\"" + devMessage,
-            origin, getPosition(optionStart)));
+        unknownOption(unknownOption, optionStart, devMessage);
       }
       return true;
     }
 
+    private void unknownOption(String unknownOption, TextPosition optionStart) {
+      unknownOption(unknownOption, optionStart, "");
+    }
+
+    private void unknownOption(
+        String unknownOption, TextPosition optionStart, String additionalMessage) {
+      throw reporter.fatalError((new StringDiagnostic(
+          "Unknown option \"-" + unknownOption + "\"" + additionalMessage,
+          origin, getPosition(optionStart))));
+    }
+
     private boolean parseUnsupportedOptionAndErr(TextPosition optionStart) {
       String option = Iterables.find(UNSUPPORTED_FLAG_OPTIONS, this::skipFlag, null);
       if (option != null) {
@@ -768,14 +777,19 @@
           TextPosition start = getPosition();
           acceptString("-");
           String unknownOption = acceptString();
-          throw reporter.fatalError(new StringDiagnostic(
-              "Unknown option \"-" + unknownOption + "\"",
-              origin,
-              start));
+          unknownOption(unknownOption, start);
         }
       } else {
         builder.setType(ProguardKeepRuleType.KEEP);
       }
+      if (!eof() && !Character.isWhitespace(peekChar()) && peekChar() != ',') {
+        // The only path to here is through "-keep" with an unsupported suffix.
+        unacceptString("-keep");
+        TextPosition start = getPosition();
+        acceptString("-");
+        String unknownOption = acceptString();
+        unknownOption(unknownOption, start);
+      }
       parseRuleModifiers(builder);
     }
 
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index 56dc071..2499ccb 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -288,6 +288,7 @@
    * @return a class descriptor i.e. "Ljava/lang/Object;"
    */
   public static String getDescriptorFromClassBinaryName(String typeBinaryName) {
+    assert typeBinaryName != null;
     return ('L' + typeBinaryName + ';');
   }
 
diff --git a/src/test/java/com/android/tools/r8/GenerateMainDexListResult.java b/src/test/java/com/android/tools/r8/GenerateMainDexListResult.java
new file mode 100644
index 0000000..392a72b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/GenerateMainDexListResult.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2019, 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;
+
+public class GenerateMainDexListResult
+    extends TestBaseResult<GenerateMainDexListResult, GenerateMainDexListRunResult> {
+
+  GenerateMainDexListResult(TestState state) {
+    super(state);
+  }
+
+  @Override
+  public GenerateMainDexListResult self() {
+    return this;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java b/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java
new file mode 100644
index 0000000..b9842f2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2019, 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;
+
+import java.util.List;
+
+public class GenerateMainDexListRunResult extends TestRunResult<GenerateMainDexListRunResult> {
+
+  List<String> mainDexList;
+
+  public GenerateMainDexListRunResult(List<String> mainDexList) {
+    super(null, null);
+    this.mainDexList = mainDexList;
+  }
+
+  @Override
+  protected GenerateMainDexListRunResult self() {
+    return this;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java b/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java
new file mode 100644
index 0000000..6eae670
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2019, 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;
+
+import com.android.tools.r8.GenerateMainDexListCommand.Builder;
+import com.android.tools.r8.debug.DebugTestConfig;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.origin.Origin;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+public class GenerateMainDexListTestBuilder
+    extends TestBaseBuilder<
+        GenerateMainDexListCommand,
+        Builder,
+        GenerateMainDexListResult,
+        GenerateMainDexListRunResult,
+        GenerateMainDexListTestBuilder> {
+
+  private GenerateMainDexListTestBuilder(TestState state, Builder builder) {
+    super(state, builder);
+  }
+
+  public static GenerateMainDexListTestBuilder create(TestState state) {
+    return new GenerateMainDexListTestBuilder(state, GenerateMainDexListCommand.builder());
+  }
+
+  @Override
+  GenerateMainDexListTestBuilder self() {
+    return this;
+  }
+
+  @Override
+  public GenerateMainDexListRunResult run(String mainClass)
+      throws IOException, CompilationFailedException {
+    throw new Unimplemented("No support for running with a main class");
+  }
+
+  @Override
+  public GenerateMainDexListRunResult run(Class mainClass)
+      throws IOException, CompilationFailedException {
+    throw new Unimplemented("No support for running with a main class");
+  }
+
+  public DebugTestConfig debugConfig() {
+    throw new Unimplemented("No support for debug configuration");
+  }
+
+  public GenerateMainDexListRunResult run() throws CompilationFailedException {
+    return new GenerateMainDexListRunResult(GenerateMainDexList.run(builder.build()));
+  }
+
+  public GenerateMainDexListTestBuilder addMainDexRules(Collection<String> rules) {
+    builder.addMainDexRules(new ArrayList<>(rules), Origin.unknown());
+    return self();
+  }
+
+  public GenerateMainDexListTestBuilder addMainDexRules(String... rules) {
+    return addMainDexRules(Arrays.asList(rules));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index e0baab1..1df6808 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -103,6 +103,10 @@
     return ProguardTestBuilder.create(new TestState(temp));
   }
 
+  public static GenerateMainDexListTestBuilder testForMainDexListGenerator(TemporaryFolder temp) {
+    return GenerateMainDexListTestBuilder.create(new TestState(temp));
+  }
+
   public R8TestBuilder testForR8(Backend backend) {
     return testForR8(temp, backend);
   }
@@ -131,6 +135,10 @@
     return testForProguard(temp);
   }
 
+  public GenerateMainDexListTestBuilder testForMainDexListGenerator() {
+    return testForMainDexListGenerator(temp);
+  }
+
   public enum Backend {
     CF,
     DEX
@@ -163,6 +171,8 @@
 
   /**
    * Write lines of text to a temporary file.
+   *
+   * The file will include a line separator after the last line.
    */
   protected Path writeTextToTempFile(String... lines) throws IOException {
     return writeTextToTempFile(System.lineSeparator(), Arrays.asList(lines));
@@ -170,11 +180,28 @@
 
   /**
    * Write lines of text to a temporary file, along with the specified line separator.
+   *
+   * The file will include a line separator after the last line.
    */
   protected Path writeTextToTempFile(String lineSeparator, List<String> lines)
       throws IOException {
+    return writeTextToTempFile(lineSeparator, lines, true);
+  }
+
+  /**
+   * Write lines of text to a temporary file, along with the specified line separator.
+   *
+   * The argument <code>includeTerminatingLineSeparator</code> control if the file will include
+   * a line separator after the last line.
+   */
+  protected Path writeTextToTempFile(
+      String lineSeparator, List<String> lines, boolean includeTerminatingLineSeparator)
+      throws IOException {
     Path file = temp.newFile().toPath();
-    String contents = String.join(lineSeparator, lines) + lineSeparator;
+    String contents = String.join(lineSeparator, lines);
+    if (includeTerminatingLineSeparator) {
+      contents += lineSeparator;
+    }
     Files.write(file, contents.getBytes(StandardCharsets.UTF_8));
     return file;
   }
diff --git a/src/test/java/com/android/tools/r8/TestBaseBuilder.java b/src/test/java/com/android/tools/r8/TestBaseBuilder.java
new file mode 100644
index 0000000..5fcd1b5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestBaseBuilder.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2019, 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;
+
+import com.android.tools.r8.origin.Origin;
+import java.nio.file.Path;
+import java.util.Collection;
+
+public abstract class TestBaseBuilder<
+        C extends BaseCommand,
+        B extends BaseCommand.Builder<C, B>,
+        CR extends TestBaseResult<CR, RR>,
+        RR extends TestRunResult,
+        T extends TestBaseBuilder<C, B, CR, RR, T>>
+    extends TestBuilder<RR, T> {
+
+  final B builder;
+
+  TestBaseBuilder(TestState state, B builder) {
+    super(state);
+    this.builder = builder;
+  }
+
+  @Override
+  public T addProgramClassFileData(Collection<byte[]> classes) {
+    for (byte[] clazz : classes) {
+      builder.addClassProgramData(clazz, Origin.unknown());
+    }
+    return self();
+  }
+
+  @Override
+  public T addProgramFiles(Collection<Path> files) {
+    builder.addProgramFiles(files);
+    return self();
+  }
+
+  @Override
+  public T addLibraryFiles(Collection<Path> files) {
+    builder.addLibraryFiles(files);
+    return self();
+  }
+
+  public T addMainDexListFiles(Collection<Path> files) {
+    builder.addMainDexListFiles(files);
+    return self();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBaseResult.java b/src/test/java/com/android/tools/r8/TestBaseResult.java
new file mode 100644
index 0000000..e7623a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestBaseResult.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2019, 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;
+
+public abstract class TestBaseResult<CR extends TestBaseResult<CR, RR>, RR extends TestRunResult> {
+  final TestState state;
+
+  TestBaseResult(TestState state) {
+    this.state = state;
+  }
+
+  public abstract CR self();
+}
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 61bf1b3..c67c05c 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -3,16 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
-
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.utils.ListUtils;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
 
 public abstract class TestBuilder<RR extends TestRunResult, T extends TestBuilder<RR, T>> {
 
@@ -92,14 +88,6 @@
   }
 
   static Collection<Path> getFilesForInnerClasses(Collection<Class<?>> classes) throws IOException {
-    Set<Path> paths = new HashSet<>();
-    for (Class clazz : classes) {
-      Path path = ToolHelper.getClassFileForTestClass(clazz);
-      String prefix = path.toString().replace(CLASS_EXTENSION, "$");
-      paths.addAll(
-          ToolHelper.getClassFilesForTestDirectory(
-              path.getParent(), p -> p.toString().startsWith(prefix)));
-    }
-    return paths;
+    return ToolHelper.getClassFilesForInnerClasses(classes);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 0c84e7c..d74fab5 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -4,9 +4,6 @@
 package com.android.tools.r8;
 
 import static com.android.tools.r8.TestBase.Backend.DEX;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.fail;
 
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.ToolHelper.DexVm;
@@ -29,19 +26,17 @@
 import org.hamcrest.Matcher;
 
 public abstract class TestCompileResult<
-    CR extends TestCompileResult<CR, RR>, RR extends TestRunResult> {
+        CR extends TestCompileResult<CR, RR>, RR extends TestRunResult>
+    extends TestBaseResult<CR, RR> {
 
-  final TestState state;
   public final AndroidApp app;
   final List<Path> additionalRunClassPath = new ArrayList<>();
 
   TestCompileResult(TestState state, AndroidApp app) {
-    this.state = state;
+    super(state);
     this.app = app;
   }
 
-  public abstract CR self();
-
   public abstract Backend getBackend();
 
   public abstract TestDiagnosticMessages getDiagnosticMessages();
@@ -83,57 +78,27 @@
   }
 
   public CR assertNoMessages() {
-    assertEquals(0, getDiagnosticMessages().getInfos().size());
-    assertEquals(0, getDiagnosticMessages().getWarnings().size());
-    assertEquals(0, getDiagnosticMessages().getErrors().size());
+    getDiagnosticMessages().assertNoMessages();
     return self();
   }
 
   public CR assertOnlyInfos() {
-    assertNotEquals(0, getDiagnosticMessages().getInfos().size());
-    assertEquals(0, getDiagnosticMessages().getWarnings().size());
-    assertEquals(0, getDiagnosticMessages().getErrors().size());
+    getDiagnosticMessages().assertOnlyInfos();
     return self();
   }
 
   public CR assertOnlyWarnings() {
-    assertEquals(0, getDiagnosticMessages().getInfos().size());
-    assertNotEquals(0, getDiagnosticMessages().getWarnings().size());
-    assertEquals(0, getDiagnosticMessages().getErrors().size());
+    getDiagnosticMessages().assertOnlyWarnings();
     return self();
   }
 
   public CR assertWarningMessageThatMatches(Matcher<String> matcher) {
-    assertNotEquals(0, getDiagnosticMessages().getWarnings().size());
-    for (int i = 0; i < getDiagnosticMessages().getWarnings().size(); i++) {
-      if (matcher.matches(getDiagnosticMessages().getWarnings().get(i).getDiagnosticMessage())) {
-        return self();
-      }
-    }
-    StringBuilder builder = new StringBuilder("No warning matches " + matcher.toString());
-    builder.append(System.lineSeparator());
-    if (getDiagnosticMessages().getWarnings().size() == 0) {
-      builder.append("There where no warnings.");
-    } else {
-      builder.append("There where " + getDiagnosticMessages().getWarnings().size() + " warnings:");
-      builder.append(System.lineSeparator());
-      for (int i = 0; i < getDiagnosticMessages().getWarnings().size(); i++) {
-        builder.append(getDiagnosticMessages().getWarnings().get(i).getDiagnosticMessage());
-        builder.append(System.lineSeparator());
-      }
-    }
-    fail(builder.toString());
+    getDiagnosticMessages().assertWarningMessageThatMatches(matcher);
     return self();
   }
 
   public CR assertNoWarningMessageThatMatches(Matcher<String> matcher) {
-    assertNotEquals(0, getDiagnosticMessages().getWarnings().size());
-    for (int i = 0; i < getDiagnosticMessages().getWarnings().size(); i++) {
-      String message = getDiagnosticMessages().getWarnings().get(i).getDiagnosticMessage();
-      if (matcher.matches(message)) {
-        fail("The warning: \"" + message + "\" + matches " + matcher + ".");
-      }
-    }
+    getDiagnosticMessages().assertNoWarningMessageThatMatches(matcher);
     return self();
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index cbd0cb9..77dc0df 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.debug.DebugTestConfig;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -24,7 +23,7 @@
         CR extends TestCompileResult<CR, RR>,
         RR extends TestRunResult,
         T extends TestCompilerBuilder<C, B, CR, RR, T>>
-    extends TestBuilder<RR, T> {
+    extends TestBaseBuilder<C, B, CR, RR, T> {
 
   public static final Consumer<InternalOptions> DEFAULT_OPTIONS =
       new Consumer<InternalOptions>() {
@@ -32,7 +31,6 @@
         public void accept(InternalOptions options) {}
       };
 
-  final B builder;
   final Backend backend;
 
   // Default initialized setup. Can be overwritten if needed.
@@ -44,8 +42,7 @@
   private PrintStream stdout = null;
 
   TestCompilerBuilder(TestState state, B builder, Backend backend) {
-    super(state);
-    this.builder = builder;
+    super(state, builder);
     this.backend = backend;
     defaultLibrary = TestBase.runtimeJar(backend);
     programConsumer = TestBase.emptyConsumer(backend);
@@ -135,30 +132,10 @@
     return self();
   }
 
-  public T addMainDexListFiles(Collection<Path> files) {
-    builder.addMainDexListFiles(files);
-    return self();
-  }
-
-  @Override
-  public T addProgramClassFileData(Collection<byte[]> classes) {
-    for (byte[] clazz : classes) {
-      builder.addClassProgramData(clazz, Origin.unknown());
-    }
-    return self();
-  }
-
-  @Override
-  public T addProgramFiles(Collection<Path> files) {
-    builder.addProgramFiles(files);
-    return self();
-  }
-
   @Override
   public T addLibraryFiles(Collection<Path> files) {
     defaultLibrary = null;
-    builder.addLibraryFiles(files);
-    return self();
+    return super.addLibraryFiles(files);
   }
 
   public T noDesugaring() {
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
index 3e734cc..d1e55f4 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8;
 
 import java.util.List;
+import org.hamcrest.Matcher;
+
 
 public interface TestDiagnosticMessages {
 
@@ -13,4 +15,20 @@
   public List<Diagnostic> getWarnings();
 
   public List<Diagnostic> getErrors();
+
+  public TestDiagnosticMessages assertNoMessages();
+
+  public TestDiagnosticMessages assertOnlyInfos();
+
+  public TestDiagnosticMessages assertOnlyWarnings();
+
+  public TestDiagnosticMessages assertInfosCount(int count);
+
+  public TestDiagnosticMessages assertWarningsCount(int count);
+
+  public TestDiagnosticMessages assertErrorsCount(int count);
+
+  public TestDiagnosticMessages assertWarningMessageThatMatches(Matcher<String> matcher);
+
+  public TestDiagnosticMessages assertNoWarningMessageThatMatches(Matcher<String> matcher);
 }
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index d0476b9..43d71b1 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -4,8 +4,13 @@
 
 package com.android.tools.r8;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+
 import java.util.ArrayList;
 import java.util.List;
+import org.hamcrest.Matcher;
 
 public class TestDiagnosticMessagesImpl implements DiagnosticsHandler, TestDiagnosticMessages {
   private final List<Diagnostic> infos = new ArrayList<>();
@@ -38,4 +43,75 @@
   public List<Diagnostic> getErrors() {
     return errors;
   }
+
+
+  public TestDiagnosticMessages assertNoMessages() {
+    assertEquals(0, getInfos().size());
+    assertEquals(0, getWarnings().size());
+    assertEquals(0, getErrors().size());
+    return this;
+  }
+
+  public TestDiagnosticMessages assertOnlyInfos() {
+    assertNotEquals(0, getInfos().size());
+    assertEquals(0, getWarnings().size());
+    assertEquals(0, getErrors().size());
+    return this;
+  }
+
+  public TestDiagnosticMessages assertOnlyWarnings() {
+    assertEquals(0, getInfos().size());
+    assertNotEquals(0, getWarnings().size());
+    assertEquals(0, getErrors().size());
+    return this;
+  }
+
+  public TestDiagnosticMessages assertInfosCount(int count) {
+    assertEquals(count, getInfos().size());
+    return this;
+  }
+
+  public TestDiagnosticMessages assertWarningsCount(int count) {
+    assertEquals(count, getWarnings().size());
+    return this;
+  }
+
+  public TestDiagnosticMessages assertErrorsCount(int count) {
+    assertEquals(count, getErrors().size());
+    return this;
+  }
+
+  public TestDiagnosticMessages assertWarningMessageThatMatches(Matcher<String> matcher) {
+    assertNotEquals(0, getWarnings().size());
+    for (int i = 0; i < getWarnings().size(); i++) {
+      if (matcher.matches(getWarnings().get(i).getDiagnosticMessage())) {
+        return this;
+      }
+    }
+    StringBuilder builder = new StringBuilder("No warning matches " + matcher.toString());
+    builder.append(System.lineSeparator());
+    if (getWarnings().size() == 0) {
+      builder.append("There where no warnings.");
+    } else {
+      builder.append("There where " + getWarnings().size() + " warnings:");
+      builder.append(System.lineSeparator());
+      for (int i = 0; i < getWarnings().size(); i++) {
+        builder.append(getWarnings().get(i).getDiagnosticMessage());
+        builder.append(System.lineSeparator());
+      }
+    }
+    fail(builder.toString());
+    return this;
+  }
+
+  public TestDiagnosticMessages assertNoWarningMessageThatMatches(Matcher<String> matcher) {
+    assertNotEquals(0, getWarnings().size());
+    for (int i = 0; i < getWarnings().size(); i++) {
+      String message = getWarnings().get(i).getDiagnosticMessage();
+      if (matcher.matches(message)) {
+        fail("The warning: \"" + message + "\" + matches " + matcher + ".");
+      }
+    }
+    return this;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 64c735d..dcfda2c 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.isDexFile;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -57,6 +58,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -849,6 +851,28 @@
         Paths.get("", parts.toArray(new String[parts.size() - 1])));
   }
 
+  public static Collection<Path> getClassFilesForInnerClasses(Path path) throws IOException {
+    Set<Path> paths = new HashSet<>();
+    String prefix = path.toString().replace(CLASS_EXTENSION, "$");
+    paths.addAll(
+        ToolHelper.getClassFilesForTestDirectory(
+            path.getParent(), p -> p.toString().startsWith(prefix)));
+    return paths;
+  }
+
+  public static Collection<Path> getClassFilesForInnerClasses(Collection<Class<?>> classes)
+      throws IOException {
+    Set<Path> paths = new HashSet<>();
+    for (Class clazz : classes) {
+      Path path = ToolHelper.getClassFileForTestClass(clazz);
+      String prefix = path.toString().replace(CLASS_EXTENSION, "$");
+      paths.addAll(
+          ToolHelper.getClassFilesForTestDirectory(
+              path.getParent(), p -> p.toString().startsWith(prefix)));
+    }
+    return paths;
+  }
+
   public static Path getFileNameForTestClass(Class clazz) {
     List<String> parts = getNamePartsForTestClass(clazz);
     return Paths.get("", parts.toArray(new String[parts.size() - 1]));
diff --git a/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java b/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java
index cd85cd4..125c364 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java
@@ -5,15 +5,17 @@
 package com.android.tools.r8.maindexlist.checkdiscard;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
+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;
 
 class HelloWorldMain {
   public static void main(String[] args) {
@@ -25,22 +27,59 @@
 
 class NonMainDexClass {}
 
+@RunWith(Parameterized.class)
 public class MainDexListCheckDiscard extends TestBase {
+  private enum Command {
+    R8,
+    Generator
+  }
+
+  private static final List<Class<?>> CLASSES =
+      ImmutableList.of(HelloWorldMain.class, MainDexClass.class, NonMainDexClass.class);
+
+  @Parameters(name = "{0}")
+  public static Object[] parameters() {
+    return Command.values();
+  }
+
+  private final Command command;
+
+  public MainDexListCheckDiscard(Command command) {
+    this.command = command;
+  }
+
+  public void runTestWithR8(String checkDiscardRule) throws Exception {
+    testForR8(Backend.DEX)
+        .addProgramClasses(CLASSES)
+        .setMinApi(AndroidApiLevel.K)
+        .addMainDexRules(keepMainProguardConfiguration(HelloWorldMain.class))
+        .addMainDexRules(checkDiscardRule)
+        .noTreeShaking()
+        .noMinification()
+        .compile();
+  }
+
+  public void runTestWithGenerator(String checkDiscardRule) throws Exception {
+    testForMainDexListGenerator()
+        .addProgramClasses(CLASSES)
+        .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+        .addMainDexRules(keepMainProguardConfiguration(HelloWorldMain.class))
+        .addMainDexRules(checkDiscardRule)
+        .run();
+  }
+
   public void runTest(String checkDiscardRule, boolean shouldFail) throws Exception {
-    R8Command command =
-        ToolHelper.prepareR8CommandBuilder(
-                readClasses(HelloWorldMain.class, MainDexClass.class, NonMainDexClass.class))
-            .addMainDexRules(
-                ImmutableList.of(keepMainProguardConfiguration(HelloWorldMain.class)),
-                Origin.unknown())
-            .addMainDexRules(ImmutableList.of(checkDiscardRule), Origin.unknown())
-            .setOutput(temp.getRoot().toPath(), OutputMode.DexIndexed)
-            .setMode(CompilationMode.RELEASE)
-            .setDisableTreeShaking(true)
-            .setDisableMinification(true)
-            .build();
     try {
-      ToolHelper.runR8(command);
+      switch (command) {
+        case R8:
+          runTestWithR8(checkDiscardRule);
+          break;
+        case Generator:
+          runTestWithGenerator(checkDiscardRule);
+          break;
+        default:
+          throw new Unreachable();
+      }
     } catch (CompilationFailedException e) {
       Assert.assertTrue(shouldFail);
       return;
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index ff84486..db5f631 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -1113,6 +1113,39 @@
   }
 
   @Test
+  public void parseInvalidKeepOption() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-keepx public class * {       ",
+        "  native <methods>;           ",
+        "}                             "
+    );
+    try {
+      ProguardConfigurationParser parser =
+          new ProguardConfigurationParser(new DexItemFactory(), reporter);
+      parser.parse(proguardConfig);
+      fail();
+    } catch (AbortException e) {
+      checkDiagnostics(handler.errors, proguardConfig, 1, 1,
+          "Unknown option", "-keepx");
+    }
+  }
+
+  @Test
+  public void parseKeepOptionEOF() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        System.lineSeparator(), ImmutableList.of("-keep"), false);
+    try {
+      ProguardConfigurationParser parser =
+          new ProguardConfigurationParser(new DexItemFactory(), reporter);
+      parser.parse(proguardConfig);
+      fail();
+    } catch (AbortException e) {
+      checkDiagnostics(handler.errors, proguardConfig, 1, 6,
+          "Expected [!]interface|@interface|class|enum");
+    }
+  }
+
+  @Test
   public void parseInvalidKeepClassOption() throws Exception {
     Path proguardConfig = writeTextToTempFile(
         "-keepclassx public class * {  ",
@@ -1493,7 +1526,7 @@
         parser.parse(createConfigurationForTesting(ImmutableList.of(option + " class A { *; }")));
         fail("Expect to fail due to testing option being turned off.");
       } catch (AbortException e) {
-        assertEquals(2, handler.errors.size());
+        assertEquals(1, handler.errors.size());
         checkDiagnostics(handler.errors, 0, null, 1, 1, "Unknown option \"" + option + "\"");
       }
     }