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: I21d317aa155cca76f80ea76a12a9ca82f3aa62e9
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index fbcd34c..2efe014 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 d7fb9f8..1636c56 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) {
         assert !options.minimalMainDex :
             "Cannot combine package distribution definition with minimal-main-dex option.";
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 9527a97..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,8 +10,8 @@
 
   public static final int ANDROID_O_API = 26;
   public static final int ANDROID_N_API = 24;
-  public static final int ANDROID_K_API = 19;
   public static final int ANDROID_L_API = 21;
+  public static final int ANDROID_K_API = 19;
   public static final int DEFAULT_ANDROID_API = 1;
 
   /** dex file version number for Android O (API level 26) */
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 55511fd..0e89d4a 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -325,6 +325,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..d1e46f3 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_L_API) // Allow native multidex.
             .setProguardMapFile(proguardMap)
             .setPackageDistributionFile(packageMap)
             .build();
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index 2841779..d5e66b8 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ArtErrorParser;
@@ -70,6 +71,7 @@
       outputApp = ToolHelper.runR8(builder.build(),
                   options -> {
                     options.printSeeds = false;
+                    options.minApiLevel = Constants.ANDROID_L_API;
                   });
     } else {
       assert compiler == CompilerUnderTest.D8;
@@ -78,6 +80,7 @@
               D8Command.builder()
                   .addProgramFiles(ListUtils.map(inputs, Paths::get))
                   .setMode(mode)
+                  .setMinApiLevel(Constants.ANDROID_L_API)
                   .build());
     }
     Path out = temp.getRoot().toPath().resolve("all.zip");
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
index 755fcab..74f9ef9 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.Resource;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.conversion.CallGraph;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
@@ -43,6 +44,7 @@
           options.testing.irOrdering = this::shuffle;
           // Only use one thread to process to process in the order decided by the callback.
           options.numberOfThreads = 1;
+          options.minApiLevel = Constants.ANDROID_L_API;
         });
   }
 
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreFixedPointTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreFixedPointTest.java
index b6d3dac..376ccf3 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreFixedPointTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreFixedPointTest.java
@@ -7,6 +7,7 @@
 
 import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
 import java.io.IOException;
@@ -21,14 +22,18 @@
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
     // First compilation.
     AndroidApp app = AndroidApp.fromProgramDirectory(Paths.get(GMSCORE_V7_DIR));
-    AndroidApp app1 = ToolHelper.runR8(app);
+    AndroidApp app1 =
+        ToolHelper.runR8(app, options -> options.minApiLevel = Constants.ANDROID_L_API);
 
     // Second compilation.
     // Add option --skip-outline-opt for second compilation. The second compilation can find
     // additional outlining opportunities as member rebinding from the first compilation can move
     // methods.
     // See b/33410508 and b/33475705.
-    AndroidApp app2 = ToolHelper.runR8(app1, options -> options.outline.enabled = false);
+    AndroidApp app2 = ToolHelper.runR8(app1, options -> {
+      options.outline.enabled = false;
+      options.minApiLevel = Constants.ANDROID_L_API;
+    });
 
     // TODO: Require that the results of the two compilations are the same.
     assertEquals(
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 c49b66d..a0a77d1 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -396,6 +396,7 @@
       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) {