diff --git a/jadx-core/src/main/java/jadx/api/metadata/annotations/NodeDeclareRef.java b/jadx-core/src/main/java/jadx/api/metadata/annotations/NodeDeclareRef.java index c956e70a454719e2a3153d07de789f2d1bc1bf49..8b237e0d80ac316652f20bc2ac22dda1ec6af5ad 100644 --- a/jadx-core/src/main/java/jadx/api/metadata/annotations/NodeDeclareRef.java +++ b/jadx-core/src/main/java/jadx/api/metadata/annotations/NodeDeclareRef.java @@ -32,6 +32,22 @@ public class NodeDeclareRef implements ICodeAnnotation { return AnnType.DECLARATION; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof NodeDeclareRef)) { + return false; + } + return node.equals(((NodeDeclareRef) o).node); + } + + @Override + public int hashCode() { + return node.hashCode(); + } + @Override public String toString() { return "NodeDeclareRef{" + node + '}'; diff --git a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java index 3266d76e99886e68c9eb8396e85fe99dba193729..4b6810c6a2eb551e427e83a63a2e365b7988f28d 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -26,6 +26,7 @@ import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.JadxError; import jadx.core.dex.attributes.nodes.JumpInfo; import jadx.core.dex.attributes.nodes.MethodOverrideAttr; +import jadx.core.dex.attributes.nodes.MethodReplaceAttr; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.instructions.ConstStringNode; import jadx.core.dex.instructions.IfNode; @@ -144,8 +145,9 @@ public class MethodGen { } else { classGen.useType(code, mth.getReturnType()); code.add(' '); - code.attachDefinition(mth); - code.add(mth.getAlias()); + MethodNode defMth = getMethodForDefinition(); + code.attachDefinition(defMth); + code.add(defMth.getAlias()); } code.add('('); @@ -178,6 +180,14 @@ public class MethodGen { return true; } + private MethodNode getMethodForDefinition() { + MethodReplaceAttr replaceAttr = mth.get(AType.METHOD_REPLACE); + if (replaceAttr != null) { + return replaceAttr.getReplaceMth(); + } + return mth; + } + private void addOverrideAnnotation(ICodeWriter code, MethodNode mth) { MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE); if (overrideAttr == null) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java index 79666f16e7e47190149323084dc1693bdf3cea57..88bc06da264190f9ffe3c988def9c633ee99cd68 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java @@ -10,6 +10,7 @@ import java.util.Objects; import jadx.api.plugins.input.data.AccessFlags; import jadx.core.Consts; import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.MethodReplaceAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; @@ -281,6 +282,9 @@ public class ClassModifier extends AbstractVisitor { if (!Objects.equals(wrappedMth.getAlias(), alias)) { wrappedMth.getMethodInfo().setAlias(alias); } + wrappedMth.addAttr(new MethodReplaceAttr(mth)); + wrappedMth.copyAttributeFrom(mth, AType.METHOD_OVERRIDE); + wrappedMth.addDebugComment("Method merged with bridge method"); return true; } diff --git a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxClassNodeAssertions.java b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxClassNodeAssertions.java index b273a8f7faf253568a06d448838bb2f3ee3869be..6c5670da4d7d5991823c1175cf9fc19df5e98f08 100644 --- a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxClassNodeAssertions.java +++ b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxClassNodeAssertions.java @@ -8,7 +8,6 @@ import org.assertj.core.api.Assertions; import jadx.api.ICodeInfo; import jadx.api.metadata.ICodeAnnotation; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.ICodeNode; import jadx.tests.api.IntegrationTest; import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; @@ -58,11 +57,12 @@ public class JadxClassNodeAssertions extends AbstractObjectAssert entry : code.getCodeMetadata().getAsMap().entrySet()) { if (entry.getKey() == refPos) { Assertions.assertThat(entry.getValue()).isEqualTo(node); - return; + return this; } } fail("Annotation for reference string: '%s' at position %d not found", refStr, refPos); + return this; } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenericsMthOverride.java b/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenericsMthOverride.java index 75d85d531bf35210f5ab24e57a4f61d9a9bbf3b8..47b2c579725eb2ba9ee20db89e0c933a76014fd9 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenericsMthOverride.java +++ b/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenericsMthOverride.java @@ -56,8 +56,6 @@ public class TestGenericsMthOverride extends IntegrationTest { assertThat(code, containsOne("public Y method(Exception x) {")); assertThat(code, containsOne("public Object method(Object x) {")); - assertThat(code, countString(3, "@Override")); - // TODO: @Override missing for class C - // assertThat(code, countString(4, "@Override")); + assertThat(code, countString(4, "@Override")); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/inline/TestOverrideBridgeMerge.java b/jadx-core/src/test/java/jadx/tests/integration/inline/TestOverrideBridgeMerge.java new file mode 100644 index 0000000000000000000000000000000000000000..fd429062dbcc0fb0be6f43e9746ba4684b04e7c3 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inline/TestOverrideBridgeMerge.java @@ -0,0 +1,45 @@ +package jadx.tests.integration.inline; + +import java.util.function.Function; + +import org.junit.jupiter.api.Test; + +import jadx.api.metadata.ICodeAnnotation; +import jadx.api.metadata.annotations.NodeDeclareRef; +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestOverrideBridgeMerge extends SmaliTest { + + public static class TestCls implements Function { + @Override + public /* bridge */ /* synthetic */ Integer apply(String str) { + return test(str); + } + + public Integer test(String str) { + return str.length(); + } + } + + @Test + public void test() { + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("Integer test(String str) {"); // not inlined + } + + @Test + public void testSmali() { + ClassNode cls = getClassNodeFromSmali(); + ICodeAnnotation mthDef = new NodeDeclareRef(getMethod(cls, "apply")); + assertThat(cls) + .checkCodeAnnotationFor("apply(String str) {", mthDef) + .code() + .containsOne("@Override") + .containsOne("public Integer apply(String str) {") + .doesNotContain("test(String str)"); + } +} diff --git a/jadx-core/src/test/smali/inline/TestOverrideBridgeMerge.smali b/jadx-core/src/test/smali/inline/TestOverrideBridgeMerge.smali new file mode 100644 index 0000000000000000000000000000000000000000..689c46a1ea25348d4b3fe36f5394c685772d27f6 --- /dev/null +++ b/jadx-core/src/test/smali/inline/TestOverrideBridgeMerge.smali @@ -0,0 +1,39 @@ +.class public Linline/TestOverrideBridgeMerge; +.super Ljava/lang/Object; + +.implements Ljava/util/function/Function; + +.annotation system Ldalvik/annotation/Signature; + value = { + "Ljava/lang/Object;", + "Ljava/util/function/Function", + "<", + "Ljava/lang/String;", + "Ljava/lang/Integer;", + ">;" + } +.end annotation + +.method public constructor ()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;->()V + return-void +.end method + +.method public bridge synthetic apply(Ljava/lang/Object;)Ljava/lang/Object; + .registers 3 + check-cast p1, Ljava/lang/String; + invoke-virtual {p0, p1}, Linline/TestOverrideBridgeMerge;->test(Ljava/lang/String;)Ljava/lang/Integer; + move-result-object v0 + return-object v0 +.end method + +.method public test(Ljava/lang/String;)Ljava/lang/Integer; + .registers 3 + .param p1, "str" # Ljava/lang/String; + invoke-virtual {p1}, Ljava/lang/String;->length()I + move-result v0 + invoke-static {v0}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer; + move-result-object v0 + return-object v0 +.end method