core: move field initialization from constructors if possible (#71)
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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("*/");
|
||||
}
|
||||
|
||||
@@ -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<T extends IAttribute> {
|
||||
public static final AType<CatchAttr> CATCH_BLOCK = new AType<CatchAttr>();
|
||||
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<SplitterBlockAttr>();
|
||||
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<ForceReturnAttr>();
|
||||
public static final AType<FieldValueAttr> FIELD_VALUE = new AType<FieldValueAttr>();
|
||||
public static final AType<FieldInitAttr> FIELD_INIT = new AType<FieldInitAttr>();
|
||||
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<FieldReplaceAttr>();
|
||||
public static final AType<JadxErrorAttr> JADX_ERROR = new AType<JadxErrorAttr>();
|
||||
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<MethodInlineAttr>();
|
||||
|
||||
@@ -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<RegisterArg> list) {
|
||||
public void getRegisterArgs(Collection<RegisterArg> list) {
|
||||
super.getRegisterArgs(list);
|
||||
list.addAll(condition.getRegisterArgs());
|
||||
}
|
||||
|
||||
@@ -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<FieldNode> 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<Object, FieldNode>(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<Object, FieldNode>(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("<clinit>()V");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MethodNode getDefaultConstructor() {
|
||||
for (MethodNode mth : methods) {
|
||||
if (mth.isDefaultConstructor()) {
|
||||
|
||||
@@ -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<RegisterArg> list) {
|
||||
public void getRegisterArgs(Collection<RegisterArg> 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 extends InsnNode> T copyCommonParams(T copy) {
|
||||
|
||||
@@ -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<FieldInitAttr> getType() {
|
||||
return AType.FIELD_INIT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "V=" + value;
|
||||
}
|
||||
}
|
||||
@@ -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<FieldValueAttr> getType() {
|
||||
return AType.FIELD_VALUE;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "V=" + value;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<BlockNode> 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<BlockNode> blocks) {
|
||||
for (BlockNode block : blocks) {
|
||||
if (!block.getInstructions().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void checkFieldsInit(ClassNode cls) {
|
||||
MethodNode clinit = cls.searchMethodByName("<clinit>()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 "<clinit>" 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<InsnNode> 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<InsnNode> putInsns = new ArrayList<InsnNode>();
|
||||
|
||||
private InitInfo(MethodNode constrMth) {
|
||||
this.constrMth = constrMth;
|
||||
}
|
||||
|
||||
public MethodNode getConstrMth() {
|
||||
return constrMth;
|
||||
}
|
||||
|
||||
public List<InsnNode> getPutInsns() {
|
||||
return putInsns;
|
||||
}
|
||||
}
|
||||
|
||||
private static void moveCommonFieldsInit(ClassNode cls) {
|
||||
List<MethodNode> constrList = getConstructorsList(cls);
|
||||
if (constrList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<InitInfo> infoList = new ArrayList<InitInfo>(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<FieldInfo> fields = new HashSet<FieldInfo>();
|
||||
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<InsnNode> base, List<InsnNode> 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<RegisterArg> regs = new HashSet<RegisterArg>();
|
||||
insn.getRegisterArgs(regs);
|
||||
if (regs.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
for (RegisterArg reg : regs) {
|
||||
if (!reg.isThis()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<MethodNode> getConstructorsList(ClassNode cls) {
|
||||
List<MethodNode> list = new ArrayList<MethodNode>();
|
||||
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<InsnNode> getFieldAssigns(MethodNode mth, FieldNode field, InsnType putInsn) {
|
||||
if (mth.isNoCode()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<InsnNode> assignInsns = new ArrayList<InsnNode>();
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -531,4 +531,13 @@ public class BlockUtils {
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
public static boolean isAllBlocksEmpty(List<BlockNode> blocks) {
|
||||
for (BlockNode block : blocks) {
|
||||
if (!block.getInstructions().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<String> s = new ArrayList<String>();
|
||||
|
||||
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<String> 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;"));
|
||||
}
|
||||
}
|
||||
@@ -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) {", "}"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user