Commit 98ef7c39 authored by Skylot's avatar Skylot

core: fix synthetic constructor remove (#265)

parent e039a5a9
......@@ -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,18 +134,53 @@ 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;
}
} else {
// remove synthetic constructor for inner classes
if (af.isSynthetic() && af.isConstructor() && mth.getBasicBlocks().size() == 2) {
List<RegisterArg> args = mth.getArguments(false);
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);
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);
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) {
......@@ -157,8 +193,6 @@ public class ClassModifier extends AbstractVisitor {
}
}
}
}
}
private static boolean isMethodUniq(ClassNode cls, MethodNode mth) {
MethodInfo mi = mth.getMethodInfo();
......@@ -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) {
......
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
}
}
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
}
}
.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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment