Add DEX version V40 as experimental

Bug: b/249922554
Change-Id: I6f418812ef43c79922f801ef975a7e28839c83c3
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index a6f6a52..0a679e8 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -784,13 +784,19 @@
     dest.forward(size * Constants.TYPE_MAP_LIST_ITEM_SIZE);
   }
 
+  private byte[] dexVersionBytes() {
+    if (options.testing.dexContainerExperiment) {
+      return DexVersion.V40.getBytes();
+    }
+    return options.testing.forceDexVersionBytes != null
+        ? options.testing.forceDexVersionBytes
+        : DexVersion.getDexVersion(options.getMinApiLevel()).getBytes();
+  }
+
   private void writeHeader(Layout layout) {
     dest.moveTo(0);
     dest.putBytes(Constants.DEX_FILE_MAGIC_PREFIX);
-    dest.putBytes(
-        options.testing.forceDexVersionBytes != null
-            ? options.testing.forceDexVersionBytes
-            : DexVersion.getDexVersion(options.getMinApiLevel()).getBytes());
+    dest.putBytes(dexVersionBytes());
     dest.putByte(Constants.DEX_FILE_MAGIC_SUFFIX);
     // Leave out checksum and signature for now.
     dest.moveTo(Constants.FILE_SIZE_OFFSET);
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index cd7460b..69c9714 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -95,6 +95,8 @@
         return AndroidApiLevel.O;
       case V39:
         return AndroidApiLevel.P;
+      case V40:
+        return AndroidApiLevel.ANDROID_PLATFORM;
       default:
         throw new Unreachable();
     }
diff --git a/src/main/java/com/android/tools/r8/utils/DexVersion.java b/src/main/java/com/android/tools/r8/utils/DexVersion.java
index 492c87b..cdac93d 100644
--- a/src/main/java/com/android/tools/r8/utils/DexVersion.java
+++ b/src/main/java/com/android/tools/r8/utils/DexVersion.java
@@ -10,10 +10,11 @@
  * Android dex version
  */
 public enum DexVersion {
-  V35(35, new byte[]{'0', '3', '5'}),
-  V37(37, new byte[]{'0', '3', '7'}),
-  V38(38, new byte[]{'0', '3', '8'}),
-  V39(39, new byte[]{'0', '3', '9'});
+  V35(35, new byte[] {'0', '3', '5'}),
+  V37(37, new byte[] {'0', '3', '7'}),
+  V38(38, new byte[] {'0', '3', '8'}),
+  V39(39, new byte[] {'0', '3', '9'}),
+  V40(40, new byte[] {'0', '4', '0'});
 
   private final int dexVersion;
 
@@ -94,15 +95,23 @@
         return Optional.of(V38);
       case 39:
         return Optional.of(V39);
+      case 40:
+        return Optional.of(V40);
       default:
         return Optional.empty();
     }
   }
 
   public static Optional<DexVersion> getDexVersion(char b0, char b1, char b2) {
-    if (b0 != '0' || b1 != '3' || b2 < '5' || '9' < b2) {
+    if (b0 != '0') {
       return Optional.empty();
     }
-    return getDexVersion(100 * (b0 - '0') + 10 * (b1 - '0') + (b2 - '0'));
+    for (DexVersion candidate : DexVersion.values()) {
+      assert candidate.getBytes()[0] == '0';
+      if (candidate.getBytes()[2] == b2 && candidate.getBytes()[1] == b1) {
+        return Optional.of(candidate);
+      }
+    }
+    return Optional.empty();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 6bf0a8f..ebd5a13 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -153,6 +153,7 @@
 
   public static final int SUPPORTED_DEX_VERSION =
       AndroidApiLevel.LATEST.getDexVersion().getIntValue();
+  public static final int EXPERIMENTAL_DEX_VERSION = DexVersion.V40.getIntValue();
 
   public static final int ASM_VERSION = Opcodes.ASM9;
 
@@ -1879,6 +1880,8 @@
     private boolean hasReadCheckDeterminism = false;
     private DeterminismChecker determinismChecker = null;
     public boolean usePcEncodingInCfForTesting = false;
+    public boolean dexContainerExperiment =
+        System.getProperty("com.android.tools.r8.dexContainerExperiment") != null;
 
     // Testing options to analyse locality of items in DEX files when they are generated.
     public boolean calculateItemUseCountInDex = false;
diff --git a/src/test/java/com/android/tools/r8/FailCompilationOnFutureVersionsTest.java b/src/test/java/com/android/tools/r8/FailCompilationOnFutureVersionsTest.java
index f7be6ca..b6f5848 100644
--- a/src/test/java/com/android/tools/r8/FailCompilationOnFutureVersionsTest.java
+++ b/src/test/java/com/android/tools/r8/FailCompilationOnFutureVersionsTest.java
@@ -22,7 +22,7 @@
 public class FailCompilationOnFutureVersionsTest extends TestBase {
 
   static final int UNSUPPORTED_CF_VERSION = InternalOptions.SUPPORTED_CF_VERSION.major() + 1;
-  static final int UNSUPPORTED_DEX_VERSION = InternalOptions.SUPPORTED_DEX_VERSION + 1;
+  static final int UNSUPPORTED_DEX_VERSION = InternalOptions.EXPERIMENTAL_DEX_VERSION + 1;
 
   private final TestParameters parameters;
 
@@ -68,6 +68,40 @@
   }
 
   @Test
+  public void testExperimentatDexVersion() throws CompilationFailedException, IOException {
+    // Generate a DEX file with a version higher than the supported one.
+    Path out =
+        testForD8()
+            .addProgramClasses(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(
+                options ->
+                    options.testing.forceDexVersionBytes =
+                        ("0" + InternalOptions.EXPERIMENTAL_DEX_VERSION).getBytes())
+            .compile()
+            .writeToZip();
+    try {
+      testForD8()
+          .addProgramFiles(out)
+          .setMinApi(parameters.getApiLevel())
+          .compileWithExpectedDiagnostics(
+              diagnotics -> {
+                diagnotics.assertOnlyErrors();
+                diagnotics.assertErrorsCount(1);
+                assertThat(
+                    diagnotics.getErrors().get(0).getDiagnosticMessage(),
+                    containsString(
+                        "Dex file with version '"
+                            + InternalOptions.EXPERIMENTAL_DEX_VERSION
+                            + "' cannot be used with min sdk level "));
+              });
+    } catch (CompilationFailedException e) {
+      return;
+    }
+    fail("Expected compilation error");
+  }
+
+  @Test
   public void testCf() {
     try {
       testForD8()