未验证 提交 17f99ed9 编写于 作者: S Skylot

fix: adjust class processing order for correct methods inline (#1264)

上级 954d239b
package jadx.core; 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.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
...@@ -85,9 +90,23 @@ public final class ProcessClass { ...@@ -85,9 +90,23 @@ public final class ProcessClass {
return generateCode(topParentClass); return generateCode(topParentClass);
} }
try { try {
Set<ClassNode> useIn = new HashSet<>(cls.getUseIn());
List<ClassNode> usedInDeps = new ArrayList<>();
for (ClassNode depCls : cls.getDependencies()) { for (ClassNode depCls : cls.getDependencies()) {
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); process(depCls, false);
} }
}
ICodeInfo code = process(cls, true); ICodeInfo code = process(cls, true);
if (code == null) { if (code == null) {
throw new JadxRuntimeException("Codegen failed"); throw new JadxRuntimeException("Codegen failed");
......
...@@ -9,6 +9,7 @@ import java.util.Iterator; ...@@ -9,6 +9,7 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
...@@ -29,6 +30,7 @@ import jadx.core.dex.attributes.FieldInitInsnAttr; ...@@ -29,6 +30,7 @@ import jadx.core.dex.attributes.FieldInitInsnAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr; import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField; import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.LineAttrNode; 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.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
...@@ -291,6 +293,9 @@ public class ClassGen { ...@@ -291,6 +293,9 @@ public class ClassGen {
} }
private void addMethod(ICodeWriter code, MethodNode mth) { private void addMethod(ICodeWriter code, MethodNode mth) {
if (skipMethod(mth)) {
return;
}
if (code.getLength() != clsDeclOffset) { if (code.getLength() != clsDeclOffset) {
code.newLine(); code.newLine();
} }
...@@ -307,6 +312,29 @@ public class ClassGen { ...@@ -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() { private boolean isMethodsPresents() {
for (MethodNode mth : cls.getMethods()) { for (MethodNode mth : cls.getMethods()) {
if (!mth.contains(AFlag.DONT_GENERATE)) { if (!mth.contains(AFlag.DONT_GENERATE)) {
......
...@@ -4,8 +4,6 @@ import java.util.List; ...@@ -4,8 +4,6 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import jadx.api.plugins.input.data.attributes.PinnedAttribute; 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.attributes.AType;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
...@@ -26,11 +24,7 @@ public class MethodInlineAttr extends PinnedAttribute { ...@@ -26,11 +24,7 @@ public class MethodInlineAttr extends PinnedAttribute {
} }
MethodInlineAttr mia = new MethodInlineAttr(replaceInsn, regNums); MethodInlineAttr mia = new MethodInlineAttr(replaceInsn, regNums);
mth.addAttr(mia); mth.addAttr(mia);
if (Consts.DEBUG) { mth.addDebugComment("Marked for inline");
mth.addDebugComment("Removed for inline");
} else {
mth.add(AFlag.DONT_GENERATE);
}
return mia; return mia;
} }
......
...@@ -289,6 +289,10 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, ...@@ -289,6 +289,10 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return parentClass; return parentClass;
} }
public ClassNode getTopParentClass() {
return parentClass.getTopParentClass();
}
public boolean isNoCode() { public boolean isNoCode() {
return noCode; return noCode;
} }
......
...@@ -6,4 +6,8 @@ public enum ProcessState { ...@@ -6,4 +6,8 @@ public enum ProcessState {
PROCESS_STARTED, PROCESS_STARTED,
PROCESS_COMPLETE, PROCESS_COMPLETE,
GENERATED_AND_UNLOADED; GENERATED_AND_UNLOADED;
public boolean isProcessComplete() {
return this == PROCESS_COMPLETE || this == GENERATED_AND_UNLOADED;
}
} }
...@@ -70,9 +70,6 @@ public class ClassModifier extends AbstractVisitor { ...@@ -70,9 +70,6 @@ public class ClassModifier extends AbstractVisitor {
* Remove synthetic fields if type is outer class or class will be inlined (anonymous) * Remove synthetic fields if type is outer class or class will be inlined (anonymous)
*/ */
private static void removeSyntheticFields(ClassNode cls) { private static void removeSyntheticFields(ClassNode cls) {
if (cls.getAccessFlags().isStatic()) {
return;
}
boolean inline = cls.isAnonymous(); boolean inline = cls.isAnonymous();
if (inline || cls.getClassInfo().isInner()) { if (inline || cls.getClassInfo().isInner()) {
for (FieldNode field : cls.getFields()) { for (FieldNode field : cls.getFields()) {
......
...@@ -9,6 +9,10 @@ import org.slf4j.LoggerFactory; ...@@ -9,6 +9,10 @@ import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodInlineAttr; 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.InsnType;
import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
...@@ -16,11 +20,14 @@ import jadx.core.dex.instructions.args.InsnArg; ...@@ -16,11 +20,14 @@ import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode; 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.IMethodDetails;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.utils.BlockUtils; import jadx.core.utils.BlockUtils;
import jadx.core.utils.ListUtils;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
...@@ -112,6 +119,7 @@ public class InlineMethods extends AbstractVisitor { ...@@ -112,6 +119,7 @@ public class InlineMethods extends AbstractVisitor {
if (methodDetailsAttr != null) { if (methodDetailsAttr != null) {
inlCopy.addAttr(methodDetailsAttr); inlCopy.addAttr(methodDetailsAttr);
} }
updateUsageInfo(mth, callMth, mia.getInsn());
} }
private boolean isAssignNeeded(InsnNode inlineInsn, InvokeNode parentInsn, MethodNode callMthNode) { private boolean isAssignNeeded(InsnNode inlineInsn, InvokeNode parentInsn, MethodNode callMthNode) {
...@@ -135,4 +143,39 @@ public class InlineMethods extends AbstractVisitor { ...@@ -135,4 +143,39 @@ public class InlineMethods extends AbstractVisitor {
ssaVar.setType(varType); ssaVar.setType(varType);
return fakeArg; 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 { ...@@ -261,7 +261,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
} }
boolean updated = updateReturnType(mth, baseMth, superTypes); boolean updated = updateReturnType(mth, baseMth, superTypes);
if (updated) { 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; return updated;
} }
......
...@@ -50,4 +50,20 @@ public class ListUtils { ...@@ -50,4 +50,20 @@ public class ListUtils {
public static List<BlockNode> distinctList(List<BlockNode> list) { public static List<BlockNode> distinctList(List<BlockNode> list) {
return new ArrayList<>(new LinkedHashSet<>(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 { ...@@ -60,6 +60,10 @@ public abstract class SmaliTest extends IntegrationTest {
return searchCls(loadFromSmaliFiles(), getTestPkg() + '.' + clsName); return searchCls(loadFromSmaliFiles(), getTestPkg() + '.' + clsName);
} }
protected ClassNode getClassNodeFromSmaliFiles() {
return searchCls(loadFromSmaliFiles(), getTestPkg() + '.' + getTestName());
}
protected List<ClassNode> loadFromSmaliFiles() { protected List<ClassNode> loadFromSmaliFiles() {
jadxDecompiler = loadFiles(collectSmaliFiles(getTestPkg(), getTestName())); jadxDecompiler = loadFiles(collectSmaliFiles(getTestPkg(), getTestName()));
RootNode root = JadxInternalAccess.getRoot(jadxDecompiler); RootNode root = JadxInternalAccess.getRoot(jadxDecompiler);
......
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");
}
}
.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
.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
.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
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册