core: fix synthetic constructor remove (#265)
This commit is contained in:
@@ -210,7 +210,7 @@ public class DexNode implements IDexNode {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DEX";
|
||||
return "DEX: " + file;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,33 +134,66 @@ 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);
|
||||
continue;
|
||||
}
|
||||
// remove synthetic constructor for inner classes
|
||||
if (af.isSynthetic() && af.isConstructor() && mth.getBasicBlocks().size() == 2) {
|
||||
List<InsnNode> insns = mth.getBasicBlocks().get(0).getInstructions();
|
||||
if (insns.size() == 1 && insns.get(0).getType() == InsnType.CONSTRUCTOR) {
|
||||
ConstructorInsn constr = (ConstructorInsn) insns.get(0);
|
||||
} else {
|
||||
// remove synthetic constructor for inner classes
|
||||
if (af.isSynthetic() && af.isConstructor() && mth.getBasicBlocks().size() == 2) {
|
||||
List<RegisterArg> 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);
|
||||
if (isRemovedClassInArgs(cls, args)) {
|
||||
modifySyntheticMethod(cls, mth, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isRemovedClassInArgs(ClassNode cls, List<RegisterArg> mthArgs) {
|
||||
for (RegisterArg arg : mthArgs) {
|
||||
ArgType argType = arg.getType();
|
||||
if (!argType.isObject()) {
|
||||
continue;
|
||||
}
|
||||
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<RegisterArg> args) {
|
||||
List<InsnNode> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isMethodUniq(ClassNode cls, MethodNode mth) {
|
||||
MethodInfo mi = mth.getMethodInfo();
|
||||
for (MethodNode otherMth : cls.getMethods()) {
|
||||
@@ -191,5 +225,4 @@ public class ClassModifier extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+35
@@ -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
|
||||
}
|
||||
}
|
||||
+28
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
.class public Ljadx/tests/inner/TestCls;
|
||||
.super Ljava/lang/Object;
|
||||
|
||||
# direct methods
|
||||
.method public synthetic constructor <init>(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;-><init>(Ljava/lang/String;Z)V
|
||||
|
||||
return-void
|
||||
.end method
|
||||
|
||||
.method public constructor <init>(Ljava/lang/String;Z)V
|
||||
.registers 3
|
||||
.param p1, "a" # Ljava/lang/String;
|
||||
.param p2, "b" # Z
|
||||
|
||||
.prologue
|
||||
invoke-direct {p0}, Ljava/lang/Object;-><init>()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;-><init>(Ljava/lang/String;)V
|
||||
|
||||
return-object v0
|
||||
.end method
|
||||
Reference in New Issue
Block a user