diff --git a/jadx-core/src/main/java/jadx/core/ProcessClass.java b/jadx-core/src/main/java/jadx/core/ProcessClass.java index f1d43ad7d..1e347e8a6 100644 --- a/jadx-core/src/main/java/jadx/core/ProcessClass.java +++ b/jadx-core/src/main/java/jadx/core/ProcessClass.java @@ -1,5 +1,10 @@ package jadx.core; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -85,8 +90,22 @@ public final class ProcessClass { return generateCode(topParentClass); } try { + Set useIn = new HashSet<>(cls.getUseIn()); + List usedInDeps = new ArrayList<>(); for (ClassNode depCls : cls.getDependencies()) { - process(depCls, false); + if (useIn.contains(depCls)) { + // postpone to resolve cross dependencies + usedInDeps.add(depCls); + } else { + process(depCls, false); + } + } + if (!usedInDeps.isEmpty()) { + // process current class before its usage + process(cls, false); + for (ClassNode depCls : usedInDeps) { + process(depCls, false); + } } ICodeInfo code = process(cls, true); if (code == null) { diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index f0bae6932..278fb10e6 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -9,6 +9,7 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.jetbrains.annotations.NotNull; @@ -29,6 +30,7 @@ import jadx.core.dex.attributes.FieldInitInsnAttr; import jadx.core.dex.attributes.nodes.EnumClassAttr; import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField; import jadx.core.dex.attributes.nodes.LineAttrNode; +import jadx.core.dex.attributes.nodes.MethodInlineAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.ClassInfo; @@ -291,6 +293,9 @@ public class ClassGen { } private void addMethod(ICodeWriter code, MethodNode mth) { + if (skipMethod(mth)) { + return; + } if (code.getLength() != clsDeclOffset) { code.newLine(); } @@ -307,6 +312,29 @@ public class ClassGen { } } + /** + * Additional checks for inlined methods + */ + private boolean skipMethod(MethodNode mth) { + MethodInlineAttr inlineAttr = mth.get(AType.METHOD_INLINE); + if (inlineAttr == null || inlineAttr.notNeeded()) { + return false; + } + if (mth.getUseIn().isEmpty()) { + mth.add(AFlag.DONT_GENERATE); + return true; + } + List useInCompleted = mth.getUseIn().stream() + .filter(m -> m.getTopParentClass().getState().isProcessComplete()) + .collect(Collectors.toList()); + if (useInCompleted.isEmpty()) { + mth.add(AFlag.DONT_GENERATE); + return true; + } + mth.addDebugComment("Method not inlined, still used in: " + useInCompleted); + return false; + } + private boolean isMethodsPresents() { for (MethodNode mth : cls.getMethods()) { if (!mth.contains(AFlag.DONT_GENERATE)) { diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodInlineAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodInlineAttr.java index fdda93a72..32a2425b0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodInlineAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodInlineAttr.java @@ -4,8 +4,6 @@ import java.util.List; import java.util.Objects; import jadx.api.plugins.input.data.attributes.PinnedAttribute; -import jadx.core.Consts; -import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.nodes.InsnNode; @@ -26,11 +24,7 @@ public class MethodInlineAttr extends PinnedAttribute { } MethodInlineAttr mia = new MethodInlineAttr(replaceInsn, regNums); mth.addAttr(mia); - if (Consts.DEBUG) { - mth.addDebugComment("Removed for inline"); - } else { - mth.add(AFlag.DONT_GENERATE); - } + mth.addDebugComment("Marked for inline"); return mia; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index cf668a698..b0820c0c0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -289,6 +289,10 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return parentClass; } + public ClassNode getTopParentClass() { + return parentClass.getTopParentClass(); + } + public boolean isNoCode() { return noCode; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ProcessState.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ProcessState.java index 81ca66852..738dbd691 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ProcessState.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ProcessState.java @@ -6,4 +6,8 @@ public enum ProcessState { PROCESS_STARTED, PROCESS_COMPLETE, GENERATED_AND_UNLOADED; + + public boolean isProcessComplete() { + return this == PROCESS_COMPLETE || this == GENERATED_AND_UNLOADED; + } } 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 ba34dfb6a..75a38bdea 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 @@ -70,9 +70,6 @@ public class ClassModifier extends AbstractVisitor { * Remove synthetic fields if type is outer class or class will be inlined (anonymous) */ private static void removeSyntheticFields(ClassNode cls) { - if (cls.getAccessFlags().isStatic()) { - return; - } boolean inline = cls.isAnonymous(); if (inline || cls.getClassInfo().isInner()) { for (FieldNode field : cls.getFields()) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/InlineMethods.java b/jadx-core/src/main/java/jadx/core/dex/visitors/InlineMethods.java index 9c00f5d92..e43878ca6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/InlineMethods.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/InlineMethods.java @@ -9,6 +9,10 @@ import org.slf4j.LoggerFactory; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.MethodInlineAttr; +import jadx.core.dex.info.FieldInfo; +import jadx.core.dex.info.MethodInfo; +import jadx.core.dex.instructions.BaseInvokeNode; +import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.args.ArgType; @@ -16,11 +20,14 @@ import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; import jadx.core.utils.BlockUtils; +import jadx.core.utils.ListUtils; import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -112,6 +119,7 @@ public class InlineMethods extends AbstractVisitor { if (methodDetailsAttr != null) { inlCopy.addAttr(methodDetailsAttr); } + updateUsageInfo(mth, callMth, mia.getInsn()); } private boolean isAssignNeeded(InsnNode inlineInsn, InvokeNode parentInsn, MethodNode callMthNode) { @@ -135,4 +143,39 @@ public class InlineMethods extends AbstractVisitor { ssaVar.setType(varType); return fakeArg; } + + private void updateUsageInfo(MethodNode mth, MethodNode inlinedMth, InsnNode insn) { + inlinedMth.getUseIn().remove(mth); + insn.visitInsns(innerInsn -> { + // TODO: share code with UsageInfoVisitor + switch (innerInsn.getType()) { + case INVOKE: + case CONSTRUCTOR: + MethodInfo callMth = ((BaseInvokeNode) innerInsn).getCallMth(); + MethodNode callMthNode = mth.root().resolveMethod(callMth); + if (callMthNode != null) { + callMthNode.setUseIn(ListUtils.safeReplace(callMthNode.getUseIn(), inlinedMth, mth)); + replaceClsUsage(mth, inlinedMth, callMthNode.getParentClass()); + } + break; + + case IGET: + case IPUT: + case SPUT: + case SGET: + FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) innerInsn).getIndex(); + FieldNode fieldNode = mth.root().resolveField(fieldInfo); + if (fieldNode != null) { + fieldNode.setUseIn(ListUtils.safeReplace(fieldNode.getUseIn(), inlinedMth, mth)); + replaceClsUsage(mth, inlinedMth, fieldNode.getParentClass()); + } + break; + } + }); + } + + private void replaceClsUsage(MethodNode mth, MethodNode inlinedMth, ClassNode parentClass) { + parentClass.setUseInMth(ListUtils.safeReplace(parentClass.getUseInMth(), inlinedMth, mth)); + parentClass.setUseIn(ListUtils.safeReplace(parentClass.getUseIn(), inlinedMth.getParentClass(), mth.getParentClass())); + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java index 0d60ccece..becb7e225 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java @@ -261,7 +261,7 @@ public class OverrideMethodVisitor extends AbstractVisitor { } boolean updated = updateReturnType(mth, baseMth, superTypes); if (updated) { - mth.addInfoComment("Return type fixed from '" + returnType + "' to match base method"); + mth.addDebugComment("Return type fixed from '" + returnType + "' to match base method"); } return updated; } diff --git a/jadx-core/src/main/java/jadx/core/utils/ListUtils.java b/jadx-core/src/main/java/jadx/core/utils/ListUtils.java index d987490ce..0c9865e04 100644 --- a/jadx-core/src/main/java/jadx/core/utils/ListUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/ListUtils.java @@ -50,4 +50,20 @@ public class ListUtils { public static List distinctList(List list) { return new ArrayList<>(new LinkedHashSet<>(list)); } + + /** + * Replace old element to new one. + * Support null and empty immutable list (created by Collections.emptyList()) + */ + public static List safeReplace(List list, T oldObj, T newObj) { + if (list == null || list.isEmpty()) { + // immutable empty list + List newList = new ArrayList<>(1); + newList.add(newObj); + return newList; + } + list.remove(oldObj); + list.add(newObj); + return list; + } } 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 f6e9dd308..0b332e0b5 100644 --- a/jadx-core/src/test/java/jadx/tests/api/SmaliTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/SmaliTest.java @@ -60,6 +60,10 @@ public abstract class SmaliTest extends IntegrationTest { return searchCls(loadFromSmaliFiles(), getTestPkg() + '.' + clsName); } + protected ClassNode getClassNodeFromSmaliFiles() { + return searchCls(loadFromSmaliFiles(), getTestPkg() + '.' + getTestName()); + } + protected List loadFromSmaliFiles() { jadxDecompiler = loadFiles(collectSmaliFiles(getTestPkg(), getTestName())); RootNode root = JadxInternalAccess.getRoot(jadxDecompiler); diff --git a/jadx-core/src/test/java/jadx/tests/integration/inline/TestSyntheticInline3.java b/jadx-core/src/test/java/jadx/tests/integration/inline/TestSyntheticInline3.java new file mode 100644 index 000000000..a195173e8 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inline/TestSyntheticInline3.java @@ -0,0 +1,49 @@ +package jadx.tests.integration.inline; + +import java.util.function.Function; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestSyntheticInline3 extends SmaliTest { + + @SuppressWarnings({ "Convert2Lambda", "TrivialFunctionalExpressionUsage" }) + public static class TestCls { + private String strField; + + private String str() { + return "a"; + } + + private void test() { + new Function() { + @Override + public Void apply(String s) { + System.out.println(s + strField + str()); + return null; + } + }.apply("c"); + } + } + + @Test + public void test() { + assertThat(getClassNode(TestCls.class)) + .code(); + } + + @Test + public void testSmali() { + allowWarnInCode(); + disableCompilation(); + assertThat(getClassNodeFromSmaliFiles()) + .code() + .doesNotContain(".access$getDialog$p(") + .doesNotContain(".access$getChooserIntent(") + .doesNotContain("= r1;") + .doesNotContain("this$0"); + } +} diff --git a/jadx-core/src/test/smali/inline/TestSyntheticInline3/KotlinFunction1.smali b/jadx-core/src/test/smali/inline/TestSyntheticInline3/KotlinFunction1.smali new file mode 100644 index 000000000..3beecd7f7 --- /dev/null +++ b/jadx-core/src/test/smali/inline/TestSyntheticInline3/KotlinFunction1.smali @@ -0,0 +1,26 @@ +.class public interface abstract Lkotlin/jvm/functions/Function1; +.super Ljava/lang/Object; +.source "SourceFile" + +.implements Lkotlin/Function; + +.annotation system Ldalvik/annotation/Signature; + value = { + "", + "Ljava/lang/Object;", + "Lkotlin/Function<", + "TR;>;" + } +.end annotation + +.method public abstract invoke(Ljava/lang/Object;)Ljava/lang/Object; + .annotation system Ldalvik/annotation/Signature; + value = { + "(TP1;)TR;" + } + .end annotation +.end method diff --git a/jadx-core/src/test/smali/inline/TestSyntheticInline3/TestSyntheticInline3$onCreate$1.smali b/jadx-core/src/test/smali/inline/TestSyntheticInline3/TestSyntheticInline3$onCreate$1.smali new file mode 100644 index 000000000..bf35c5739 --- /dev/null +++ b/jadx-core/src/test/smali/inline/TestSyntheticInline3/TestSyntheticInline3$onCreate$1.smali @@ -0,0 +1,63 @@ +.class final Linline/TestSyntheticInline3$onCreate$1; +.super Lkotlin/jvm/internal/Lambda; + +.implements Lkotlin/jvm/functions/Function1; + +.annotation system Ldalvik/annotation/InnerClass; + accessFlags = 0x18 + name = null +.end annotation + +.annotation system Ldalvik/annotation/Signature; + value = { + "Lkotlin/jvm/internal/Lambda;", + "Lkotlin/jvm/functions/Function1<", + "Ljava/lang/String;", + "Lkotlin/Unit;", + ">;" + } +.end annotation + + +.field final synthetic this$0:Linline/TestSyntheticInline3; + + +.method constructor (Linline/TestSyntheticInline3;)V + .registers 2 + iput-object p1, p0, Linline/TestSyntheticInline3$onCreate$1;->this$0:Linline/TestSyntheticInline3; + const/4 p1, 0x1 + invoke-direct {p0, p1}, Lkotlin/jvm/internal/Lambda;->(I)V + return-void +.end method + + +.method public bridge synthetic invoke(Ljava/lang/Object;)Ljava/lang/Object; + .registers 2 + check-cast p1, Ljava/lang/String; + invoke-virtual {p0, p1}, Linline/TestSyntheticInline3$onCreate$1;->invoke(Ljava/lang/String;)V + sget-object p1, Lkotlin/Unit;->INSTANCE:Lkotlin/Unit; + return-object p1 +.end method + +.method public final invoke(Ljava/lang/String;)V + .registers 5 + iget-object v0, p0, Linline/TestSyntheticInline3$onCreate$1;->this$0:Linline/TestSyntheticInline3; + invoke-static {v0}, Linline/TestSyntheticInline3;->access$getDialog$p(Linline/TestSyntheticInline3;)Landroidx/appcompat/app/AlertDialog; + move-result-object v0 + if-nez v0, :cond_9 + goto :goto_c + :cond_9 + invoke-virtual {v0}, Landroidx/appcompat/app/AppCompatDialog;->dismiss()V + :goto_c + iget-object v0, p0, Linline/TestSyntheticInline3$onCreate$1;->this$0:Linline/TestSyntheticInline3; + invoke-virtual {v0}, Landroid/app/Activity;->getIntent()Landroid/content/Intent; + move-result-object v1 + const-string v2, "intent" + invoke-static {v1, v2}, Lkotlin/jvm/internal/Intrinsics;->checkNotNullExpressionValue(Ljava/lang/Object;Ljava/lang/String;)V + invoke-static {v0, v1, p1}, Linline/TestSyntheticInline3;->access$getChooserIntent(Linline/TestSyntheticInline3;Landroid/content/Intent;Ljava/lang/String;)Landroid/content/Intent; + move-result-object p1 + invoke-virtual {v0, p1}, Landroid/app/Activity;->startActivity(Landroid/content/Intent;)V + iget-object p1, p0, Linline/TestSyntheticInline3$onCreate$1;->this$0:Linline/TestSyntheticInline3; + invoke-virtual {p1}, Landroid/app/Activity;->finish()V + return-void +.end method diff --git a/jadx-core/src/test/smali/inline/TestSyntheticInline3/TestSyntheticInline3.smali b/jadx-core/src/test/smali/inline/TestSyntheticInline3/TestSyntheticInline3.smali new file mode 100644 index 000000000..ffc274f5f --- /dev/null +++ b/jadx-core/src/test/smali/inline/TestSyntheticInline3/TestSyntheticInline3.smali @@ -0,0 +1,33 @@ +.class public final Linline/TestSyntheticInline3; +.super Landroid/app/Activity; + +.field private dialog:Landroidx/appcompat/app/AlertDialog; + +.method public static final synthetic access$getChooserIntent(Linline/TestSyntheticInline3;Landroid/content/Intent;Ljava/lang/String;)Landroid/content/Intent; + .registers 3 + invoke-direct {p0, p1, p2}, Linline/TestSyntheticInline3;->getChooserIntent(Landroid/content/Intent;Ljava/lang/String;)Landroid/content/Intent; + move-result-object p0 + return-object p0 +.end method + +.method public static final synthetic access$getDialog$p(Linline/TestSyntheticInline3;)Landroidx/appcompat/app/AlertDialog; + .registers 1 + iget-object p0, p0, Linline/TestSyntheticInline3;->dialog:Landroidx/appcompat/app/AlertDialog; + return-object p0 +.end method + +.method protected onCreate(Landroid/os/Bundle;)V + .registers 3 + invoke-super {p0, p1}, Landroidx/fragment/app/FragmentActivity;->onCreate(Landroid/os/Bundle;)V + new-instance v0, Linline/TestSyntheticInline3$onCreate$1; + invoke-direct {v0, p0}, Linline/TestSyntheticInline3$onCreate$1;->(Linline/TestSyntheticInline3;)V + return-void +.end method + + +.method private final getChooserIntent(Landroid/content/Intent;Ljava/lang/String;)Landroid/content/Intent; + .registers 5 + new-instance v0, Landroid/content/Intent; + invoke-direct {v0, p1}, Landroid/content/Intent;->(Landroid/content/Intent;)V + return-object v0 +.end method