Update DEX container format header

  * Add the additional header offset field to the V41 header
  * Set data offset and length to 0 for V41

Bug: b/249922554
Change-Id: I22a801005ca9a7784f2628cc72eda0344acebcb6
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
index 68f379f..6087898 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
+import static com.android.tools.r8.utils.DexVersion.Layout.CONTAINER_DEX;
+
 import com.android.tools.r8.ByteBufferProvider;
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.DexFilePerClassFileConsumer;
@@ -274,6 +276,6 @@
     // Collect the non-fixed sections.
     timing.time("collect", fileWriter::collect);
     // Generate and write the bytes.
-    return timing.time("generate", () -> fileWriter.generate(offset));
+    return timing.time("generate", () -> fileWriter.generate(offset, CONTAINER_DEX));
   }
 }
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 7cf3ca9..912f4f1 100644
--- a/src/main/java/com/android/tools/r8/dex/Constants.java
+++ b/src/main/java/com/android/tools/r8/dex/Constants.java
@@ -45,12 +45,14 @@
   public static final int CLASS_DEFS_OFF_OFFSET = CLASS_DEFS_SIZE_OFFSET + 4;
   public static final int DATA_SIZE_OFFSET = CLASS_DEFS_OFF_OFFSET + 4;
   public static final int DATA_OFF_OFFSET = DATA_SIZE_OFFSET + 4;
+  public static final int HEADER_OFF_OFFSET = DATA_OFF_OFFSET + 4;
 
   public static final int ENDIAN_CONSTANT = 0x12345678;
   public static final int REVERSE_ENDIAN_CONSTANT = 0x78563412;
 
   public static final int TYPE_HEADER_ITEM = 0x0;
   public static final int TYPE_HEADER_ITEM_SIZE = 0x70;
+  public static final int TYPE_HEADER_ITEM_SIZE_V41 = 0x74;
   public static final int TYPE_STRING_ID_ITEM = 0x0001;
   public static final int TYPE_STRING_ID_ITEM_SIZE = 0x04;
   public static final int TYPE_TYPE_ID_ITEM = 0x0002;
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 86182c2..f36a045 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
+import static com.android.tools.r8.utils.DexVersion.Layout.SINGLE_DEX;
 import static com.android.tools.r8.utils.LebUtils.sizeAsUleb128;
 
 import com.android.tools.r8.ByteBufferProvider;
@@ -206,18 +207,18 @@
   }
 
   public ByteBufferResult generate() {
-    DexContainerSection res = generate(0);
+    DexContainerSection res = generate(0, SINGLE_DEX);
     return new ByteBufferResult(res.getBuffer().stealByteBuffer(), res.getLayout().getEndOfFile());
   }
 
-  public DexContainerSection generate(int offset) {
+  public DexContainerSection generate(int offset, DexVersion.Layout layoutType) {
     // Check restrictions on interface methods.
     checkInterfaceMethods();
 
     // Check restriction on the names of fields, methods and classes
     assert verifyNames();
 
-    Layout layout = Layout.from(mapping, offset, includeStringData);
+    Layout layout = Layout.from(mapping, offset, layoutType, includeStringData);
     layout.setCodesOffset(layout.dataSectionOffset);
 
     // Sort the codes first, as their order might impact size due to alignment constraints.
@@ -298,7 +299,7 @@
     layout.setEndOfFile(dest.position());
 
     // Now that we have all mixedSectionOffsets, lets write the indexed items.
-    dest.moveTo(layout.headerOffset + Constants.TYPE_HEADER_ITEM_SIZE);
+    dest.moveTo(layout.headerOffset + layout.getHeaderSize());
     if (includeStringData) {
       writeFixedSectionItems(mapping.getStrings(), layout.stringIdsOffset, this::writeStringItem);
     } else {
@@ -816,7 +817,7 @@
     // Leave out checksum and signature for now.
     dest.moveTo(layout.headerOffset + Constants.FILE_SIZE_OFFSET);
     dest.putInt(layout.getEndOfFile() - layout.headerOffset);
-    dest.putInt(Constants.TYPE_HEADER_ITEM_SIZE);
+    dest.putInt(layout.getHeaderSize());
     dest.putInt(Constants.ENDIAN_CONSTANT);
     dest.putInt(0);
     dest.putInt(0);
@@ -839,8 +840,14 @@
     int numberOfClasses = mapping.getClasses().length;
     dest.putInt(numberOfClasses);
     dest.putInt(numberOfClasses == 0 ? 0 : layout.classDefsOffset);
-    dest.putInt(layout.getDataSectionSize());
-    dest.putInt(layout.dataSectionOffset);
+    if (layout.isContainerSection()) {
+      dest.putInt(0);
+      dest.putInt(0);
+      dest.putInt(layout.headerOffset);
+    } else {
+      dest.putInt(layout.getDataSectionSize());
+      dest.putInt(layout.dataSectionOffset);
+    }
     assert dest.position() == layout.stringIdsOffset;
   }
 
@@ -937,6 +944,7 @@
     final int callSiteIdsOffset;
     final int methodHandleIdsOffset;
     final int dataSectionOffset;
+    final DexVersion.Layout layoutType;
 
     // Mixed size sections
     private int codesOffset = NOT_SET; // aligned
@@ -963,7 +971,8 @@
         int classDefsOffset,
         int callSiteIdsOffset,
         int methodHandleIdsOffset,
-        int dataSectionOffset) {
+        int dataSectionOffset,
+        DexVersion.Layout layoutType) {
       this.headerOffset = headerOffset;
       this.stringIdsOffset = stringIdsOffset;
       this.typeIdsOffset = typeIdsOffset;
@@ -974,6 +983,7 @@
       this.callSiteIdsOffset = callSiteIdsOffset;
       this.methodHandleIdsOffset = methodHandleIdsOffset;
       this.dataSectionOffset = dataSectionOffset;
+      this.layoutType = layoutType;
       assert stringIdsOffset <= typeIdsOffset;
       assert typeIdsOffset <= protoIdsOffset;
       assert protoIdsOffset <= fieldIdsOffset;
@@ -985,13 +995,18 @@
     }
 
     static Layout from(ObjectToOffsetMapping mapping) {
-      return from(mapping, 0, true);
+      return from(mapping, 0, SINGLE_DEX, true);
     }
 
-    static Layout from(ObjectToOffsetMapping mapping, int offset, boolean includeStringData) {
+    static Layout from(
+        ObjectToOffsetMapping mapping,
+        int offset,
+        DexVersion.Layout layoutType,
+        boolean includeStringData) {
+      assert offset == 0 || layoutType.isContainer();
       return new Layout(
           offset,
-          offset += Constants.TYPE_HEADER_ITEM_SIZE,
+          offset += layoutType.getHeaderSize(),
           offset +=
               includeStringData
                   ? mapping.getStrings().size() * Constants.TYPE_STRING_ID_ITEM_SIZE
@@ -1002,7 +1017,8 @@
           offset += mapping.getMethods().size() * Constants.TYPE_METHOD_ID_ITEM_SIZE,
           offset += mapping.getClasses().length * Constants.TYPE_CLASS_DEF_ITEM_SIZE,
           offset += mapping.getCallSites().size() * Constants.TYPE_CALL_SITE_ID_ITEM_SIZE,
-          offset += mapping.getMethodHandles().size() * Constants.TYPE_METHOD_HANDLE_ITEM_SIZE);
+          offset += mapping.getMethodHandles().size() * Constants.TYPE_METHOD_HANDLE_ITEM_SIZE,
+          layoutType);
     }
 
     int getDataSectionSize() {
@@ -1127,6 +1143,14 @@
       this.mapOffset = mapOffset;
     }
 
+    public boolean isContainerSection() {
+      return layoutType.isContainer();
+    }
+
+    public int getHeaderSize() {
+      return layoutType.getHeaderSize();
+    }
+
     public List<MapItem> generateMapInfo(FileWriter fileWriter) {
       return generateMapInfo(
           fileWriter,
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 e1b4588..cbfd8fc 100644
--- a/src/main/java/com/android/tools/r8/utils/DexVersion.java
+++ b/src/main/java/com/android/tools/r8/utils/DexVersion.java
@@ -3,26 +3,49 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.utils.structural.Ordered;
 import java.util.Optional;
 
 /** Android dex version */
 public enum DexVersion implements Ordered<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'}),
-  V40(40, new byte[] {'0', '4', '0'}),
-  V41(41, new byte[] {'0', '4', '1'});
+  V35(35, new byte[] {'0', '3', '5'}, Layout.SINGLE_DEX),
+  V37(37, new byte[] {'0', '3', '7'}, Layout.SINGLE_DEX),
+  V38(38, new byte[] {'0', '3', '8'}, Layout.SINGLE_DEX),
+  V39(39, new byte[] {'0', '3', '9'}, Layout.SINGLE_DEX),
+  V40(40, new byte[] {'0', '4', '0'}, Layout.SINGLE_DEX),
+  V41(41, new byte[] {'0', '4', '1'}, Layout.CONTAINER_DEX);
+
+  public enum Layout {
+    SINGLE_DEX,
+    CONTAINER_DEX;
+
+    public boolean isContainer() {
+      return this == CONTAINER_DEX;
+    }
+
+    public int getHeaderSize() {
+      return isContainer() ? Constants.TYPE_HEADER_ITEM_SIZE_V41 : Constants.TYPE_HEADER_ITEM_SIZE;
+    }
+  }
 
   private final int dexVersion;
-
   private final byte[] dexVersionBytes;
+  private final Layout layout;
 
-  DexVersion(int dexVersion, byte[] dexVersionBytes) {
+  DexVersion(int dexVersion, byte[] dexVersionBytes, Layout layout) {
     this.dexVersion = dexVersion;
     this.dexVersionBytes = dexVersionBytes;
+    this.layout = layout;
+  }
+
+  public Layout getLayout() {
+    return layout;
+  }
+
+  public boolean isContainerDex() {
+    return getLayout() == Layout.CONTAINER_DEX;
   }
 
   public int getIntValue() {
diff --git a/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatBasicTest.java b/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatBasicTest.java
index 41aa6d0..28a01e0 100644
--- a/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatBasicTest.java
+++ b/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatBasicTest.java
@@ -8,6 +8,8 @@
 import static com.android.tools.r8.dex.Constants.DATA_SIZE_OFFSET;
 import static com.android.tools.r8.dex.Constants.DEX_MAGIC_SIZE;
 import static com.android.tools.r8.dex.Constants.FILE_SIZE_OFFSET;
+import static com.android.tools.r8.dex.Constants.HEADER_OFF_OFFSET;
+import static com.android.tools.r8.dex.Constants.HEADER_SIZE_OFFSET;
 import static com.android.tools.r8.dex.Constants.MAP_OFF_OFFSET;
 import static com.android.tools.r8.dex.Constants.SIGNATURE_OFFSET;
 import static com.android.tools.r8.dex.Constants.STRING_IDS_OFF_OFFSET;
@@ -31,7 +33,6 @@
 import com.android.tools.r8.utils.BitUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DexVersion;
-import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
@@ -167,8 +168,10 @@
       int dataSize = buffer.getInt(offset + DATA_SIZE_OFFSET);
       int dataOffset = buffer.getInt(offset + DATA_OFF_OFFSET);
       int file_size = buffer.getInt(offset + FILE_SIZE_OFFSET);
-      offset = dataOffset + dataSize;
-      assertEquals(file_size, offset - ListUtils.last(sections));
+      if (!expectedVersion.isContainerDex()) {
+        assertEquals(file_size, dataOffset + dataSize);
+      }
+      offset += expectedVersion.isContainerDex() ? file_size : dataOffset + dataSize;
     }
     assertEquals(buffer.capacity(), offset);
 
@@ -207,8 +210,19 @@
     buffer.get(magic);
     assertArrayEquals(magicBytes(expectedVersion), magic);
 
+    assertEquals(
+        expectedVersion.isContainerDex()
+            ? Constants.TYPE_HEADER_ITEM_SIZE_V41
+            : Constants.TYPE_HEADER_ITEM_SIZE,
+        buffer.getInt(offset + HEADER_SIZE_OFFSET));
     assertEquals(stringIdsSize, buffer.getInt(offset + STRING_IDS_SIZE_OFFSET));
     assertEquals(stringIdsOffset, buffer.getInt(offset + STRING_IDS_OFF_OFFSET));
+    if (expectedVersion.isContainerDex()) {
+      assertEquals(0, buffer.getInt(offset + DATA_SIZE_OFFSET));
+      assertEquals(0, buffer.getInt(offset + DATA_OFF_OFFSET));
+      // Additional header field from V41.
+      assertEquals(offset, buffer.getInt(offset + HEADER_OFF_OFFSET));
+    }
     assertEquals(stringIdsSize, getSizeFromMap(TYPE_STRING_ID_ITEM, buffer, offset));
     assertEquals(stringIdsOffset, getOffsetFromMap(TYPE_STRING_ID_ITEM, buffer, offset));
   }