Update string_ids size and offset in all DEX sections

The writing of the container format was missing updating the string_ids
information in all the container sections but the last.

This change updates both the header and the map.

Bug: b/249922554
Change-Id: I527368b6a5fc9e0db97473a51f793cf985d649ee
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 021a904..8c90b56 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.Timing.TimingMerger;
@@ -145,6 +146,8 @@
       fileTiming.end();
       timings.add(fileTiming);
     }
+    updateStringIdsSizeAndOffset(dexOutputBuffer, sections);
+
     merger.add(timings);
     merger.end();
 
@@ -162,6 +165,39 @@
     }
   }
 
+  private void updateStringIdsSizeAndOffset(
+      DexOutputBuffer dexOutputBuffer, List<DexContainerSection> sections) {
+    // The last section has the shared string_ids table. Now it is written the final size and
+    // offset is known and the remaining sections can be updated to point to the shared table.
+    // This updates both the size and offset in the header and in the map.
+    DexContainerSection lastSection = ListUtils.last(sections);
+    int stringIdsSize = lastSection.getFileWriter().getMixedSectionOffsets().getStringData().size();
+    int stringIdsOffset = lastSection.getLayout().stringIdsOffset;
+    for (DexContainerSection section : sections) {
+      if (section != lastSection) {
+        dexOutputBuffer.moveTo(section.getLayout().headerOffset + Constants.STRING_IDS_SIZE_OFFSET);
+        dexOutputBuffer.putInt(stringIdsSize);
+        dexOutputBuffer.putInt(stringIdsOffset);
+        dexOutputBuffer.moveTo(section.getLayout().getMapOffset());
+        // Skip size.
+        dexOutputBuffer.getInt();
+        while (dexOutputBuffer.position() < section.getLayout().getEndOfFile()) {
+          int sectionType = dexOutputBuffer.getShort();
+          dexOutputBuffer.getShort(); // Skip unused.
+          if (sectionType == Constants.TYPE_STRING_ID_ITEM) {
+            dexOutputBuffer.putInt(stringIdsSize);
+            dexOutputBuffer.putInt(stringIdsOffset);
+            break;
+          } else {
+            // Skip size and offset for this type.
+            dexOutputBuffer.getInt();
+            dexOutputBuffer.getInt();
+          }
+        }
+      }
+    }
+  }
+
   private DexContainerSection writeVirtualFileSection(
       VirtualFile virtualFile,
       Timing timing,
diff --git a/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java b/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
index e552456..d9cabaa 100644
--- a/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
+++ b/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
@@ -126,6 +126,14 @@
     byteBuffer.putInt(anInteger);
   }
 
+  public short getShort() {
+    return byteBuffer.getShort();
+  }
+
+  public int getInt() {
+    return byteBuffer.getInt();
+  }
+
   public boolean assertZero() {
     int pos = byteBuffer.position();
     int i = byteBuffer.getInt();
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 af1ee96..73fa4bf 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
@@ -3,6 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex.container;
 
+import static com.android.tools.r8.dex.Constants.DATA_OFF_OFFSET;
+import static com.android.tools.r8.dex.Constants.DATA_SIZE_OFFSET;
+import static com.android.tools.r8.dex.Constants.MAP_OFF_OFFSET;
+import static com.android.tools.r8.dex.Constants.STRING_IDS_OFF_OFFSET;
+import static com.android.tools.r8.dex.Constants.STRING_IDS_SIZE_OFFSET;
+import static com.android.tools.r8.dex.Constants.TYPE_STRING_ID_ITEM;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.ByteDataView;
@@ -10,6 +16,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.maindexlist.MainDexListTests;
 import com.android.tools.r8.transformers.ClassTransformer;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -17,7 +25,11 @@
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
@@ -91,8 +103,7 @@
                 options -> options.getTestingOptions().dexContainerExperiment = true)
             .compile()
             .writeToZip();
-    List<byte[]> dexFromDexing = unzipContent(outputFromDexing);
-    assertEquals(1, dexFromDexing.size());
+    validateSingleContainerDex(outputFromDexing);
   }
 
   @Test
@@ -105,7 +116,7 @@
                 options -> options.getTestingOptions().dexContainerExperiment = true)
             .compile()
             .writeToZip();
-    assertEquals(1, unzipContent(outputA).size());
+    validateSingleContainerDex(outputA);
 
     Path outputB =
         testForD8(Backend.DEX)
@@ -115,7 +126,84 @@
                 options -> options.getTestingOptions().dexContainerExperiment = true)
             .compile()
             .writeToZip();
-    assertEquals(1, unzipContent(outputB).size());
+    validateSingleContainerDex(outputB);
+  }
+
+  private void validateSingleContainerDex(Path output) throws IOException {
+    List<byte[]> dexes = unzipContent(output);
+    assertEquals(1, dexes.size());
+    validateStringIdsSizeAndOffsets(dexes.get(0));
+  }
+
+  private void validateStringIdsSizeAndOffsets(byte[] dex) {
+    ByteBuffer buffer = ByteBuffer.wrap(dex);
+    setByteOrder(buffer);
+
+    IntList sections = new IntArrayList();
+    int offset = 0;
+    while (offset < buffer.capacity()) {
+      sections.add(offset);
+      int dataSize = buffer.getInt(offset + DATA_SIZE_OFFSET);
+      int dataOffset = buffer.getInt(offset + DATA_OFF_OFFSET);
+      offset = dataOffset + dataSize;
+    }
+    assertEquals(buffer.capacity(), offset);
+
+    int lastOffset = sections.getInt(sections.size() - 1);
+    int stringIdsSize = buffer.getInt(lastOffset + STRING_IDS_SIZE_OFFSET);
+    int stringIdsOffset = buffer.getInt(lastOffset + STRING_IDS_OFF_OFFSET);
+
+    for (Integer sectionOffset : sections) {
+      assertEquals(stringIdsSize, buffer.getInt(sectionOffset + STRING_IDS_SIZE_OFFSET));
+      assertEquals(stringIdsOffset, buffer.getInt(sectionOffset + STRING_IDS_OFF_OFFSET));
+      assertEquals(stringIdsSize, getSizeFromMap(TYPE_STRING_ID_ITEM, buffer, sectionOffset));
+      assertEquals(stringIdsOffset, getOffsetFromMap(TYPE_STRING_ID_ITEM, buffer, sectionOffset));
+    }
+  }
+
+  private int getSizeFromMap(int type, ByteBuffer buffer, int offset) {
+    int mapOffset = buffer.getInt(offset + MAP_OFF_OFFSET);
+    buffer.position(mapOffset);
+    int mapSize = buffer.getInt();
+    for (int i = 0; i < mapSize; i++) {
+      int sectionType = buffer.getShort();
+      buffer.getShort(); // Skip unused.
+      int sectionSize = buffer.getInt();
+      buffer.getInt(); // Skip offset.
+      if (type == sectionType) {
+        return sectionSize;
+      }
+    }
+    throw new RuntimeException("Not found");
+  }
+
+  private int getOffsetFromMap(int type, ByteBuffer buffer, int offset) {
+    int mapOffset = buffer.getInt(offset + MAP_OFF_OFFSET);
+    buffer.position(mapOffset);
+    int mapSize = buffer.getInt();
+    for (int i = 0; i < mapSize; i++) {
+      int sectionType = buffer.getShort();
+      buffer.getShort(); // Skip unused.
+      buffer.getInt(); // SKip size.
+      int sectionOffset = buffer.getInt();
+      if (type == sectionType) {
+        return sectionOffset;
+      }
+    }
+    throw new RuntimeException("Not found");
+  }
+
+  private void setByteOrder(ByteBuffer buffer) {
+    // Make sure we set the right endian for reading.
+    buffer.order(ByteOrder.LITTLE_ENDIAN);
+    int endian = buffer.getInt(Constants.ENDIAN_TAG_OFFSET);
+    if (endian == Constants.REVERSE_ENDIAN_CONSTANT) {
+      buffer.order(ByteOrder.BIG_ENDIAN);
+    } else {
+      if (endian != Constants.ENDIAN_CONSTANT) {
+        throw new CompilationError("Unable to determine endianess for reading dex file.");
+      }
+    }
   }
 
   private List<byte[]> unzipContent(Path zip) throws IOException {