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 {