Refactor container DEX tests
Change-Id: I5ec83e034cabd304525252653a52f47ac5ba6ac0
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 81d709c9..efc954e 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,61 +3,21 @@
// 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.CHECKSUM_OFFSET;
-import static com.android.tools.r8.dex.Constants.CONTAINER_OFF_OFFSET;
-import static com.android.tools.r8.dex.Constants.CONTAINER_SIZE_OFFSET;
-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.DEX_MAGIC_SIZE;
-import static com.android.tools.r8.dex.Constants.FILE_SIZE_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;
-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.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.ByteDataView;
-import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
-import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.dex.CompatByteBuffer;
-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;
-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;
-import it.unimi.dsi.fastutil.ints.IntArrayList;
-import it.unimi.dsi.fastutil.ints.IntList;
-import java.io.IOException;
-import java.nio.ByteOrder;
import java.nio.file.Path;
-import java.security.MessageDigest;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.zip.Adler32;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
@RunWith(Parameterized.class)
-public class DexContainerFormatBasicTest extends TestBase {
+public class DexContainerFormatBasicTest extends DexContainerFormatTestBase {
@Parameter() public TestParameters parameters;
@@ -176,239 +136,4 @@
assertArrayEquals(unzipContent(outputBoth).get(0), unzipContent(outputMerged).get(0));
}
-
- private void validateDex(Path output, int expectedDexes, DexVersion expectedVersion)
- throws Exception {
- List<byte[]> dexes = unzipContent(output);
- assertEquals(expectedDexes, dexes.size());
- for (byte[] dex : dexes) {
- validate(dex, expectedVersion);
- }
- }
-
- private void validateSingleContainerDex(Path output) throws Exception {
- List<byte[]> dexes = unzipContent(output);
- assertEquals(1, dexes.size());
- validate(dexes.get(0), DexVersion.V41);
- }
-
- private void validate(byte[] dex, DexVersion expectedVersion) throws Exception {
- CompatByteBuffer buffer = CompatByteBuffer.wrap(dex);
- setByteOrder(buffer);
-
- IntList sections = new IntArrayList();
- int offset = 0;
- while (offset < buffer.capacity()) {
- assertTrue(BitUtils.isAligned(4, offset));
- sections.add(offset);
- 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);
- if (expectedVersion.isContainerDex()) {
- assertEquals(0, dataSize);
- assertEquals(0, dataOffset);
- } else {
- assertEquals(file_size, dataOffset + dataSize);
- }
- offset += expectedVersion.isContainerDex() ? file_size : dataOffset + dataSize;
- assertEquals(file_size, offset - ListUtils.last(sections));
- }
- assertEquals(buffer.capacity(), offset);
-
- for (Integer sectionOffset : sections) {
- validateHeader(sections, buffer, sectionOffset, expectedVersion);
- validateMap(buffer, sectionOffset);
- validateSignature(buffer, sectionOffset);
- validateChecksum(buffer, sectionOffset);
- }
- }
-
- private byte[] magicBytes(DexVersion version) {
- byte[] magic = new byte[DEX_MAGIC_SIZE];
- System.arraycopy(
- Constants.DEX_FILE_MAGIC_PREFIX, 0, magic, 0, Constants.DEX_FILE_MAGIC_PREFIX.length);
- System.arraycopy(
- version.getBytes(),
- 0,
- magic,
- Constants.DEX_FILE_MAGIC_PREFIX.length,
- version.getBytes().length);
- magic[Constants.DEX_FILE_MAGIC_PREFIX.length + version.getBytes().length] =
- Constants.DEX_FILE_MAGIC_SUFFIX;
- assertEquals(
- DEX_MAGIC_SIZE, Constants.DEX_FILE_MAGIC_PREFIX.length + version.getBytes().length + 1);
- return magic;
- }
-
- private void validateHeader(
- IntList sections, CompatByteBuffer buffer, int offset, DexVersion expectedVersion) {
- 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);
-
- byte[] magic = new byte[DEX_MAGIC_SIZE];
- 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(buffer.capacity(), buffer.getInt(offset + CONTAINER_SIZE_OFFSET));
- assertEquals(offset, buffer.getInt(offset + CONTAINER_OFF_OFFSET));
- }
- assertEquals(stringIdsSize, getSizeFromMap(TYPE_STRING_ID_ITEM, buffer, offset));
- assertEquals(stringIdsOffset, getOffsetFromMap(TYPE_STRING_ID_ITEM, buffer, offset));
- }
-
- private void validateMap(CompatByteBuffer buffer, int offset) {
- int mapOffset = buffer.getInt(offset + MAP_OFF_OFFSET);
- buffer.position(mapOffset);
- int mapSize = buffer.getInt();
- int previousOffset = Integer.MAX_VALUE;
- for (int i = 0; i < mapSize; i++) {
- buffer.getShort(); // Skip section type.
- buffer.getShort(); // Skip unused.
- buffer.getInt(); // Skip section size.
- int o = buffer.getInt();
- if (i > 0) {
- assertTrue("" + i + ": " + o + " " + previousOffset, o > previousOffset);
- }
- previousOffset = o;
- }
- }
-
- private void validateSignature(CompatByteBuffer buffer, int offset) throws Exception {
- int sectionSize = buffer.getInt(offset + FILE_SIZE_OFFSET);
- MessageDigest md = MessageDigest.getInstance("SHA-1");
- md.update(
- buffer.asByteBuffer().array(), offset + FILE_SIZE_OFFSET, sectionSize - FILE_SIZE_OFFSET);
- byte[] expectedSignature = new byte[20];
- md.digest(expectedSignature, 0, 20);
- for (int i = 0; i < expectedSignature.length; i++) {
- assertEquals(expectedSignature[i], buffer.get(offset + SIGNATURE_OFFSET + i));
- }
- }
-
- private void validateChecksum(CompatByteBuffer buffer, int offset) {
- int sectionSize = buffer.getInt(offset + FILE_SIZE_OFFSET);
- Adler32 adler = new Adler32();
- adler.update(
- buffer.asByteBuffer().array(), offset + SIGNATURE_OFFSET, sectionSize - SIGNATURE_OFFSET);
- assertEquals((int) adler.getValue(), buffer.getInt(offset + CHECKSUM_OFFSET));
- }
-
- private int getSizeFromMap(int type, CompatByteBuffer 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, CompatByteBuffer 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(CompatByteBuffer 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 {
- List<byte[]> result = new ArrayList<>();
- ZipUtils.iter(zip, (entry, inputStream) -> result.add(ByteStreams.toByteArray(inputStream)));
- return result;
- }
-
- private static void generateApplication(Path applicationJar, String rootPackage, int methodCount)
- throws Throwable {
- ImmutableList.Builder<String> builder = ImmutableList.builder();
- for (int i = 0; i < 10000; ++i) {
- String pkg = rootPackage + "." + (i % 2 == 0 ? "a" : "b");
- String className = "Class" + i;
- builder.add(pkg + "." + className);
- }
- List<String> classes = builder.build();
-
- generateApplication(applicationJar, classes, methodCount);
- }
-
- private static void generateApplication(Path output, List<String> classes, int methodCount)
- throws IOException {
- ArchiveConsumer consumer = new ArchiveConsumer(output);
- for (String typename : classes) {
- String descriptor = DescriptorUtils.javaTypeToDescriptor(typename);
- byte[] bytes =
- transformer(MainDexListTests.ClassStub.class)
- .setClassDescriptor(descriptor)
- .addClassTransformer(
- new ClassTransformer() {
- @Override
- public MethodVisitor visitMethod(
- int access,
- String name,
- String descriptor,
- String signature,
- String[] exceptions) {
- // This strips <init>() too.
- if (name.equals("methodStub")) {
- for (int i = 0; i < methodCount; i++) {
- MethodVisitor mv =
- super.visitMethod(
- access, "method" + i, descriptor, signature, exceptions);
- mv.visitCode();
- mv.visitInsn(Opcodes.RETURN);
- mv.visitMaxs(0, 0);
- mv.visitEnd();
- }
- }
- return null;
- }
- })
- .transform();
- consumer.accept(ByteDataView.of(bytes), descriptor, null);
- }
- consumer.finished(null);
- }
-
- // Simple stub/template for generating the input classes.
- public static class ClassStub {
- public static void methodStub() {}
- }
}
diff --git a/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatEmptyTest.java b/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatEmptyTest.java
index b2e6aec..ec8e01a 100644
--- a/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatEmptyTest.java
+++ b/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatEmptyTest.java
@@ -5,16 +5,10 @@
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.ZipUtils;
-import com.google.common.io.ByteStreams;
-import java.io.IOException;
import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -22,7 +16,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class DexContainerFormatEmptyTest extends TestBase {
+public class DexContainerFormatEmptyTest extends DexContainerFormatTestBase {
@Parameter() public TestParameters parameters;
@@ -59,10 +53,4 @@
.writeToZip();
assertEquals(0, unzipContent(outputFromDexing).size());
}
-
- private List<byte[]> unzipContent(Path zip) throws IOException {
- List<byte[]> result = new ArrayList<>();
- ZipUtils.iter(zip, (entry, inputStream) -> result.add(ByteStreams.toByteArray(inputStream)));
- return result;
- }
}
diff --git a/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatTestBase.java b/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatTestBase.java
new file mode 100644
index 0000000..7938844
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatTestBase.java
@@ -0,0 +1,286 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// 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.CHECKSUM_OFFSET;
+import static com.android.tools.r8.dex.Constants.CONTAINER_OFF_OFFSET;
+import static com.android.tools.r8.dex.Constants.CONTAINER_SIZE_OFFSET;
+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.DEX_MAGIC_SIZE;
+import static com.android.tools.r8.dex.Constants.FILE_SIZE_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;
+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.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.dex.CompatByteBuffer;
+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.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;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.Adler32;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class DexContainerFormatTestBase extends TestBase {
+
+ static void validateDex(Path output, int expectedDexes, DexVersion expectedVersion)
+ throws Exception {
+ List<byte[]> dexes = unzipContent(output);
+ assertEquals(expectedDexes, dexes.size());
+ for (byte[] dex : dexes) {
+ validate(dex, expectedVersion);
+ }
+ }
+
+ static void validateSingleContainerDex(Path output) throws Exception {
+ List<byte[]> dexes = unzipContent(output);
+ assertEquals(1, dexes.size());
+ validate(dexes.get(0), DexVersion.V41);
+ }
+
+ static void validate(byte[] dex, DexVersion expectedVersion) throws Exception {
+ CompatByteBuffer buffer = CompatByteBuffer.wrap(dex);
+ setByteOrder(buffer);
+
+ IntList sections = new IntArrayList();
+ int offset = 0;
+ while (offset < buffer.capacity()) {
+ assertTrue(BitUtils.isAligned(4, offset));
+ sections.add(offset);
+ 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);
+ if (expectedVersion.isContainerDex()) {
+ assertEquals(0, dataSize);
+ assertEquals(0, dataOffset);
+ } else {
+ assertEquals(file_size, dataOffset + dataSize);
+ }
+ offset += expectedVersion.isContainerDex() ? file_size : dataOffset + dataSize;
+ assertEquals(file_size, offset - ListUtils.last(sections));
+ }
+ assertEquals(buffer.capacity(), offset);
+
+ for (Integer sectionOffset : sections) {
+ validateHeader(sections, buffer, sectionOffset, expectedVersion);
+ validateMap(buffer, sectionOffset);
+ validateSignature(buffer, sectionOffset);
+ validateChecksum(buffer, sectionOffset);
+ }
+ }
+
+ static byte[] magicBytes(DexVersion version) {
+ byte[] magic = new byte[DEX_MAGIC_SIZE];
+ System.arraycopy(
+ Constants.DEX_FILE_MAGIC_PREFIX, 0, magic, 0, Constants.DEX_FILE_MAGIC_PREFIX.length);
+ System.arraycopy(
+ version.getBytes(),
+ 0,
+ magic,
+ Constants.DEX_FILE_MAGIC_PREFIX.length,
+ version.getBytes().length);
+ magic[Constants.DEX_FILE_MAGIC_PREFIX.length + version.getBytes().length] =
+ Constants.DEX_FILE_MAGIC_SUFFIX;
+ assertEquals(
+ DEX_MAGIC_SIZE, Constants.DEX_FILE_MAGIC_PREFIX.length + version.getBytes().length + 1);
+ return magic;
+ }
+
+ static void validateHeader(
+ IntList sections, CompatByteBuffer buffer, int offset, DexVersion expectedVersion) {
+ 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);
+
+ byte[] magic = new byte[DEX_MAGIC_SIZE];
+ 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(buffer.capacity(), buffer.getInt(offset + CONTAINER_SIZE_OFFSET));
+ assertEquals(offset, buffer.getInt(offset + CONTAINER_OFF_OFFSET));
+ }
+ assertEquals(stringIdsSize, getSizeFromMap(TYPE_STRING_ID_ITEM, buffer, offset));
+ assertEquals(stringIdsOffset, getOffsetFromMap(TYPE_STRING_ID_ITEM, buffer, offset));
+ }
+
+ static void validateMap(CompatByteBuffer buffer, int offset) {
+ int mapOffset = buffer.getInt(offset + MAP_OFF_OFFSET);
+ buffer.position(mapOffset);
+ int mapSize = buffer.getInt();
+ int previousOffset = Integer.MAX_VALUE;
+ for (int i = 0; i < mapSize; i++) {
+ buffer.getShort(); // Skip section type.
+ buffer.getShort(); // Skip unused.
+ buffer.getInt(); // Skip section size.
+ int o = buffer.getInt();
+ if (i > 0) {
+ assertTrue("" + i + ": " + o + " " + previousOffset, o > previousOffset);
+ }
+ previousOffset = o;
+ }
+ }
+
+ static void validateSignature(CompatByteBuffer buffer, int offset) throws Exception {
+ int sectionSize = buffer.getInt(offset + FILE_SIZE_OFFSET);
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ md.update(
+ buffer.asByteBuffer().array(), offset + FILE_SIZE_OFFSET, sectionSize - FILE_SIZE_OFFSET);
+ byte[] expectedSignature = new byte[20];
+ md.digest(expectedSignature, 0, 20);
+ for (int i = 0; i < expectedSignature.length; i++) {
+ assertEquals(expectedSignature[i], buffer.get(offset + SIGNATURE_OFFSET + i));
+ }
+ }
+
+ static void validateChecksum(CompatByteBuffer buffer, int offset) {
+ int sectionSize = buffer.getInt(offset + FILE_SIZE_OFFSET);
+ Adler32 adler = new Adler32();
+ adler.update(
+ buffer.asByteBuffer().array(), offset + SIGNATURE_OFFSET, sectionSize - SIGNATURE_OFFSET);
+ assertEquals((int) adler.getValue(), buffer.getInt(offset + CHECKSUM_OFFSET));
+ }
+
+ static int getSizeFromMap(int type, CompatByteBuffer 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");
+ }
+
+ static int getOffsetFromMap(int type, CompatByteBuffer 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");
+ }
+
+ static void setByteOrder(CompatByteBuffer 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.");
+ }
+ }
+ }
+
+ static List<byte[]> unzipContent(Path zip) throws IOException {
+ List<byte[]> result = new ArrayList<>();
+ ZipUtils.iter(zip, (entry, inputStream) -> result.add(ByteStreams.toByteArray(inputStream)));
+ return result;
+ }
+
+ static void generateApplication(Path applicationJar, String rootPackage, int methodCount)
+ throws Throwable {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ for (int i = 0; i < 10000; ++i) {
+ String pkg = rootPackage + "." + (i % 2 == 0 ? "a" : "b");
+ String className = "Class" + i;
+ builder.add(pkg + "." + className);
+ }
+ List<String> classes = builder.build();
+
+ generateApplication(applicationJar, classes, methodCount);
+ }
+
+ private static void generateApplication(Path output, List<String> classes, int methodCount)
+ throws IOException {
+ ArchiveConsumer consumer = new ArchiveConsumer(output);
+ for (String typename : classes) {
+ String descriptor = DescriptorUtils.javaTypeToDescriptor(typename);
+ byte[] bytes =
+ transformer(MainDexListTests.ClassStub.class)
+ .setClassDescriptor(descriptor)
+ .addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access,
+ String name,
+ String descriptor,
+ String signature,
+ String[] exceptions) {
+ // This strips <init>() too.
+ if (name.equals("methodStub")) {
+ for (int i = 0; i < methodCount; i++) {
+ MethodVisitor mv =
+ super.visitMethod(
+ access, "method" + i, descriptor, signature, exceptions);
+ mv.visitCode();
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+ }
+ return null;
+ }
+ })
+ .transform();
+ consumer.accept(ByteDataView.of(bytes), descriptor, null);
+ }
+ consumer.finished(null);
+ }
+
+ // Simple stub/template for generating the input classes.
+ public static class ClassStub {
+ public static void methodStub() {}
+ }
+}