Check dex index overflow when running mono dex

Mono dex is enforced when the 3 following conditions are met:
- min API is =< 20.
- no main dex list was provided.
- no main dex rules was provided.

Bug: 62482021
Change-Id: If17bba6cd2b6b8517e1eb85c937283eddb49585c
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 7dd2d33..dd30160 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -207,7 +207,6 @@
       RootSet rootSet;
       byte[] proguardSeedsData = null;
       timing.begin("Strip unused code");
-      Set<DexType> mainDexBaseClasses = null;
       try {
         Set<DexType> missingClasses = appInfo.getMissingClasses();
         missingClasses = filterMissingClasses(missingClasses, options.dontWarnPatterns);
@@ -281,7 +280,7 @@
         // Lets find classes which may have code executed before secondary dex files installation.
         RootSet mainDexRootSet =
             new RootSetBuilder(application, appInfo, options.mainDexKeepRules).run(executorService);
-        mainDexBaseClasses = enqueuer.traceMainDex(mainDexRootSet, timing);
+        Set<DexType> mainDexBaseClasses = enqueuer.traceMainDex(mainDexRootSet, timing);
 
         // Calculate the automatic main dex list according to legacy multidex constraints.
         // Add those classes to an eventual manual list of classes.
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 7476792..3638c01 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.dex.VirtualFile.FilePerClassDistributor;
 import com.android.tools.r8.dex.VirtualFile.FillFilesDistributor;
 import com.android.tools.r8.dex.VirtualFile.PackageMapDistributor;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -124,6 +125,16 @@
         assert packageDistribution == null :
             "Cannot combine package distribution definition with file-per-class option.";
         distributor = new FilePerClassDistributor(this);
+      } else if (options.minApiLevel < Constants.ANDROID_L_API
+            && options.mainDexKeepRules.isEmpty()
+            && application.mainDexList.isEmpty()) {
+        if (packageDistribution != null) {
+          throw new CompilationError("Cannot apply package distribution. Multidex is not"
+              + " supported with API level " + options.minApiLevel +"."
+              + " For API level < " + Constants.ANDROID_L_API + ", main dex classes list or"
+              + " rules must be specified.");
+        }
+        distributor = new VirtualFile.MonoDexDistributor(this);
       } else if (packageDistribution != null) {
         distributor = new PackageMapDistributor(this, packageDistribution, executorService);
       } else {
diff --git a/src/main/java/com/android/tools/r8/dex/Constants.java b/src/main/java/com/android/tools/r8/dex/Constants.java
index 201fc82..3ea63bb 100644
--- a/src/main/java/com/android/tools/r8/dex/Constants.java
+++ b/src/main/java/com/android/tools/r8/dex/Constants.java
@@ -10,6 +10,7 @@
 
   public static final int ANDROID_O_API = 26;
   public static final int ANDROID_N_API = 24;
+  public static final int ANDROID_L_API = 21;
   public static final int ANDROID_K_API = 19;
   public static final int DEFAULT_ANDROID_API = 1;
 
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 3db0502..95fc27a 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -309,6 +309,27 @@
     }
   }
 
+  public static class MonoDexDistributor extends DistributorBase {
+    public MonoDexDistributor(ApplicationWriter writer) {
+      super(writer);
+    }
+
+    @Override
+    public Map<Integer, VirtualFile> run() throws ExecutionException, IOException {
+      VirtualFile mainDexFile = new VirtualFile(0, writer.namingLens);
+      nameToFileMap.put(0, mainDexFile);
+
+      for (DexProgramClass programClass : classes) {
+        mainDexFile.addClass(programClass);
+        if (mainDexFile.isFull()) {
+          throw new CompilationError("Cannot fit all classes in a single dex file.");
+        }
+      }
+      mainDexFile.commitTransaction();
+      return nameToFileMap;
+    }
+  }
+
   public static class PackageMapDistributor extends DistributorBase {
     private final PackageDistribution packageDistribution;
     private final ExecutorService executorService;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index baaf7d0..c3f2d08 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -166,7 +166,7 @@
 
   private boolean isInMainDexList(DexType iface) {
     ImmutableSet<DexType> list = converter.application.mainDexList;
-    return list != null && list.contains(iface);
+    return list.contains(iface);
   }
 
   // Represent a static interface method as a method of companion class.
diff --git a/src/test/java/com/android/tools/r8/dex/ExtraFileTest.java b/src/test/java/com/android/tools/r8/dex/ExtraFileTest.java
index 376577c..9fcf8c2 100644
--- a/src/test/java/com/android/tools/r8/dex/ExtraFileTest.java
+++ b/src/test/java/com/android/tools/r8/dex/ExtraFileTest.java
@@ -47,6 +47,7 @@
         R8Command.builder()
             .addProgramFiles(original)
             .setOutputPath(out)
+            .setMinApiLevel(Constants.ANDROID_N_API) // Allow native multidex.
             .setProguardMapFile(proguardMap)
             .setPackageDistributionFile(packageMap)
             .build();
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 f540bc0..fe9caab 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -111,7 +111,8 @@
     if (!verifyApplications && !regenerateApplications) {
       return;
     }
-    AndroidApp generated = generateApplication(TWO_LARGE_CLASSES, MAX_METHOD_COUNT);
+    AndroidApp generated =
+        generateApplication(TWO_LARGE_CLASSES, Constants.ANDROID_N_API, MAX_METHOD_COUNT);
     if (regenerateApplications) {
       generated.write(getTwoLargeClassesAppPath(), OutputMode.Indexed, true);
     } else {
@@ -126,7 +127,7 @@
     if (!verifyApplications && !regenerateApplications) {
       return;
     }
-    AndroidApp generated = generateApplication(MANY_CLASSES, 1);
+    AndroidApp generated = generateApplication(MANY_CLASSES, Constants.DEFAULT_ANDROID_API, 1);
     if (regenerateApplications) {
       generated.write(getManyClassesAppPath(), OutputMode.Indexed, true);
     } else {
@@ -368,10 +369,11 @@
     }
   }
 
-  private static AndroidApp generateApplication(List<String> classes, int methodCount)
+  private static AndroidApp generateApplication(List<String> classes, int minApi, int methodCount)
       throws IOException, ExecutionException {
     Timing timing = new Timing("MainDexListTests");
     InternalOptions options = new InternalOptions();
+    options.minApiLevel = minApi;
     DexItemFactory factory = options.itemFactory;
     DexApplication.Builder builder = new DexApplication.Builder(factory, timing);
     for (String clazz : classes) {
diff --git a/src/test/java/com/android/tools/r8/maindexlist/two-large-classes.zip b/src/test/java/com/android/tools/r8/maindexlist/two-large-classes.zip
index 9ad745f..ef4af5b 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/two-large-classes.zip
+++ b/src/test/java/com/android/tools/r8/maindexlist/two-large-classes.zip
Binary files differ