fix: inline anonymous classes with not default constructor (#450)

This commit is contained in:
Skylot
2019-04-14 19:02:16 +03:00
parent 0aa7173e83
commit 4cb9f23a7d
11 changed files with 319 additions and 45 deletions
@@ -582,14 +582,14 @@ public class InsnGen {
} else {
parent = cls.getSuperClass();
}
MethodNode defCtr = cls.getDefaultConstructor();
if (defCtr != null) {
if (RegionUtils.notEmpty(defCtr.getRegion())) {
defCtr.add(AFlag.ANONYMOUS_CONSTRUCTOR);
} else {
defCtr.add(AFlag.DONT_GENERATE);
// hide empty anonymous constructors
for (MethodNode ctor : cls.getMethods()) {
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
&& RegionUtils.isEmpty(ctor.getRegion())) {
ctor.add(AFlag.DONT_GENERATE);
}
}
code.add("new ");
if (parent == null) {
code.add("Object");
@@ -34,6 +34,7 @@ import jadx.core.dex.nodes.parser.AnnotationsParser;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.nodes.parser.SignatureParser;
import jadx.core.dex.nodes.parser.StaticValuesParser;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -124,7 +125,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
accFlagsValue = cls.getAccessFlags();
}
this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS);
markAnonymousClass(this);
markAnonymousClass();
buildCache();
} catch (Exception e) {
throw new JadxRuntimeException("Error decode class: " + clsInfo, e);
@@ -403,10 +404,25 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
&& getSuperClass().getObject().equals(ArgType.ENUM.getObject());
}
public boolean markAnonymousClass() {
if (isAnonymous() || isLambdaCls()) {
add(AFlag.ANONYMOUS_CLASS);
add(AFlag.DONT_GENERATE);
for (MethodNode mth : getMethods()) {
if (mth.isConstructor()) {
mth.add(AFlag.ANONYMOUS_CONSTRUCTOR);
}
}
return true;
}
return false;
}
public boolean isAnonymous() {
return clsInfo.isInner()
&& clsInfo.getAlias().getShortName().startsWith(Consts.ANONYMOUS_CLASS_PREFIX)
&& getDefaultConstructor() != null;
&& Character.isDigit(clsInfo.getShortName().charAt(0))
&& methods.stream().filter(MethodNode::isConstructor).count() == 1;
}
public boolean isLambdaCls() {
@@ -425,13 +441,6 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
return c;
}
private static void markAnonymousClass(ClassNode cls) {
if (cls.isAnonymous() || cls.isLambdaCls()) {
cls.add(AFlag.ANONYMOUS_CLASS);
cls.add(AFlag.DONT_GENERATE);
}
}
@Nullable
public MethodNode getClassInitMth() {
return searchMethodByShortId("<clinit>()V");
@@ -552,9 +552,12 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
return false;
}
public boolean isConstructor() {
return accFlags.isConstructor() && mthInfo.isConstructor();
}
public boolean isDefaultConstructor() {
boolean result = false;
if (accFlags.isConstructor() && mthInfo.isConstructor()) {
if (isConstructor()) {
int defaultArgCount = 0;
// workaround for non-static inner class constructor, that has synthetic argument
if (parentClass.getClassInfo().isInner()
@@ -565,9 +568,9 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
defaultArgCount = 1;
}
}
result = argsList == null || argsList.size() == defaultArgCount;
return argsList == null || argsList.size() == defaultArgCount;
}
return result;
return false;
}
public boolean isVirtual() {
@@ -51,10 +51,11 @@ public class ClassModifier extends AbstractVisitor {
cls.add(AFlag.DONT_GENERATE);
return false;
}
markAnonymousClass(cls);
cls.markAnonymousClass();
removeSyntheticFields(cls);
cls.getMethods().forEach(ClassModifier::removeSyntheticMethods);
cls.getMethods().forEach(ClassModifier::removeEmptyMethods);
cls.getMethods().forEach(ClassModifier::cleanInsnsInAnonymousConstructor);
return false;
}
@@ -65,13 +66,6 @@ public class ClassModifier extends AbstractVisitor {
&& cls.getInnerClasses().isEmpty();
}
private void markAnonymousClass(ClassNode cls) {
if (cls.isAnonymous()) {
cls.add(AFlag.ANONYMOUS_CLASS);
cls.add(AFlag.DONT_GENERATE);
}
}
/**
* Remove synthetic fields if type is outer class or class will be inlined (anonymous)
*/
@@ -324,12 +318,37 @@ public class ClassModifier extends AbstractVisitor {
}
}
/**
* Remove super call and put into removed fields from anonymous constructor
*/
private static void cleanInsnsInAnonymousConstructor(MethodNode mth) {
if (!mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
return;
}
for (BlockNode block : mth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) {
InsnType type = insn.getType();
if (type == InsnType.CONSTRUCTOR) {
ConstructorInsn ctorInsn = (ConstructorInsn) insn;
if (ctorInsn.isSuper()) {
ctorInsn.add(AFlag.DONT_GENERATE);
}
} else if (type == InsnType.IPUT) {
FieldInfo fldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
FieldNode fieldNode = mth.dex().resolveField(fldInfo);
if (fieldNode != null && fieldNode.contains(AFlag.DONT_GENERATE)) {
insn.add(AFlag.DONT_GENERATE);
}
}
}
}
}
private static boolean isNonDefaultConstructorExists(MethodNode defCtor) {
ClassNode parentClass = defCtor.getParentClass();
for (MethodNode mth : parentClass.getMethods()) {
if (mth != defCtor
&& mth.getAccessFlags().isConstructor()
&& mth.getMethodInfo().isConstructor()
&& mth.isConstructor()
&& !mth.isDefaultConstructor()) {
return true;
}
@@ -11,7 +11,6 @@ import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ArithNode;
@@ -39,8 +38,8 @@ import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.InsnRemover;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.utils.BlockUtils.replaceInsn;
@@ -207,23 +206,22 @@ public class ModVisitor extends AbstractVisitor {
if (callMthNode == null) {
return;
}
ClassNode classNode = callMthNode.getParentClass();
ClassInfo classInfo = classNode.getClassInfo();
ClassNode parentClass = mth.getParentClass();
if (!classInfo.isInner()
|| !Character.isDigit(classInfo.getShortName().charAt(0))
|| !parentClass.getInnerClasses().contains(classNode)) {
return;
}
// TODO: calculate this constructor and other constructor usage
Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(callMthNode, co);
if (argsMap.isEmpty() && !callMthNode.getArguments(true).isEmpty()) {
return;
}
// all checks passed
classNode.add(AFlag.ANONYMOUS_CLASS);
callMthNode.add(AFlag.DONT_GENERATE);
ClassNode classNode = callMthNode.getParentClass();
if (!classNode.contains(AFlag.ANONYMOUS_CLASS)) {
// check if class can be anonymous but not yet marked due to dependency issues
if (!classNode.markAnonymousClass()) {
return;
}
}
if (!mth.getParentClass().getInnerClasses().contains(classNode)) {
return;
}
for (Map.Entry<InsnArg, FieldNode> entry : argsMap.entrySet()) {
FieldNode field = entry.getValue();
if (field == null) {
@@ -7,6 +7,7 @@ import java.util.Set;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrList;
import jadx.core.dex.attributes.nodes.LoopInfo;
@@ -195,7 +196,13 @@ public class RegionUtils {
return false;
}
if (container instanceof IBlock) {
return !((IBlock) container).getInstructions().isEmpty();
List<InsnNode> insnList = ((IBlock) container).getInstructions();
for (InsnNode insnNode : insnList) {
if (!insnNode.contains(AFlag.DONT_GENERATE)) {
return true;
}
}
return false;
} else if (container instanceof IRegion) {
IRegion region = (IRegion) container;
for (IContainer block : region.getSubBlocks()) {