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 ec1a9d57039..9e8706dd5d1 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 @@ -14,6 +14,9 @@ import jadx.core.codegen.json.JsonMappingGen; import jadx.core.deobf.Deobfuscator; import jadx.core.deobf.NameMapper; +import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.nodes.MethodOverrideAttr; import jadx.core.dex.attributes.nodes.RenameReasonAttr; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.FieldInfo; @@ -195,7 +198,7 @@ private static void checkMethods(Deobfuscator deobfuscator, ClassNode cls, JadxA Set names = new HashSet<>(methods.size()); for (MethodNode mth : methods) { String signature = mth.getMethodInfo().makeSignature(true, false); - if (!names.add(signature)) { + if (!names.add(signature) && canRename(mth)) { deobfuscator.forceRenameMethod(mth); mth.addAttr(new RenameReasonAttr("collision with other method in class")); } @@ -203,6 +206,23 @@ private static void checkMethods(Deobfuscator deobfuscator, ClassNode cls, JadxA } } + private static boolean canRename(MethodNode mth) { + if (mth.contains(AFlag.DONT_RENAME)) { + return false; + } + MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE); + if (overrideAttr != null) { + for (MethodNode relatedMth : overrideAttr.getRelatedMthNodes()) { + if (relatedMth != mth && mth.getParentClass().equals(relatedMth.getParentClass())) { + // ignore rename if exists related method from same class (bridge method in most cases) + // such rename will also rename current method and will not help to resolve name collision + return false; + } + } + } + return true; + } + private static void processRootPackages(Deobfuscator deobfuscator, RootNode root, List classes) { Set rootPkgs = collectRootPkgs(classes); root.getCacheStorage().setRootPkgs(rootPkgs); diff --git a/jadx-core/src/test/java/jadx/tests/integration/inline/TestSyntheticBridgeRename.java b/jadx-core/src/test/java/jadx/tests/integration/inline/TestSyntheticBridgeRename.java new file mode 100644 index 00000000000..bcdfee58af9 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inline/TestSyntheticBridgeRename.java @@ -0,0 +1,47 @@ +package jadx.tests.integration.inline; + +import org.assertj.core.api.Condition; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; +import jadx.tests.api.extensions.profiles.TestProfile; +import jadx.tests.api.extensions.profiles.TestWithProfiles; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestSyntheticBridgeRename extends IntegrationTest { + + @SuppressWarnings("InnerClassMayBeStatic") + public static class TestCls { + private abstract class Inner { + public abstract V get(String value); + } + + public class IntInner extends Inner { + public Integer get(String value) { + return value.length(); + } + } + + public void test() { + IntInner inner = new IntInner(); + call(inner.get("a")); + } + + private static void call(Integer value) { + } + } + + @TestWithProfiles({ TestProfile.DX_J8, TestProfile.JAVA8 }) + public void test() { + ClassNode cls = getClassNode(TestCls.class); + assertThat(searchCls(cls.getInnerClasses(), "IntInner").getMethods()) + .as("check that bridge method was generated by compiler") + .haveAtLeastOne(new Condition<>(mth -> mth.getAccessFlags().isBridge(), "bridge")); + + assertThat(cls) + .code() + .doesNotContain("mo0get") + .containsOne("call(inner.get(\"a\"));"); + } +}