diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 377d6c534..739888e25 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -8,6 +8,7 @@ import jadx.core.dex.visitors.DebugInfoVisitor; import jadx.core.dex.visitors.DependencyCollector; import jadx.core.dex.visitors.DotGraphVisitor; import jadx.core.dex.visitors.EnumVisitor; +import jadx.core.dex.visitors.ExtractFieldInit; import jadx.core.dex.visitors.FallbackModeVisitor; import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.dex.visitors.MethodInlineVisitor; @@ -96,6 +97,7 @@ public class Jadx { } passes.add(new MethodInlineVisitor()); + passes.add(new ExtractFieldInit()); passes.add(new ClassModifier()); passes.add(new EnumVisitor()); passes.add(new PrepareForCodeGen()); diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index 01a84e1b2..9089fff06 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -15,8 +15,10 @@ import jadx.core.dex.instructions.mods.ConstructorInsn; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.nodes.parser.FieldValueAttr; +import jadx.core.dex.nodes.parser.FieldInitAttr; +import jadx.core.dex.nodes.parser.FieldInitAttr.InitType; import jadx.core.utils.ErrorsCounter; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.CodegenException; @@ -339,13 +341,18 @@ public class ClassGen { useType(code, f.getType()); code.add(' '); code.add(f.getAlias()); - FieldValueAttr fv = f.get(AType.FIELD_VALUE); + FieldInitAttr fv = f.get(AType.FIELD_INIT); if (fv != null) { code.add(" = "); if (fv.getValue() == null) { code.add(TypeGen.literalToString(0, f.getType())); } else { - annotationGen.encodeValue(code, fv.getValue()); + if (fv.getValueType() == InitType.CONST) { + annotationGen.encodeValue(code, fv.getValue()); + } else if (fv.getValueType() == InitType.INSN) { + InsnGen insnGen = makeInsnGen(fv.getInsnMth()); + addInsnBody(insnGen, code, fv.getInsn()); + } } } code.add(';'); @@ -374,8 +381,7 @@ public class ClassGen { ConstructorInsn constrInsn = f.getConstrInsn(); if (constrInsn.getArgsCount() > f.getStartArg()) { if (igen == null) { - MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod()); - igen = new InsnGen(mthGen, false); + igen = makeInsnGen(enumFields.getStaticMethod()); } MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth()); igen.generateMethodArguments(code, constrInsn, f.getStartArg(), callMth); @@ -399,6 +405,19 @@ public class ClassGen { } } + private InsnGen makeInsnGen(MethodNode mth) { + MethodGen mthGen = new MethodGen(this, mth); + return new InsnGen(mthGen, false); + } + + private void addInsnBody(InsnGen insnGen, CodeWriter code, InsnNode insn) { + try { + insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP); + } catch (Exception e) { + ErrorsCounter.classError(cls, "Failed to generate init code", e); + } + } + public void useType(CodeWriter code, ArgType type) { PrimitiveType stype = type.getPrimitiveType(); if (stype == null) { diff --git a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java index 41b801794..5a5d2b1dc 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java @@ -15,7 +15,7 @@ import jadx.core.dex.nodes.IBlock; import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.InsnNode; -import jadx.core.dex.nodes.parser.FieldValueAttr; +import jadx.core.dex.nodes.parser.FieldInitAttr; import jadx.core.dex.regions.Region; import jadx.core.dex.regions.SwitchRegion; import jadx.core.dex.regions.SynchronizedRegion; @@ -249,7 +249,7 @@ public class RegionGen extends InsnGen { } else { staticField(code, fn.getFieldInfo()); // print original value, sometimes replace with incorrect field - FieldValueAttr valueAttr = fn.get(AType.FIELD_VALUE); + FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT); if (valueAttr != null && valueAttr.getValue() != null) { code.add(" /*").add(valueAttr.getValue().toString()).add("*/"); } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java index 9241d05d9..1ad75335e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java @@ -16,7 +16,7 @@ import jadx.core.dex.attributes.nodes.LoopLabelAttr; import jadx.core.dex.attributes.nodes.MethodInlineAttr; import jadx.core.dex.attributes.nodes.PhiListAttr; import jadx.core.dex.attributes.nodes.SourceFileAttr; -import jadx.core.dex.nodes.parser.FieldValueAttr; +import jadx.core.dex.nodes.parser.FieldInitAttr; import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.SplitterBlockAttr; @@ -37,7 +37,7 @@ public class AType { public static final AType CATCH_BLOCK = new AType(); public static final AType SPLITTER_BLOCK = new AType(); public static final AType FORCE_RETURN = new AType(); - public static final AType FIELD_VALUE = new AType(); + public static final AType FIELD_INIT = new AType(); public static final AType FIELD_REPLACE = new AType(); public static final AType JADX_ERROR = new AType(); public static final AType METHOD_INLINE = new AType(); diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/mods/TernaryInsn.java b/jadx-core/src/main/java/jadx/core/dex/instructions/mods/TernaryInsn.java index 1ee157519..c104e7b30 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/mods/TernaryInsn.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/mods/TernaryInsn.java @@ -9,7 +9,7 @@ import jadx.core.dex.regions.conditions.IfCondition; import jadx.core.utils.InsnUtils; import jadx.core.utils.Utils; -import java.util.List; +import java.util.Collection; public final class TernaryInsn extends InsnNode { @@ -54,7 +54,7 @@ public final class TernaryInsn extends InsnNode { } @Override - public void getRegisterArgs(List list) { + public void getRegisterArgs(Collection list) { super.getRegisterArgs(list); list.addAll(condition.getRegisterArgs()); } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index beb92d2a6..2e0517439 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -16,7 +16,8 @@ import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.nodes.parser.AnnotationsParser; -import jadx.core.dex.nodes.parser.FieldValueAttr; +import jadx.core.dex.nodes.parser.FieldInitAttr; +import jadx.core.dex.nodes.parser.FieldInitAttr.InitType; import jadx.core.dex.nodes.parser.SignatureParser; import jadx.core.dex.nodes.parser.StaticValuesParser; import jadx.core.utils.exceptions.DecodeException; @@ -155,25 +156,28 @@ public class ClassNode extends LineAttrNode implements ILoadable { private void loadStaticValues(ClassDef cls, List staticFields) throws DecodeException { for (FieldNode f : staticFields) { if (f.getAccessFlags().isFinal()) { - f.addAttr(new FieldValueAttr(null)); + f.addAttr(FieldInitAttr.NULL_VALUE); } } - int offset = cls.getStaticValuesOffset(); - if (offset != 0) { - StaticValuesParser parser = new StaticValuesParser(dex, dex.openSection(offset)); - int count = parser.processFields(staticFields); - constFields = new LinkedHashMap(count); - for (FieldNode f : staticFields) { - AccessInfo accFlags = f.getAccessFlags(); - if (accFlags.isStatic() && accFlags.isFinal()) { - FieldValueAttr fv = f.get(AType.FIELD_VALUE); - if (fv != null && fv.getValue() != null) { - if (accFlags.isPublic()) { - dex.getConstFields().put(fv.getValue(), f); - } - constFields.put(fv.getValue(), f); + if (offset == 0) { + return; + } + StaticValuesParser parser = new StaticValuesParser(dex, dex.openSection(offset)); + int count = parser.processFields(staticFields); + if (count == 0) { + return; + } + constFields = new LinkedHashMap(count); + for (FieldNode f : staticFields) { + AccessInfo accFlags = f.getAccessFlags(); + if (accFlags.isStatic() && accFlags.isFinal()) { + FieldInitAttr fv = f.get(AType.FIELD_INIT); + if (fv != null && fv.getValue() != null && fv.getValueType() == InitType.CONST) { + if (accFlags.isPublic()) { + dex.getConstFields().put(fv.getValue(), f); } + constFields.put(fv.getValue(), f); } } } @@ -442,6 +446,12 @@ public class ClassNode extends LineAttrNode implements ILoadable { && getDefaultConstructor() != null; } + @Nullable + public MethodNode getClassInitMth() { + return searchMethodByName("()V"); + } + + @Nullable public MethodNode getDefaultConstructor() { for (MethodNode mth : methods) { if (mth.isDefaultConstructor()) { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java index 46c3f4416..b3d938648 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java @@ -13,6 +13,7 @@ import jadx.core.utils.InsnUtils; import jadx.core.utils.Utils; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; @@ -157,12 +158,12 @@ public class InsnNode extends LineAttrNode { this.offset = offset; } - public void getRegisterArgs(List list) { + public void getRegisterArgs(Collection collection) { for (InsnArg arg : this.getArguments()) { if (arg.isRegister()) { - list.add((RegisterArg) arg); + collection.add((RegisterArg) arg); } else if (arg.isInsnWrap()) { - ((InsnWrapArg) arg).getWrapInsn().getRegisterArgs(list); + ((InsnWrapArg) arg).getWrapInsn().getRegisterArgs(collection); } } } @@ -235,8 +236,27 @@ public class InsnNode extends LineAttrNode { if (this == other) { return true; } - return insnType == other.insnType - && arguments.size() == other.arguments.size(); + if (insnType != other.insnType + || arguments.size() != other.arguments.size()) { + return false; + } + // check wrapped instructions + int size = arguments.size(); + for (int i = 0; i < size; i++) { + InsnArg arg = arguments.get(i); + InsnArg otherArg = other.arguments.get(i); + if (arg.isInsnWrap()) { + if (!otherArg.isInsnWrap()) { + return false; + } + InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn(); + InsnNode otherWrapInsn = ((InsnWrapArg) otherArg).getWrapInsn(); + if (!wrapInsn.isSame(otherWrapInsn)) { + return false; + } + } + } + return true; } protected T copyCommonParams(T copy) { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/FieldInitAttr.java b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/FieldInitAttr.java new file mode 100644 index 000000000..b46dd8bbd --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/FieldInitAttr.java @@ -0,0 +1,61 @@ +package jadx.core.dex.nodes.parser; + +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.IAttribute; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; + +public class FieldInitAttr implements IAttribute { + + public static FieldInitAttr NULL_VALUE = constValue(null); + + public enum InitType { + CONST, + INSN + + } + + private final Object value; + private final InitType valueType; + private final MethodNode insnMth; + + private FieldInitAttr(InitType valueType, Object value, MethodNode insnMth) { + this.value = value; + this.valueType = valueType; + this.insnMth = insnMth; + } + + public static FieldInitAttr constValue(Object value) { + return new FieldInitAttr(InitType.CONST, value, null); + } + + public static FieldInitAttr insnValue(MethodNode mth, InsnNode insn) { + return new FieldInitAttr(InitType.INSN, insn, mth); + } + + public Object getValue() { + return value; + } + + public InsnNode getInsn() { + return (InsnNode) value; + } + + public InitType getValueType() { + return valueType; + } + + public MethodNode getInsnMth() { + return insnMth; + } + + @Override + public AType getType() { + return AType.FIELD_INIT; + } + + @Override + public String toString() { + return "V=" + value; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/FieldValueAttr.java b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/FieldValueAttr.java deleted file mode 100644 index d61d9f626..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/FieldValueAttr.java +++ /dev/null @@ -1,27 +0,0 @@ -package jadx.core.dex.nodes.parser; - -import jadx.core.dex.attributes.AType; -import jadx.core.dex.attributes.IAttribute; - -public class FieldValueAttr implements IAttribute { - - private final Object value; - - public FieldValueAttr(Object value) { - this.value = value; - } - - @Override - public AType getType() { - return AType.FIELD_VALUE; - } - - public Object getValue() { - return value; - } - - @Override - public String toString() { - return "V=" + value; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/StaticValuesParser.java b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/StaticValuesParser.java index 5e5b19083..121163e1a 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/StaticValuesParser.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/StaticValuesParser.java @@ -19,7 +19,9 @@ public class StaticValuesParser extends EncValueParser { int count = Leb128.readUnsignedLeb128(in); for (int i = 0; i < count; i++) { Object value = parseValue(); - fields.get(i).addAttr(new FieldValueAttr(value)); + if (i < fields.size()) { + fields.get(i).addAttr(FieldInitAttr.constValue(value)); + } } return count; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java index 29bbe03ce..383dd98af 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java @@ -18,6 +18,7 @@ import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; +import jadx.core.utils.BlockUtils; import jadx.core.utils.InstructionRemover; import jadx.core.utils.exceptions.JadxException; @@ -45,8 +46,6 @@ public class ClassModifier extends AbstractVisitor { removeSyntheticMethods(cls); removeEmptyMethods(cls); - checkFieldsInit(cls); - markAnonymousClass(cls); return false; } @@ -182,55 +181,15 @@ public class ClassModifier extends AbstractVisitor { // remove public empty constructors if (af.isConstructor() - && af.isPublic() + && (af.isPublic() || af.isStatic()) && mth.getArguments(false).isEmpty() && !mth.contains(AType.JADX_ERROR)) { List bb = mth.getBasicBlocks(); - if (bb == null || bb.isEmpty() || allBlocksEmpty(bb)) { + if (bb == null || bb.isEmpty() || BlockUtils.isAllBlocksEmpty(bb)) { mth.add(AFlag.DONT_GENERATE); } } } } - private static boolean allBlocksEmpty(List blocks) { - for (BlockNode block : blocks) { - if (!block.getInstructions().isEmpty()) { - return false; - } - } - return true; - } - - private static void checkFieldsInit(ClassNode cls) { - MethodNode clinit = cls.searchMethodByName("()V"); - if (clinit == null - || !clinit.getAccessFlags().isStatic() - || clinit.isNoCode()) { - return; - } - - for (BlockNode block : clinit.getBasicBlocks()) { - for (InsnNode insn : block.getInstructions()) { - if (insn.getType() == InsnType.SPUT) { - processStaticFieldAssign(cls, (IndexInsnNode) insn); - } - } - } - } - - /** - * Remove field initialization if it assign in "" method - */ - private static void processStaticFieldAssign(ClassNode cls, IndexInsnNode insn) { - FieldInfo field = (FieldInfo) insn.getIndex(); - String thisClass = cls.getClassInfo().getFullName(); - if (field.getDeclClass().getFullName().equals(thisClass)) { - FieldNode fn = cls.searchField(field); - if (fn != null && fn.getAccessFlags().isFinal()) { - fn.remove(AType.FIELD_VALUE); - } - } - } - } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java new file mode 100644 index 000000000..0d293e346 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java @@ -0,0 +1,241 @@ +package jadx.core.dex.visitors; + +import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.info.AccessInfo; +import jadx.core.dex.info.FieldInfo; +import jadx.core.dex.instructions.IndexInsnNode; +import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.instructions.args.RegisterArg; +import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.parser.FieldInitAttr; +import jadx.core.utils.BlockUtils; +import jadx.core.utils.InstructionRemover; +import jadx.core.utils.exceptions.JadxException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@JadxVisitor( + name = "ExtractFieldInit", + desc = "Move duplicated field initialization from constructors", + runAfter = ModVisitor.class, + runBefore = ClassModifier.class +) +public class ExtractFieldInit extends AbstractVisitor { + + @Override + public boolean visit(ClassNode cls) throws JadxException { + if (cls.isEnum()) { + return false; + } + for (ClassNode inner : cls.getInnerClasses()) { + visit(inner); + } + checkStaticFieldsInit(cls); + moveStaticFieldsInit(cls); + moveCommonFieldsInit(cls); + return false; + } + + private static void checkStaticFieldsInit(ClassNode cls) { + MethodNode clinit = cls.getClassInitMth(); + if (clinit == null + || !clinit.getAccessFlags().isStatic() + || clinit.isNoCode()) { + return; + } + + for (BlockNode block : clinit.getBasicBlocks()) { + for (InsnNode insn : block.getInstructions()) { + if (insn.getType() == InsnType.SPUT) { + processStaticFieldAssign(cls, (IndexInsnNode) insn); + } + } + } + } + + /** + * Remove final field in place initialization if it assign in class init method + */ + private static void processStaticFieldAssign(ClassNode cls, IndexInsnNode insn) { + FieldInfo field = (FieldInfo) insn.getIndex(); + String thisClass = cls.getClassInfo().getFullName(); + if (field.getDeclClass().getFullName().equals(thisClass)) { + FieldNode fn = cls.searchField(field); + if (fn != null && fn.getAccessFlags().isFinal()) { + fn.remove(AType.FIELD_INIT); + } + } + } + + private static void moveStaticFieldsInit(ClassNode cls) { + MethodNode classInitMth = cls.getClassInitMth(); + if (classInitMth == null) { + return; + } + for (FieldNode field : cls.getFields()) { + if (field.contains(AFlag.DONT_GENERATE)) { + continue; + } + if (field.getAccessFlags().isStatic()) { + List initInsns = getFieldAssigns(classInitMth, field, InsnType.SPUT); + if (initInsns.size() == 1) { + InsnNode insn = initInsns.get(0); + if (checkInsn(insn)) { + InstructionRemover.remove(classInitMth, insn); + addFieldInitAttr(classInitMth, field, insn); + } + } + } + } + } + + private static class InitInfo { + private final MethodNode constrMth; + private final List putInsns = new ArrayList(); + + private InitInfo(MethodNode constrMth) { + this.constrMth = constrMth; + } + + public MethodNode getConstrMth() { + return constrMth; + } + + public List getPutInsns() { + return putInsns; + } + } + + private static void moveCommonFieldsInit(ClassNode cls) { + List constrList = getConstructorsList(cls); + if (constrList.isEmpty()) { + return; + } + List infoList = new ArrayList(constrList.size()); + for (MethodNode constrMth : constrList) { + if (constrMth.isNoCode() || constrMth.getBasicBlocks().isEmpty()) { + return; + } + InitInfo info = new InitInfo(constrMth); + infoList.add(info); + // TODO: check not only first block + BlockNode blockNode = constrMth.getBasicBlocks().get(0); + for (InsnNode insn : blockNode.getInstructions()) { + if (insn.getType() == InsnType.IPUT && checkInsn(insn)) { + info.getPutInsns().add(insn); + } else if (!info.getPutInsns().isEmpty()) { + break; + } + } + } + // compare collected instructions + InitInfo common = null; + for (InitInfo info : infoList) { + if (common == null) { + common = info; + } else if (!compareInsns(common.getPutInsns(), info.getPutInsns())) { + return; + } + } + if (common == null) { + return; + } + Set fields = new HashSet(); + for (InsnNode insn : common.getPutInsns()) { + FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex(); + FieldNode field = cls.dex().resolveField(fieldInfo); + if (field == null) { + return; + } + if (!fields.add(fieldInfo)) { + return; + } + } + // all checks passed + for (InitInfo info : infoList) { + for (InsnNode putInsn : info.getPutInsns()) { + InstructionRemover.remove(info.getConstrMth(), putInsn); + } + } + for (InsnNode insn : common.getPutInsns()) { + FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex(); + FieldNode field = cls.dex().resolveField(fieldInfo); + addFieldInitAttr(common.getConstrMth(), field, insn); + } + } + + private static boolean compareInsns(List base, List other) { + if (base.size() != other.size()) { + return false; + } + int count = base.size(); + for (int i = 0; i < count; i++) { + InsnNode baseInsn = base.get(i); + InsnNode otherInsn = other.get(i); + if (!baseInsn.isSame(otherInsn)) { + return false; + } + } + return true; + } + + private static boolean checkInsn(InsnNode insn) { + Set regs = new HashSet(); + insn.getRegisterArgs(regs); + if (regs.isEmpty()) { + return true; + } + for (RegisterArg reg : regs) { + if (!reg.isThis()) { + return false; + } + } + return true; + } + + private static List getConstructorsList(ClassNode cls) { + List list = new ArrayList(); + for (MethodNode mth : cls.getMethods()) { + AccessInfo accFlags = mth.getAccessFlags(); + if (!accFlags.isStatic() && accFlags.isConstructor()) { + list.add(mth); + if (BlockUtils.isAllBlocksEmpty(mth.getBasicBlocks())) { + return Collections.emptyList(); + } + } + } + return list; + } + + private static List getFieldAssigns(MethodNode mth, FieldNode field, InsnType putInsn) { + if (mth.isNoCode()) { + return Collections.emptyList(); + } + List assignInsns = new ArrayList(); + for (BlockNode block : mth.getBasicBlocks()) { + for (InsnNode insn : block.getInstructions()) { + if (insn.getType() == putInsn) { + FieldInfo putNode = (FieldInfo) ((IndexInsnNode) insn).getIndex(); + if (putNode.equals(field.getFieldInfo())) { + assignInsns.add(insn); + } + } + } + } + return assignInsns; + } + + private static void addFieldInitAttr(MethodNode classInitMth, FieldNode field, InsnNode insn) { + InsnNode assignInsn = InsnNode.wrapArg(insn.getArg(0)); + field.addAttr(FieldInitAttr.insnValue(classInitMth, assignInsn)); + } +} diff --git a/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java b/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java index 82b66fdbe..aecd7d51d 100644 --- a/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java @@ -531,4 +531,13 @@ public class BlockUtils { } return block; } + + public static boolean isAllBlocksEmpty(List blocks) { + for (BlockNode block : blocks) { + if (!block.getInstructions().isEmpty()) { + return false; + } + } + return true; + } } diff --git a/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java b/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java index 26937cb25..fa3705892 100644 --- a/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java @@ -9,7 +9,7 @@ import jadx.core.dex.instructions.InsnType; import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; -import jadx.core.dex.nodes.parser.FieldValueAttr; +import jadx.core.dex.nodes.parser.FieldInitAttr; import jadx.core.utils.exceptions.JadxRuntimeException; import org.jetbrains.annotations.Nullable; @@ -82,7 +82,7 @@ public class InsnUtils { FieldInfo f = (FieldInfo) ((IndexInsnNode) insn).getIndex(); FieldNode fieldNode = dex.resolveField(f); if (fieldNode != null) { - FieldValueAttr attr = fieldNode.get(AType.FIELD_VALUE); + FieldInitAttr attr = fieldNode.get(AType.FIELD_INIT); if (attr != null) { return attr.getValue(); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit.java new file mode 100644 index 000000000..8cca832b0 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit.java @@ -0,0 +1,49 @@ +package jadx.tests.integration.others; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.junit.Test; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; + +public class TestFieldInit extends IntegrationTest { + + public static class TestCls { + + public class A { + } + + private static List s = new ArrayList(); + + private A a = new A(); + private int i = 1 + Random.class.getSimpleName().length(); + private int n = 0; + + public TestCls(int z) { + this.n = z; + this.n = 0; + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("List s = new ArrayList")); + assertThat(code, containsOne("A a = new A();")); + assertThat(code, containsOne("int i = (Random.class.getSimpleName().length() + 1);")); + assertThat(code, containsOne("int n = 0;")); + assertThat(code, not(containsString("static {"))); + assertThat(code, containsOne("this.n = z;")); + assertThat(code, containsOne("this.n = 0;")); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit2.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit2.java new file mode 100644 index 000000000..26bec5eac --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit2.java @@ -0,0 +1,44 @@ +package jadx.tests.integration.others; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import org.junit.Test; + +import static jadx.tests.api.utils.JadxMatchers.containsLines; +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.junit.Assert.assertThat; + +public class TestFieldInit2 extends IntegrationTest { + + public static class TestCls { + + public interface BasicAbstract { + void doSomething(); + } + + private BasicAbstract x = new BasicAbstract() { + @Override + public void doSomething() { + y = 1; + } + }; + private int y = 0; + + public TestCls() { + } + + public TestCls(int z) { + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("x = new BasicAbstract() {")); + assertThat(code, containsOne("y = 0;")); + assertThat(code, containsLines(1, "public TestFieldInit2$TestCls(int z) {", "}")); + } +}