fix: adjust class processing order for correct methods inline (#1264)
This commit is contained in:
@@ -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<ClassNode> useIn = new HashSet<>(cls.getUseIn());
|
||||
List<ClassNode> 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) {
|
||||
|
||||
@@ -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<MethodNode> 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)) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -289,6 +289,10 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
return parentClass;
|
||||
}
|
||||
|
||||
public ClassNode getTopParentClass() {
|
||||
return parentClass.getTopParentClass();
|
||||
}
|
||||
|
||||
public boolean isNoCode() {
|
||||
return noCode;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -50,4 +50,20 @@ public class ListUtils {
|
||||
public static List<BlockNode> distinctList(List<BlockNode> 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 <T> List<T> safeReplace(List<T> list, T oldObj, T newObj) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
// immutable empty list
|
||||
List<T> newList = new ArrayList<>(1);
|
||||
newList.add(newObj);
|
||||
return newList;
|
||||
}
|
||||
list.remove(oldObj);
|
||||
list.add(newObj);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ClassNode> loadFromSmaliFiles() {
|
||||
jadxDecompiler = loadFiles(collectSmaliFiles(getTestPkg(), getTestName()));
|
||||
RootNode root = JadxInternalAccess.getRoot(jadxDecompiler);
|
||||
|
||||
@@ -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<String, Void>() {
|
||||
@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");
|
||||
}
|
||||
}
|
||||
@@ -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 = {
|
||||
"<P1:",
|
||||
"Ljava/lang/Object;",
|
||||
"R:",
|
||||
"Ljava/lang/Object;",
|
||||
">",
|
||||
"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
|
||||
+63
@@ -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 <init>(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;-><init>(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
|
||||
@@ -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;-><init>(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;-><init>(Landroid/content/Intent;)V
|
||||
return-object v0
|
||||
.end method
|
||||
Reference in New Issue
Block a user