diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java index c407d1098649b2e62d2db3e81df0abb3409e79e5..bc653d8ef6241770ea74f758434533c0211d74e9 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java @@ -210,7 +210,7 @@ public class DexNode implements IDexNode { @Override public String toString() { - return "DEX"; + return "DEX: " + file; } } 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 25949c45495940575e96ce10c0c2e64974ae6282..75d467a07899813609f297c2e3eb2e66ad545ed3 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 @@ -11,6 +11,7 @@ import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.SSAVar; @@ -133,29 +134,62 @@ public class ClassModifier extends AbstractVisitor { if (af.isBridge() && af.isSynthetic() && !isMethodUniq(cls, mth)) { // TODO add more checks before method deletion mth.add(AFlag.DONT_GENERATE); + } else { + // remove synthetic constructor for inner classes + if (af.isSynthetic() && af.isConstructor() && mth.getBasicBlocks().size() == 2) { + List args = mth.getArguments(false); + if (isRemovedClassInArgs(cls, args)) { + modifySyntheticMethod(cls, mth, args); + } + } + } + } + } + + private static boolean isRemovedClassInArgs(ClassNode cls, List mthArgs) { + for (RegisterArg arg : mthArgs) { + ArgType argType = arg.getType(); + if (!argType.isObject()) { continue; } - // remove synthetic constructor for inner classes - if (af.isSynthetic() && af.isConstructor() && mth.getBasicBlocks().size() == 2) { - List insns = mth.getBasicBlocks().get(0).getInstructions(); - if (insns.size() == 1 && insns.get(0).getType() == InsnType.CONSTRUCTOR) { - ConstructorInsn constr = (ConstructorInsn) insns.get(0); - List args = mth.getArguments(false); - if (constr.isThis() && !args.isEmpty()) { - // remove first arg for non-static class (references to outer class) - if (args.get(0).getType().equals(cls.getParentClass().getClassInfo().getType())) { - args.get(0).add(AFlag.SKIP_ARG); - } - // remove unused args - for (RegisterArg arg : args) { - SSAVar sVar = arg.getSVar(); - if (sVar != null && sVar.getUseCount() == 0) { - arg.add(AFlag.SKIP_ARG); - } - } - mth.add(AFlag.DONT_GENERATE); + ClassNode argCls = cls.dex().resolveClass(argType); + if (argCls == null) { + // check if missing class from current top class + ClassInfo argClsInfo = ClassInfo.fromType(cls.root(), argType); + if (argClsInfo.isInner() + && cls.getFullName().startsWith(argClsInfo.getParentClass().getFullName())) { + return true; + } + } else { + if (argCls.contains(AFlag.DONT_GENERATE)) { + return true; + } + } + } + return false; + } + + /** + * Remove synthetic constructor and redirect calls to existing constructor + */ + private static void modifySyntheticMethod(ClassNode cls, MethodNode mth, List args) { + List insns = mth.getBasicBlocks().get(0).getInstructions(); + if (insns.size() == 1 && insns.get(0).getType() == InsnType.CONSTRUCTOR) { + ConstructorInsn constr = (ConstructorInsn) insns.get(0); + if (constr.isThis() && !args.isEmpty()) { + // remove first arg for non-static class (references to outer class) + RegisterArg firstArg = args.get(0); + if (firstArg.getType().equals(cls.getParentClass().getClassInfo().getType())) { + firstArg.add(AFlag.SKIP_ARG); + } + // remove unused args + for (RegisterArg arg : args) { + SSAVar sVar = arg.getSVar(); + if (sVar != null && sVar.getUseCount() == 0) { + arg.add(AFlag.SKIP_ARG); } } + mth.add(AFlag.DONT_GENERATE); } } } @@ -191,5 +225,4 @@ public class ClassModifier extends AbstractVisitor { } } } - } diff --git a/jadx-core/src/test/java/jadx/tests/api/SmaliTest.java b/jadx-core/src/test/java/jadx/tests/api/SmaliTest.java index 7b79d35042bd460c85783d5b0cdfb045a82de38f..75d507ca4048f6f6dc4fb298286f19e879a47c71 100644 --- a/jadx-core/src/test/java/jadx/tests/api/SmaliTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/SmaliTest.java @@ -37,7 +37,7 @@ public abstract class SmaliTest extends IntegrationTest { if (smaliFile.exists()) { return smaliFile; } - throw new AssertionError("Smali file not found: " + SMALI_TESTS_DIR + "/" + clsName + SMALI_TESTS_EXT); + throw new AssertionError("Smali file not found: " + smaliFile.getAbsolutePath()); } private static boolean compileSmali(File input, File output) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestInnerClassFakeSyntheticConstructor.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestInnerClassFakeSyntheticConstructor.java new file mode 100644 index 0000000000000000000000000000000000000000..1560806a6c46a3e17b4fba329a34387bde9a6fb3 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inner/TestInnerClassFakeSyntheticConstructor.java @@ -0,0 +1,35 @@ +package jadx.tests.integration.inner; + +import org.junit.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.junit.Assert.assertThat; + +public class TestInnerClassFakeSyntheticConstructor extends SmaliTest { + +// public class TestCls { +// public /* synthetic */ TestCls(String a) { +// this(a, true); +// } +// +// public TestCls(String a, boolean b) { +// } +// +// public static TestCls build(String str) { +// return new TestCls(str); +// } +// } + + @Test + public void test() { + ClassNode cls = getClassNodeFromSmali("inner/TestInnerClassFakeSyntheticConstructor", "jadx.tests.inner.TestCls"); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("TestCls(String a) {")); + // and must compile + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestInnerClassSyntheticConstructor.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestInnerClassSyntheticConstructor.java new file mode 100644 index 0000000000000000000000000000000000000000..6deb70ef83c6fe374a970a68c86c5ff35ac5c433 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inner/TestInnerClassSyntheticConstructor.java @@ -0,0 +1,28 @@ +package jadx.tests.integration.inner; + +import org.junit.Test; + +import jadx.tests.api.IntegrationTest; +import jadx.tests.api.SmaliTest; + +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; + +public class TestInnerClassSyntheticConstructor extends IntegrationTest { + + private class TestCls { + private int mth() { + return 1; + } + } + + public int call() { + return new TestCls().mth(); + } + + @Test + public void test() { + getClassNode(TestInnerClassSyntheticConstructor.class); + // must compile, no usage of removed synthetic empty class + } +} diff --git a/jadx-core/src/test/smali/inner/TestInnerClassFakeSyntheticConstructor.smali b/jadx-core/src/test/smali/inner/TestInnerClassFakeSyntheticConstructor.smali new file mode 100644 index 0000000000000000000000000000000000000000..aa1498cee8c03b54441406b62a70ed441923474e --- /dev/null +++ b/jadx-core/src/test/smali/inner/TestInnerClassFakeSyntheticConstructor.smali @@ -0,0 +1,38 @@ +.class public Ljadx/tests/inner/TestCls; +.super Ljava/lang/Object; + +# direct methods +.method public synthetic constructor (Ljava/lang/String;)V + .registers 3 + .param p1, "a" # Ljava/lang/String; + + .prologue + const/4 v0, 0x1 + + invoke-direct {p0, p1, v0}, Ljadx/tests/inner/TestCls;->(Ljava/lang/String;Z)V + + return-void +.end method + +.method public constructor (Ljava/lang/String;Z)V + .registers 3 + .param p1, "a" # Ljava/lang/String; + .param p2, "b" # Z + + .prologue + invoke-direct {p0}, Ljava/lang/Object;->()V + + return-void +.end method + +.method public static build(Ljava/lang/String;)Ljadx/tests/inner/TestCls; + .registers 2 + .param p0, "str" # Ljava/lang/String; + + .prologue + new-instance v0, Ljadx/tests/inner/TestCls; + + invoke-direct {v0, p0}, Ljadx/tests/inner/TestCls;->(Ljava/lang/String;)V + + return-object v0 +.end method