diff --git a/src/tools/java/com/google/devtools/build/android/dexer/ZipEntryComparator.java b/src/tools/java/com/google/devtools/build/android/dexer/ZipEntryComparator.java index ab0b83899..db95db4ab 100644 --- a/src/tools/java/com/google/devtools/build/android/dexer/ZipEntryComparator.java +++ b/src/tools/java/com/google/devtools/build/android/dexer/ZipEntryComparator.java @@ -44,6 +44,9 @@ public int compare(ZipEntry a, ZipEntry b) { // Copied from com.android.dx.cf.direct.ClassPathOpener @VisibleForTesting static int compareClassNames(String a, String b) { + String originalA = a; + String originalB = b; + // Ensure inner classes sort second a = a.replace('$', '0'); b = b.replace('$', '0'); @@ -55,6 +58,13 @@ static int compareClassNames(String a, String b) { a = a.replace("package-info", ""); b = b.replace("package-info", ""); - return a.compareTo(b); + int normalizedResult = a.compareTo(b); + if (normalizedResult != 0) { + return normalizedResult; + } + + // Normalization is lossy. Keep distinct raw names distinct when this comparator is used as a + // TreeMap key comparator, otherwise entries such as Foo$2$1$1 and Foo$2$101 collapse. + return originalA.compareTo(originalB); } } diff --git a/src/tools/javatests/com/google/devtools/build/android/dexer/DexFileSplitterTest.java b/src/tools/javatests/com/google/devtools/build/android/dexer/DexFileSplitterTest.java index 1551df2de..5784e876e 100644 --- a/src/tools/javatests/com/google/devtools/build/android/dexer/DexFileSplitterTest.java +++ b/src/tools/javatests/com/google/devtools/build/android/dexer/DexFileSplitterTest.java @@ -29,6 +29,7 @@ import java.nio.file.Path; import java.util.HashSet; import java.util.Set; +import java.util.TreeMap; import java.util.concurrent.ExecutionException; import java.util.zip.CRC32; import java.util.zip.ZipEntry; @@ -123,6 +124,19 @@ public void testDuplicateInputIgnored() throws Exception { assertThat(dexEntries(outputArchives.get(0))).containsExactlyElementsIn(expectedFiles); } + @Test + public void testClassNameComparatorPreservesNormalizationCollisions() { + TreeMap classNames = + new TreeMap<>(ZipEntryComparator::compareClassNames); + + classNames.put("Foo$2$1$1.class.dex", true); + classNames.put("Foo$2$101.class.dex", true); + + assertThat(classNames.keySet()) + .containsExactly("Foo$2$1$1.class.dex", "Foo$2$101.class.dex") + .inOrder(); + } + @Test public void testSingleInputMultidexOutput() throws Exception { ImmutableList outputArchives =