Remove DexOverflowException

Rename MainDexOverflowException to MainDexOverflow and add new method
Reporter.fatalError(MainDexOverflow) to give the correct error message
for respectively R8 and D8. Pass BaseCompilerCommand to Reporter so that
the Reporter knows which message to use.

Switch InheritanceClassInDexDistributor to throwing CompilationError
instead of DexOverflowException, and remove DexOverflowException as a
result.

Change-Id: I2172e44c9641ecc75ae3e597fa73e9bd511efc3e
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index e393f32..799d834 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -37,7 +37,7 @@
     programConsumer = null;
     mode = null;
     minApiLevel = 0;
-    reporter = new Reporter(new DefaultDiagnosticsHandler());
+    reporter = new Reporter(new DefaultDiagnosticsHandler(), this);
     enableDesugaring = true;
     optimizeMultidexForLinearAlloc = false;
   }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d14f96e..5d29d90 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.ClassAndMemberPublicizer;
 import com.android.tools.r8.graph.DexApplication;
@@ -179,7 +178,7 @@
       String proguardSeedsData,
       InternalOptions options,
       ProguardMapSupplier proguardMapSupplier)
-      throws ExecutionException, DexOverflowException {
+      throws ExecutionException {
     try {
       Marker marker = getMarker(options);
       if (options.isGeneratingClassFiles()) {
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index dcc89c6..7ef43a3 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.utils.AndroidApp;
@@ -175,7 +174,7 @@
   }
 
   private static void writeApp(DexApplication app, Path output, ExecutorService executor)
-      throws IOException, ExecutionException, DexOverflowException {
+      throws IOException, ExecutionException {
     InternalOptions options = new InternalOptions();
     AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
     ApplicationWriter writer = new ApplicationWriter(app, options, null, null, null, null, null);
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 9721d3b..e1905ff 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -162,7 +161,7 @@
   }
 
   private Iterable<VirtualFile> distribute(ExecutorService executorService)
-      throws ExecutionException, IOException, DexOverflowException {
+      throws ExecutionException, IOException {
     // Distribute classes into dex files.
     VirtualFile.Distributor distributor;
     if (options.isGeneratingDexFilePerClassFile()) {
@@ -179,8 +178,7 @@
     return distributor.run();
   }
 
-  public void write(ExecutorService executorService)
-      throws IOException, ExecutionException, DexOverflowException {
+  public void write(ExecutorService executorService) throws IOException, ExecutionException {
     application.timing.begin("DexApplication.write");
     try {
       insertAttributeAnnotations();
diff --git a/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java b/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
index 3d0b01c..9e75e45 100644
--- a/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
+++ b/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.dex;
 
 import com.android.tools.r8.dex.VirtualFile.VirtualFileCycler;
-import com.android.tools.r8.errors.DexOverflowException;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -306,7 +306,7 @@
     directSubClasses = new DirectSubClassesInfo(app, classes);
   }
 
-  public void distribute() throws DexOverflowException {
+  public void distribute() {
     List<ClassGroup> remainingInheritanceGroups = collectInheritanceGroups();
     // Sort to ensure reproducible allocation
     remainingInheritanceGroups.sort(null);
@@ -369,8 +369,7 @@
     return groupClassNumber;
   }
 
-  private Collection<VirtualFile> assignGroup(ClassGroup group,
-      List<VirtualFile> dexBlackList) throws DexOverflowException {
+  private Collection<VirtualFile> assignGroup(ClassGroup group, List<VirtualFile> dexBlackList) {
     VirtualFileCycler cycler = new VirtualFileCycler(dexes, namingLens, dexIndexOffset);
     if (group.members.isEmpty()) {
       return Collections.emptyList();
@@ -411,8 +410,8 @@
    * They will fail to link during DexOpt but they will be loaded only once.
    * @param classes set of classes to assign, the set will be destroyed during assignment.
    */
-  private Collection<VirtualFile> assignClassesWithLinkingError(Set<DexProgramClass> classes,
-      Collection<VirtualFile> dexBlackList) throws DexOverflowException {
+  private Collection<VirtualFile> assignClassesWithLinkingError(
+      Set<DexProgramClass> classes, Collection<VirtualFile> dexBlackList) {
 
     List<ClassGroup> layers = collectNoDirectInheritanceGroups(classes);
 
@@ -440,7 +439,7 @@
             dexForLayer.abortTransaction();
             if (dexForLayer.isEmpty()) {
               // The class is too big to fit in one dex
-              throw new DexOverflowException("Class '" + dexProgramClass.toSourceString()
+              throw new CompilationError("Class '" + dexProgramClass.toSourceString()
                   + "' from " + dexProgramClass.getOrigin().toString()
                   + " is too big to fit in a dex.");
             }
@@ -614,8 +613,8 @@
    * Assign as many classes as possible by layer starting by roots.
    * @return the list of classes that were not assigned.
    */
-  private Set<DexProgramClass> assignFromRoot(VirtualFile dex,
-      Collection<DexProgramClass> classes) throws DexOverflowException {
+  private Set<DexProgramClass> assignFromRoot(
+      VirtualFile dex, Collection<DexProgramClass> classes) {
 
     int totalClasses = classes.size();
     int assignedClasses = 0;
@@ -635,7 +634,7 @@
             dex.abortTransaction();
             if (dex.isEmpty()) {
               // The class is too big to fit in one dex
-              throw new DexOverflowException("Class '" + clazz.toSourceString() + "' from "
+              throw new CompilationError("Class '" + clazz.toSourceString() + "' from "
                   + clazz.getOrigin().toString() + " is too big to fit in a dex.");
             }
             isLayerFullyAssigned = false;
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 7f55b19..9ac752a 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -3,9 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
-import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.errors.InternalCompilerError;
-import com.android.tools.r8.errors.MainDexOverflowException;
+import com.android.tools.r8.errors.MainDexOverflow;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
@@ -24,6 +23,7 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
@@ -185,15 +185,16 @@
     return transaction.getNumberOfFields();
   }
 
-  void throwIfFull(boolean hasMainDexList) throws DexOverflowException {
+  void throwIfFull(boolean hasMainDexList, Reporter reporter) {
     if (!isFull()) {
       return;
     }
-    throw new MainDexOverflowException(
-        hasMainDexList,
-        transaction.getNumberOfMethods(),
-        transaction.getNumberOfFields(),
-        MAX_ENTRIES);
+    throw reporter.fatalError(
+        new MainDexOverflow(
+            hasMainDexList,
+            transaction.getNumberOfMethods(),
+            transaction.getNumberOfFields(),
+            MAX_ENTRIES));
   }
 
   private boolean isFilledEnough(FillStrategy fillStrategy) {
@@ -226,8 +227,7 @@
       this.writer = writer;
     }
 
-    public abstract Collection<VirtualFile> run()
-        throws ExecutionException, IOException, DexOverflowException;
+    public abstract Collection<VirtualFile> run() throws ExecutionException, IOException;
   }
 
   /**
@@ -296,7 +296,7 @@
       originalNames = computeOriginalNameMapping(classes, application.getProguardMap());
     }
 
-    protected void fillForMainDexList(Set<DexProgramClass> classes) throws DexOverflowException {
+    protected void fillForMainDexList(Set<DexProgramClass> classes) {
       if (!application.mainDexList.isEmpty()) {
         VirtualFile mainDexFile = virtualFiles.get(0);
         for (DexType type : application.mainDexList) {
@@ -314,7 +314,7 @@
           }
           mainDexFile.commitTransaction();
         }
-        mainDexFile.throwIfFull(true);
+        mainDexFile.throwIfFull(true, options.reporter);
       }
     }
 
@@ -363,7 +363,7 @@
     }
 
     @Override
-    public Collection<VirtualFile> run() throws IOException, DexOverflowException {
+    public Collection<VirtualFile> run() throws IOException {
       int totalClassNumber = classes.size();
       // First fill required classes into the main dex file.
       fillForMainDexList(classes);
@@ -408,14 +408,13 @@
     }
 
     @Override
-    public Collection<VirtualFile> run()
-        throws ExecutionException, IOException, DexOverflowException {
+    public Collection<VirtualFile> run() throws ExecutionException, IOException {
       // Add all classes to the main dex file.
       for (DexProgramClass programClass : classes) {
         mainDexFile.addClass(programClass);
       }
       mainDexFile.commitTransaction();
-      mainDexFile.throwIfFull(false);
+      mainDexFile.throwIfFull(false, options.reporter);
       return virtualFiles;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/errors/DexOverflowException.java b/src/main/java/com/android/tools/r8/errors/DexOverflowException.java
deleted file mode 100644
index 818c980..0000000
--- a/src/main/java/com/android/tools/r8/errors/DexOverflowException.java
+++ /dev/null
@@ -1,14 +0,0 @@
-// 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 com.android.tools.r8.errors;
-
-/**
- * Signals when there were too many items to fit in a given dex file.
- */
-public class DexOverflowException extends CompilationError {
-
-  public DexOverflowException(String message) {
-    super(message);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/MainDexOverflow.java b/src/main/java/com/android/tools/r8/errors/MainDexOverflow.java
new file mode 100644
index 0000000..e33cf59
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/MainDexOverflow.java
@@ -0,0 +1,85 @@
+// 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 com.android.tools.r8.errors;
+
+/**
+ * Info about error when running mono dex and not all classes can fit in a dex or when running for
+ * multidex legacy and there are too many classes to fit in the main dex.
+ */
+public class MainDexOverflow {
+  private final boolean hasMainDexList;
+  private final long numOfMethods;
+  private final long numOfFields;
+  private final long maxNumOfEntries;
+
+  public MainDexOverflow(
+      boolean hasMainDexList, long numOfMethods, long numOfFields, long maxNumOfEntries) {
+    super();
+    this.hasMainDexList = hasMainDexList;
+    this.numOfMethods = numOfMethods;
+    this.numOfFields = numOfFields;
+    this.maxNumOfEntries = maxNumOfEntries;
+  }
+
+  private StringBuilder getGeneralMessage() {
+    StringBuilder messageBuilder = new StringBuilder();
+    // General message: Cannot fit.
+    messageBuilder.append("Cannot fit requested classes in ");
+    messageBuilder.append(hasMainDexList ? "the main-" : "a single ");
+    messageBuilder.append("dex file");
+
+    return messageBuilder;
+  }
+
+  private String getNumberRelatedMessage() {
+    StringBuilder messageBuilder = new StringBuilder();
+    // Show the numbers of methods and/or fields that exceed the limit.
+    if (numOfMethods > maxNumOfEntries) {
+      messageBuilder.append("# methods: ");
+      messageBuilder.append(numOfMethods);
+      messageBuilder.append(" > ").append(maxNumOfEntries);
+      if (numOfFields > maxNumOfEntries) {
+        messageBuilder.append(" ; ");
+      }
+    }
+    if (numOfFields > maxNumOfEntries) {
+      messageBuilder.append("# fields: ");
+      messageBuilder.append(numOfFields);
+      messageBuilder.append(" > ").append(maxNumOfEntries);
+    }
+
+    return messageBuilder.toString();
+  }
+
+  public String getMessage() {
+    // Default message
+    return getGeneralMessage()
+        .append(" (")
+        .append(getNumberRelatedMessage())
+        .append(")")
+        .toString();
+  }
+
+  public String getMessageForD8() {
+    StringBuilder messageBuilder = getGeneralMessage();
+    if (!hasMainDexList) {
+      messageBuilder.append(". ");
+      messageBuilder.append("Try supplying a main-dex list");
+    }
+    messageBuilder.append(".").append(System.getProperty("line.separator"));
+    messageBuilder.append(getNumberRelatedMessage());
+    return messageBuilder.toString();
+  }
+
+  public String getMessageForR8() {
+    StringBuilder messageBuilder = getGeneralMessage();
+    if (!hasMainDexList) {
+      messageBuilder.append(". ");
+      messageBuilder.append("Try supplying a main-dex list or main dex rules");
+    }
+    messageBuilder.append(".").append(System.getProperty("line.separator"));
+    messageBuilder.append(getNumberRelatedMessage());
+    return messageBuilder.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/MainDexOverflowException.java b/src/main/java/com/android/tools/r8/errors/MainDexOverflowException.java
deleted file mode 100644
index 7c3d87d..0000000
--- a/src/main/java/com/android/tools/r8/errors/MainDexOverflowException.java
+++ /dev/null
@@ -1,43 +0,0 @@
-// 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 com.android.tools.r8.errors;
-
-/**
- * Thrown when running mono dex and not all classes can fit in a dex or when running for multidex
- * legacy and there are too many classes to fit in the main dex.
- */
-public class MainDexOverflowException extends DexOverflowException {
-
-  public MainDexOverflowException(
-      boolean hasMainDexList, long numOfMethods, long numOfFields, long maxNumOfEntries) {
-    super(getMessage(hasMainDexList, numOfMethods, numOfFields, maxNumOfEntries));
-  }
-
-  private static String getMessage(
-      boolean hasMainDexList, long numOfMethods, long maxNumOfEntries, long numOfFields) {
-    StringBuilder messageBuilder = new StringBuilder();
-    // General message: Cannot fit.
-    messageBuilder.append("Cannot fit requested classes in ");
-    messageBuilder.append(hasMainDexList ? "the main-" : "a single ");
-    messageBuilder.append("dex file");
-    messageBuilder.append(" (");
-    // Show the numbers of methods and/or fields that exceed the limit.
-    if (numOfMethods > maxNumOfEntries) {
-      messageBuilder.append("# methods: ");
-      messageBuilder.append(numOfMethods);
-      messageBuilder.append(" > ").append(maxNumOfEntries);
-      if (numOfFields > maxNumOfEntries) {
-        messageBuilder.append(" ; ");
-      }
-    }
-    if (numOfFields > maxNumOfEntries) {
-      messageBuilder.append("# fields: ");
-      messageBuilder.append(numOfFields);
-      messageBuilder.append(" > ").append(maxNumOfEntries);
-    }
-    messageBuilder.append(")");
-    return messageBuilder.toString();
-  }
-
-}
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index ebb7ce6..b09f164 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -3,10 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.BaseCompilerCommand;
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8Command;
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.R8Command;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.MainDexOverflow;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
@@ -16,12 +20,18 @@
 public class Reporter implements DiagnosticsHandler {
 
   private final DiagnosticsHandler clientHandler;
+  private final BaseCompilerCommand command;
   private int errorCount = 0;
   private Diagnostic lastError;
   private final Collection<Throwable> suppressedExceptions = new ArrayList<>();
 
   public Reporter(DiagnosticsHandler clientHandler) {
+    this(clientHandler, null);
+  }
+
+  public Reporter(DiagnosticsHandler clientHandler, BaseCompilerCommand command) {
     this.clientHandler = clientHandler;
+    this.command = command;
   }
 
   @Override
@@ -75,6 +85,19 @@
   }
 
   /**
+   * @throws AbortException always.
+   */
+  public RuntimeException fatalError(MainDexOverflow e) {
+    if (command instanceof R8Command) {
+      return fatalError(new StringDiagnostic(e.getMessageForR8()));
+    } else if (command instanceof D8Command) {
+      return fatalError(new StringDiagnostic(e.getMessageForD8()));
+    } else {
+      return fatalError(new StringDiagnostic(e.getMessage()));
+    }
+  }
+
+  /**
    * @throws AbortException if any error was reported.
    */
   public void failIfPendingErrors() {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 6e8ea93..0e99a7c 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.DeviceRunner.DeviceRunnerConfigurationException;
 import com.android.tools.r8.ToolHelper.DexVm.Kind;
 import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -1353,7 +1352,7 @@
   }
 
   public static void writeApplication(DexApplication application, InternalOptions options)
-      throws ExecutionException, DexOverflowException {
+      throws ExecutionException {
     R8.writeApplication(
         Executors.newSingleThreadExecutor(),
         application,
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index 89935a7..ead9175 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.code.ConstString;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.ReturnVoid;
-import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
@@ -108,8 +107,7 @@
   }
 
   @Test
-  public void manyFilesWithSharedSynthesizedClass()
-      throws ExecutionException, IOException, DexOverflowException {
+  public void manyFilesWithSharedSynthesizedClass() throws ExecutionException, IOException {
 
     // Create classes that all reference enough strings to overflow the index, but are all
     // at different offsets in the strings array. This ensures we trigger multiple rounds of
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index 3cab10a..bda9ac5 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -10,7 +10,6 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.AndroidApp;
@@ -165,7 +164,7 @@
   }
 
   @Test
-  public void lookupFieldWithDefaultInInterface() throws DexOverflowException {
+  public void lookupFieldWithDefaultInInterface() {
     SmaliBuilder builder = new SmaliBuilder();
 
     builder.addInterface("Interface");
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index 5afb068..d4ab6c6 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -37,7 +36,7 @@
       return buildApplication(
           AndroidApp.builder().addDexProgramData(builder.compile(), Origin.unknown()).build(),
           options);
-    } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) {
+    } catch (IOException | RecognitionException | ExecutionException e) {
       throw new RuntimeException(e);
     }
   }
@@ -118,8 +117,7 @@
       return iterator;
     }
 
-    private AndroidApp writeDex(DexApplication application, InternalOptions options)
-        throws DexOverflowException {
+    private AndroidApp writeDex(DexApplication application, InternalOptions options) {
       try {
         ToolHelper.writeApplication(application, options);
         options.signalFinishedToConsumers();
@@ -129,7 +127,7 @@
       }
     }
 
-    public String run() throws DexOverflowException, IOException {
+    public String run() throws IOException {
       AppInfo appInfo = new AppInfo(application);
       IRConverter converter = new IRConverter(appInfo, options);
       converter.replaceCodeForTesting(method, code);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 99ffc11..4214a67 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -14,6 +14,8 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8Command;
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.StringResource;
@@ -22,7 +24,6 @@
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.Code;
@@ -52,9 +53,11 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AbortException;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
@@ -62,6 +65,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.MainDexList;
+import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
@@ -100,6 +104,8 @@
   public static TemporaryFolder generatedApplicationsFolder =
       ToolHelper.getTemporaryFolderForTest();
 
+  private List<Diagnostic> errors = new ArrayList<>();
+
   // Generate the test applications in a @BeforeClass method, as they are used by several tests.
   @BeforeClass
   public static void generateTestApplications() throws Throwable {
@@ -177,12 +183,15 @@
     try {
       verifyMainDexContains(TWO_LARGE_CLASSES, getTwoLargeClassesAppPath(), false);
       fail("Expect to fail, for there are too many classes for the main-dex list.");
-    } catch (DexOverflowException e) {
+    } catch (AbortException e) {
+      assertEquals(1, errors.size());
+      String message = errors.get(0).getDiagnosticMessage();
       // Make sure {@link MonoDexDistributor} was _not_ used.
-      assertFalse(e.getMessage().contains("single dex file"));
+      assertFalse(message.contains("single dex file"));
       // Make sure what exceeds the limit is the number of methods.
-      assertTrue(e.getMessage().contains("# methods: "
-          + String.valueOf(TWO_LARGE_CLASSES.size() * MAX_METHOD_COUNT)));
+      assertTrue(
+          message.contains(
+              "# methods: " + String.valueOf(TWO_LARGE_CLASSES.size() * MAX_METHOD_COUNT)));
     }
   }
 
@@ -218,12 +227,16 @@
     try {
       verifyMainDexContains(MANY_CLASSES, getManyClassesMultiDexAppPath(), false);
       fail("Expect to fail, for there are too many classes for the main-dex list.");
-    } catch (DexOverflowException e) {
+    } catch (AbortException e) {
+      assertEquals(1, errors.size());
+      String message = errors.get(0).getDiagnosticMessage();
       // Make sure {@link MonoDexDistributor} was _not_ used.
-      assertFalse(e.getMessage().contains("single dex file"));
+      assertFalse(message.contains("single dex file"));
       // Make sure what exceeds the limit is the number of methods.
-      assertTrue(e.getMessage().contains("# methods: "
-          + String.valueOf(MANY_CLASSES_COUNT * MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS)));
+      assertTrue(
+          message.contains(
+              "# methods: "
+                  + String.valueOf(MANY_CLASSES_COUNT * MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS)));
     }
   }
 
@@ -442,15 +455,22 @@
     // Notice that this one fails due to the min API.
     try {
       generateApplication(
-          MANY_CLASSES, AndroidApiLevel.K.getLevel(), false,
-          MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS);
+          MANY_CLASSES,
+          AndroidApiLevel.K.getLevel(),
+          false,
+          MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS,
+          new TestDiagnosticsHandler());
       fail("Expect to fail, for there are many classes while multidex is not enabled.");
-    } catch (DexOverflowException e) {
+    } catch (AbortException e) {
+      assertEquals(1, errors.size());
+      String message = errors.get(0).getDiagnosticMessage();
       // Make sure {@link MonoDexDistributor} was used.
-      assertTrue(e.getMessage().contains("single dex file"));
+      assertTrue(message.contains("single dex file"));
       // Make sure what exceeds the limit is the number of methods.
-      assertTrue(e.getMessage().contains("# methods: "
-          + String.valueOf(MANY_CLASSES_COUNT * MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS)));
+      assertTrue(
+          message.contains(
+              "# methods: "
+                  + String.valueOf(MANY_CLASSES_COUNT * MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS)));
     }
   }
 
@@ -506,7 +526,7 @@
     }
     Path outDir = temp.newFolder().toPath();
     R8Command.Builder builder =
-        R8Command.builder()
+        R8Command.builder(new TestDiagnosticsHandler())
             .addProgramFiles(app)
             .setMode(
                 minimalMainDex && mainDex.size() > 0
@@ -592,8 +612,20 @@
   private static AndroidApp generateApplication(
       List<String> classes, int minApi, boolean intermediate, int methodCount)
       throws IOException, ExecutionException {
+    return generateApplication(
+        classes, minApi, intermediate, methodCount, new DefaultDiagnosticsHandler());
+  }
+
+  private static AndroidApp generateApplication(
+      List<String> classes,
+      int minApi,
+      boolean intermediate,
+      int methodCount,
+      DiagnosticsHandler diagnosticsHandler)
+      throws IOException, ExecutionException {
     Timing timing = new Timing("MainDexListTests");
-    InternalOptions options = new InternalOptions();
+    InternalOptions options =
+        new InternalOptions(new DexItemFactory(), new Reporter(diagnosticsHandler));
     options.minApiLevel = minApi;
     options.intermediate = intermediate;
     DexItemFactory factory = options.itemFactory;
@@ -763,4 +795,12 @@
       throw new Unreachable();
     }
   }
+
+  private class TestDiagnosticsHandler implements DiagnosticsHandler {
+
+    @Override
+    public void error(Diagnostic error) {
+      errors.add(error);
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
index 2f6eb51..9a6e778 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.smali;
 
-import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -334,13 +333,11 @@
     return result;
   }
 
-  public byte[] compile()
-      throws IOException, RecognitionException, DexOverflowException, ExecutionException {
+  public byte[] compile() throws IOException, RecognitionException, ExecutionException {
     return Smali.compile(buildSource());
   }
 
-  public AndroidApp build()
-      throws IOException, RecognitionException, DexOverflowException, ExecutionException {
+  public AndroidApp build() throws IOException, RecognitionException, ExecutionException {
     return AndroidApp.builder().addDexProgramData(compile(), Origin.unknown()).build();
   }
 
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
index a380834..2297ebd 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
@@ -6,7 +6,6 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.SmaliWriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
@@ -27,7 +26,7 @@
       AndroidApp application =
           AndroidApp.builder().addDexProgramData(Smali.compile(smali), Origin.unknown()).build();
       assertEquals(smali, SmaliWriter.smali(application, new InternalOptions()));
-    } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) {
+    } catch (IOException | RecognitionException | ExecutionException e) {
       throw new RuntimeException(e);
     }
   }
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 1d3fd93..6c4b884 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
@@ -45,7 +44,7 @@
   protected AndroidApp buildApplication(SmaliBuilder builder) {
     try {
       return AndroidApp.builder().addDexProgramData(builder.compile(), Origin.unknown()).build();
-    } catch (IOException | RecognitionException | DexOverflowException | ExecutionException e) {
+    } catch (IOException | RecognitionException | ExecutionException e) {
       throw new RuntimeException(e);
     }
   }
@@ -56,7 +55,7 @@
           .addDexProgramData(builder.compile(), EmbeddedOrigin.INSTANCE)
           .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
           .build();
-    } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) {
+    } catch (IOException | RecognitionException | ExecutionException e) {
       throw new RuntimeException(e);
     }
   }
@@ -233,13 +232,11 @@
         processdApplication, DEFAULT_CLASS_NAME, returnType, DEFAULT_METHOD_NAME, parameters);
   }
 
-  public String runArt(AndroidApp application) throws DexOverflowException {
+  public String runArt(AndroidApp application) {
     return runArt(application, DEFAULT_MAIN_CLASS_NAME);
   }
 
-
-  public String runArt(AndroidApp application, String mainClass)
-      throws DexOverflowException {
+  public String runArt(AndroidApp application, String mainClass) {
     try {
       Path out = temp.getRoot().toPath().resolve("run-art-input.zip");
       // TODO(sgjesse): Pass in a unique temp directory for each run.
@@ -258,8 +255,7 @@
     }
   }
 
-  public void runDex2Oat(AndroidApp application)
-      throws DexOverflowException {
+  public void runDex2Oat(AndroidApp application) {
     try {
       Path dexOut = temp.getRoot().toPath().resolve("run-dex2oat-input.zip");
       Path oatFile = temp.getRoot().toPath().resolve("oat-file");
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java
index 30aa7ef..68de752 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
-import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
@@ -37,27 +36,27 @@
 public class Smali {
 
   public static byte[] compile(String smaliText)
-      throws RecognitionException, IOException, DexOverflowException, ExecutionException {
+      throws RecognitionException, IOException, ExecutionException {
     return compile(smaliText, 15);
   }
 
   public static byte[] compile(String... smaliText)
-      throws RecognitionException, IOException, DexOverflowException, ExecutionException {
+      throws RecognitionException, IOException, ExecutionException {
     return compile(Arrays.asList(smaliText), 15);
   }
 
   public static byte[] compile(String smaliText, int apiLevel)
-      throws IOException, RecognitionException, DexOverflowException, ExecutionException {
+      throws IOException, RecognitionException, ExecutionException {
     return compile(ImmutableList.of(smaliText), apiLevel);
   }
 
   public static byte[] compile(List<String> smaliTexts)
-      throws RecognitionException, IOException, DexOverflowException, ExecutionException {
+      throws RecognitionException, IOException, ExecutionException {
     return compile(smaliTexts, 15);
   }
 
   public static byte[] compile(List<String> smaliTexts, int apiLevel)
-      throws RecognitionException, IOException, ExecutionException, DexOverflowException {
+      throws RecognitionException, IOException, ExecutionException {
     DexBuilder dexBuilder = new DexBuilder(Opcodes.forApi(apiLevel));
 
     for (String smaliText : smaliTexts) {