From 87e0e5bf169fa5b9875784d57b93af5d2dc4f21e Mon Sep 17 00:00:00 2001 From: Skylot Date: Wed, 20 Jul 2022 14:38:35 +0100 Subject: [PATCH] fix: correct inline/merge with overriden bridge method (#1580) --- .../metadata/annotations/NodeDeclareRef.java | 16 +++++++ .../java/jadx/core/codegen/MethodGen.java | 14 +++++- .../jadx/core/dex/visitors/ClassModifier.java | 4 ++ .../assertj/JadxClassNodeAssertions.java | 9 ++-- .../generics/TestGenericsMthOverride.java | 4 +- .../inline/TestOverrideBridgeMerge.java | 45 +++++++++++++++++++ .../inline/TestOverrideBridgeMerge.smali | 39 ++++++++++++++++ 7 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/inline/TestOverrideBridgeMerge.java create mode 100644 jadx-core/src/test/smali/inline/TestOverrideBridgeMerge.smali 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 c956e70a454..8b237e0d80a 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 AnnType getAnnType() { 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 3266d76e998..4b6810c6a2e 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.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 boolean addDefinition(ICodeWriter code) { } 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 boolean addDefinition(ICodeWriter code) { 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 79666f16e7e..88bc06da264 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 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 @@ private static boolean checkSyntheticWrapper(MethodNode mth, InsnNode insn) { 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 b273a8f7faf..6c5670da4d7 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 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 JadxClassNodeAssertions runDecompiledAutoCheck(IntegrationTest testInstan return this; } - public void checkCodeAnnotationFor(String refStr, ICodeNode node) { + public JadxClassNodeAssertions checkCodeAnnotationFor(String refStr, ICodeAnnotation node) { checkCodeAnnotationFor(refStr, 0, node); + return this; } - public void checkCodeAnnotationFor(String refStr, int refOffset, ICodeNode node) { + public JadxClassNodeAssertions checkCodeAnnotationFor(String refStr, int refOffset, ICodeAnnotation node) { ICodeInfo code = actual.getCode(); int codePos = code.getCodeStr().indexOf(refStr); assertThat(codePos).describedAs("String '%s' not found", refStr).isNotEqualTo(-1); @@ -70,9 +70,10 @@ public void checkCodeAnnotationFor(String refStr, int refOffset, ICodeNode node) for (Map.Entry 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 75d85d531bf..47b2c579725 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 void test() { 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 00000000000..fd429062dbc --- /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 00000000000..689c46a1ea2 --- /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