diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/rename/RenameVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/RenameVisitor.java index 331ef82b2..1d0c5c9f0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/rename/RenameVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/RenameVisitor.java @@ -59,24 +59,43 @@ public class RenameVisitor extends AbstractVisitor { checkFields(aliasProvider, cls, args); checkMethods(aliasProvider, cls, args); } + boolean pkgUpdated = false; + for (PackageNode pkg : root.getPackages()) { + pkgUpdated |= checkPackage(args, aliasProvider, pkg); + } if (!args.isFsCaseSensitive() && args.isRenameCaseSensitive()) { + // check for package directory conflicts on case insensitive filesystems + Set pkgPaths = new HashSet<>(); + for (PackageNode pkg : root.getPackages()) { + String pkgPath = pkg.getAliasPkgInfo().getFullName().toLowerCase(); + if (!pkgPaths.add(pkgPath)) { + pkg.setLeafAlias(aliasProvider.forPackage(pkg), false); + pkgUpdated = true; + // verify the new name also doesn't conflict + if (!pkgPaths.add(pkg.getAliasPkgInfo().getFullName().toLowerCase())) { + pkg.setLeafAlias(aliasProvider.forPackage(pkg), false); + } + } + } + } + if (pkgUpdated) { + root.runPackagesUpdate(); + } + if (!args.isFsCaseSensitive() && args.isRenameCaseSensitive()) { + // check for class file conflicts on case insensitive filesystems (run after package rename) Set clsFullPaths = new HashSet<>(classes.size()); for (ClassNode cls : classes) { ClassInfo clsInfo = cls.getClassInfo(); if (!clsFullPaths.add(clsInfo.getAliasFullPath().toLowerCase())) { clsInfo.changeShortName(aliasProvider.forClass(cls)); cls.addAttr(new RenameReasonAttr(cls).append("case insensitive filesystem")); - clsFullPaths.add(clsInfo.getAliasFullPath().toLowerCase()); + // verify the new name also doesn't conflict + if (!clsFullPaths.add(clsInfo.getAliasFullPath().toLowerCase())) { + clsInfo.changeShortName(aliasProvider.forClass(cls)); + } } } } - boolean pkgUpdated = false; - for (PackageNode pkg : root.getPackages()) { - pkgUpdated |= checkPackage(args, aliasProvider, pkg); - } - if (pkgUpdated) { - root.runPackagesUpdate(); - } processRootPackages(aliasProvider, root, classes); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/names/TestCaseSensitiveClassInPkgChecks.java b/jadx-core/src/test/java/jadx/tests/integration/names/TestCaseSensitiveClassInPkgChecks.java new file mode 100644 index 000000000..21f210b4c --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/names/TestCaseSensitiveClassInPkgChecks.java @@ -0,0 +1,45 @@ +package jadx.tests.integration.names; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestCaseSensitiveClassInPkgChecks extends SmaliTest { + /* + * com.example.User and com.example.user - class names differ only by case in the same package. + * On case-insensitive FS both would map to the same file path, requiring class rename. + */ + + @Test + public void testClassConflictOnCaseInsensitiveFS() { + args.setFsCaseSensitive(false); + + List classes = loadFromSmaliFiles(); + assertThat(classes).hasSize(2); + + long distinct = classes.stream() + .map(cls -> cls.getClassInfo().getAliasFullPath().toLowerCase()) + .distinct() + .count(); + assertThat(distinct).isEqualTo(2L); + } + + @Test + public void testClassConflictOnCaseSensitiveFS() { + args.setFsCaseSensitive(true); + + List classes = loadFromSmaliFiles(); + assertThat(classes).hasSize(2); + + long distinct = classes.stream() + .map(cls -> cls.getClassInfo().getAliasFullPath()) + .distinct() + .count(); + assertThat(distinct).isEqualTo(2L); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/names/TestCaseSensitivePkgChecks.java b/jadx-core/src/test/java/jadx/tests/integration/names/TestCaseSensitivePkgChecks.java new file mode 100644 index 000000000..3a3d9f8fa --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/names/TestCaseSensitivePkgChecks.java @@ -0,0 +1,48 @@ +package jadx.tests.integration.names; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestCaseSensitivePkgChecks extends SmaliTest { + /* + * com.Example.Foo and com.example.Foo - same class name in packages that differ only by case. + * On case-insensitive FS both would map to the same path (com/example/foo), requiring package + * rename. + */ + + @Test + public void testPkgConflictOnCaseInsensitiveFS() { + args.setFsCaseSensitive(false); + + List classes = loadFromSmaliFiles(); + assertThat(classes).hasSize(2); + + // all package paths must be distinct when lowercased (no two classes share same dir) + long distinctPkgPaths = classes.stream() + .map(cls -> cls.getClassInfo().getAliasFullPath().toLowerCase()) + .distinct() + .count(); + assertThat(distinctPkgPaths).isEqualTo(2L); + } + + @Test + public void testPkgConflictOnCaseSensitiveFS() { + args.setFsCaseSensitive(true); + + List classes = loadFromSmaliFiles(); + assertThat(classes).hasSize(2); + + // on case-sensitive FS, original package names should be preserved + long distinctPkgPaths = classes.stream() + .map(cls -> cls.getClassInfo().getAliasFullPath()) + .distinct() + .count(); + assertThat(distinctPkgPaths).isEqualTo(2L); + } +} diff --git a/jadx-core/src/test/smali/names/TestCaseSensitiveClassInPkgChecks/1.smali b/jadx-core/src/test/smali/names/TestCaseSensitiveClassInPkgChecks/1.smali new file mode 100644 index 000000000..023c80b33 --- /dev/null +++ b/jadx-core/src/test/smali/names/TestCaseSensitiveClassInPkgChecks/1.smali @@ -0,0 +1,2 @@ +.class public Lcom/example/User; +.super Ljava/lang/Object; diff --git a/jadx-core/src/test/smali/names/TestCaseSensitiveClassInPkgChecks/2.smali b/jadx-core/src/test/smali/names/TestCaseSensitiveClassInPkgChecks/2.smali new file mode 100644 index 000000000..c9f911118 --- /dev/null +++ b/jadx-core/src/test/smali/names/TestCaseSensitiveClassInPkgChecks/2.smali @@ -0,0 +1,2 @@ +.class public Lcom/example/user; +.super Ljava/lang/Object; diff --git a/jadx-core/src/test/smali/names/TestCaseSensitivePkgChecks/1.smali b/jadx-core/src/test/smali/names/TestCaseSensitivePkgChecks/1.smali new file mode 100644 index 000000000..39123c494 --- /dev/null +++ b/jadx-core/src/test/smali/names/TestCaseSensitivePkgChecks/1.smali @@ -0,0 +1,2 @@ +.class public Lcom/Example/Foo; +.super Ljava/lang/Object; diff --git a/jadx-core/src/test/smali/names/TestCaseSensitivePkgChecks/2.smali b/jadx-core/src/test/smali/names/TestCaseSensitivePkgChecks/2.smali new file mode 100644 index 000000000..c88498e7f --- /dev/null +++ b/jadx-core/src/test/smali/names/TestCaseSensitivePkgChecks/2.smali @@ -0,0 +1,2 @@ +.class public Lcom/example/Foo; +.super Ljava/lang/Object;