From 140e6c0c407250c09d6fc931c70a0360c1b1c3e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=9A=E6=8C=AF?= Date: Fri, 21 Aug 2026 17:36:10 +0800 Subject: [PATCH] Fix DexFileSplitter class name collisions Fall back to the original class names when lossy dx-style normalization produces an equal result, so TreeMap does not drop distinct entries. --- .../build/android/dexer/ZipEntryComparator.java | 12 +++++++++++- .../build/android/dexer/DexFileSplitterTest.java | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) 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 =