core: use SSA representation for instruction arguments

This commit is contained in:
Skylot
2014-04-26 17:41:12 +04:00
parent 96db1c2479
commit e49ba61917
80 changed files with 1796 additions and 951 deletions
@@ -181,7 +181,7 @@ public final class Decompiler {
}
void processClass(ClassNode cls) {
LOG.info("processing class {} ...", cls);
LOG.debug("processing class {} ...", cls);
ProcessClass.process(cls, passes);
}
+9 -5
View File
@@ -13,13 +13,15 @@ import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
import jadx.core.dex.visitors.regions.ProcessVariables;
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.typeresolver.FinishTypeResolver;
import jadx.core.dex.visitors.typeresolver.TypeResolver;
import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
import jadx.core.dex.visitors.typeinference.TypeInference;
import jadx.core.utils.Utils;
import java.io.File;
@@ -51,15 +53,17 @@ public class Jadx {
passes.add(new FallbackModeVisitor());
} else {
passes.add(new BlockMakerVisitor());
passes.add(new SSATransform());
passes.add(new TypeInference());
passes.add(new TypeResolver());
passes.add(new ConstInlinerVisitor());
passes.add(new FinishTypeInference());
if (args.isRawCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, false, true));
}
passes.add(new ConstInlinerVisitor());
passes.add(new FinishTypeResolver());
passes.add(new EliminatePhiNodes());
passes.add(new ModVisitor());
passes.add(new EnumVisitor());
@@ -102,9 +102,7 @@ public class InsnGen {
if (f.isStatic()) {
code.add(staticField(f.getField()));
} else {
RegisterArg regArg = new RegisterArg(f.getRegNum());
regArg.replaceTypedVar(f);
instanceField(code, f.getField(), regArg);
instanceField(code, f.getField(), f.getRegisterArg());
}
} else {
throw new CodegenException("Unknown arg type " + arg);
@@ -244,7 +242,7 @@ public class InsnGen {
break;
case ARITH_ONEARG:
makeArithOneArg((ArithNode) insn, code, state);
makeArithOneArg((ArithNode) insn, code);
break;
case NEG: {
@@ -309,7 +307,7 @@ public class InsnGen {
break;
}
case CONSTRUCTOR:
makeConstructor((ConstructorInsn) insn, code, state);
makeConstructor((ConstructorInsn) insn, code);
break;
case INVOKE:
@@ -427,6 +425,9 @@ public class InsnGen {
addArg(code, insn.getArg(0));
break;
case PHI:
break;
/* fallback mode instructions */
case IF:
assert isFallback() : "if insn in not fallback mode";
@@ -542,7 +543,7 @@ public class InsnGen {
code.add("new ").add(useType(elType)).add("[]{").add(str.toString()).add('}');
}
private void makeConstructor(ConstructorInsn insn, CodeWriter code, EnumSet<Flags> state)
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
throws CodegenException {
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous()) {
@@ -739,7 +740,7 @@ public class InsnGen {
}
}
private void makeArithOneArg(ArithNode insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException {
ArithOp op = insn.getOp();
InsnArg arg = insn.getArg(0);
// "++" or "--"
@@ -4,6 +4,7 @@ import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AttributesList;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.attributes.JadxErrorAttr;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.info.AccessInfo;
@@ -13,7 +14,6 @@ import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.Region;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.utils.ErrorsCounter;
@@ -164,7 +164,7 @@ public class MethodGen {
* variable type or name (if debug info available)
*/
public String makeArgName(RegisterArg arg) {
String name = arg.getTypedVar().getName();
String name = arg.getName();
String base = "r" + arg.getRegNum();
if (fallback) {
if (name != null) {
@@ -200,7 +200,7 @@ public class MethodGen {
return name;
}
name = getUniqVarName(name);
arg.getTypedVar().setName(name);
arg.getSVar().setName(name);
return name;
}
@@ -306,9 +306,9 @@ public class MethodGen {
}
try {
if (insnGen.makeInsn(insn, code)) {
CatchAttr catchAttr = (CatchAttr) attrs.get(AttributeType.CATCH_BLOCK);
if (catchAttr != null) {
code.add("\t //" + catchAttr);
List<IAttribute> catchAttrs = attrs.getAll(AttributeType.CATCH_BLOCK);
for (IAttribute catchAttr : catchAttrs) {
code.add("\t " + catchAttr);
}
}
} catch (CodegenException e) {
@@ -43,17 +43,19 @@ public class RegionGen extends InsnGen {
if (cont instanceof IBlock) {
makeSimpleBlock((IBlock) cont, code);
} else if (cont instanceof IRegion) {
declareVars(code, cont);
if (cont instanceof Region) {
makeSimpleRegion(code, (Region) cont);
} else if (cont instanceof IfRegion) {
makeIf((IfRegion) cont, code, true);
} else if (cont instanceof SwitchRegion) {
makeSwitch((SwitchRegion) cont, code);
} else if (cont instanceof LoopRegion) {
makeLoop((LoopRegion) cont, code);
} else if (cont instanceof SynchronizedRegion) {
makeSynchronizedRegion((SynchronizedRegion) cont, code);
} else {
declareVars(code, cont);
if (cont instanceof IfRegion) {
makeIf((IfRegion) cont, code, true);
} else if (cont instanceof SwitchRegion) {
makeSwitch((SwitchRegion) cont, code);
} else if (cont instanceof LoopRegion) {
makeLoop((LoopRegion) cont, code);
} else if (cont instanceof SynchronizedRegion) {
makeSynchronizedRegion((SynchronizedRegion) cont, code);
}
}
} else {
throw new CodegenException("Not processed container: " + cont);
@@ -77,6 +79,7 @@ public class RegionGen extends InsnGen {
if (tc != null) {
makeTryCatch(region, tc.getTryBlock(), code);
} else {
declareVars(code, region);
for (IContainer c : region.getSubBlocks()) {
makeRegion(code, c);
}
@@ -26,6 +26,8 @@ public enum AttributeType {
ANNOTATION_LIST(true),
ANNOTATION_MTH_PARAMETERS(true),
PHI_LIST(true),
SOURCE_FILE(true),
// for regions
@@ -118,15 +118,14 @@ public final class AttributesList {
int count = getMultiCountInternal(type);
if (count == 0) {
return Collections.emptyList();
} else {
List<IAttribute> attrs = new ArrayList<IAttribute>(count);
for (IAttribute attr : attributes) {
if (attr.getType() == type) {
attrs.add(attr);
}
}
return attrs;
}
List<IAttribute> attrs = new ArrayList<IAttribute>(count);
for (IAttribute attr : attributes) {
if (attr.getType() == type) {
attrs.add(attr);
}
}
return attrs;
}
public void remove(AttributeType type) {
@@ -1,56 +0,0 @@
package jadx.core.dex.attributes;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.TypedVar;
import jadx.core.dex.nodes.MethodNode;
public final class BlockRegState {
private final RegisterArg[] regs;
public BlockRegState(MethodNode mth) {
this.regs = new RegisterArg[mth.getRegsCount()];
for (int i = 0; i < regs.length; i++) {
regs[i] = new RegisterArg(i);
}
}
public BlockRegState(BlockRegState state) {
this.regs = new RegisterArg[state.regs.length];
System.arraycopy(state.regs, 0, regs, 0, state.regs.length);
}
public void assignReg(RegisterArg arg) {
regs[arg.getRegNum()] = arg;
arg.getTypedVar().getUseList().add(arg);
}
public void use(RegisterArg arg) {
RegisterArg reg = regs[arg.getRegNum()];
TypedVar regType = reg.getTypedVar();
if (regType == null) {
regType = new TypedVar(arg.getType());
reg.forceSetTypedVar(regType);
}
arg.replaceTypedVar(reg);
reg.getTypedVar().getUseList().add(arg);
}
public RegisterArg getRegister(int r) {
return regs[r];
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
for (RegisterArg reg : regs) {
if (reg.getTypedVar() != null) {
if (str.length() != 0) {
str.append(", ");
}
str.append(reg);
}
}
return str.toString();
}
}
@@ -0,0 +1,30 @@
package jadx.core.dex.attributes;
import jadx.core.dex.instructions.PhiInsn;
import java.util.LinkedList;
import java.util.List;
public class PhiListAttr implements IAttribute {
private final List<PhiInsn> list = new LinkedList<PhiInsn>();
@Override
public AttributeType getType() {
return AttributeType.PHI_LIST;
}
public List<PhiInsn> getList() {
return list;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("PHI: ");
for (PhiInsn phiInsn : list) {
sb.append('r').append(phiInsn.getResult().getRegNum()).append(" ");
}
return sb.toString();
}
}
@@ -29,7 +29,7 @@ public class InsnDecoder {
private final DexNode dex;
private DecodedInstruction[] insnArr;
public InsnDecoder(MethodNode mthNode) throws DecodeException {
public InsnDecoder(MethodNode mthNode) {
this.method = mthNode;
this.dex = method.dex();
}
@@ -58,6 +58,7 @@ public enum InsnType {
TERNARY,
ARGS, // just generate arguments
PHI,
NEW_MULTIDIM_ARRAY // TODO: now multidimensional arrays created using Array.newInstance function
}
@@ -0,0 +1,28 @@
package jadx.core.dex.instructions;
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.nodes.InsnNode;
import jadx.core.utils.Utils;
public class PhiInsn extends InsnNode {
public PhiInsn(int regNum, int predecessors) {
super(InsnType.PHI, predecessors);
setResult(InsnArg.reg(regNum, ArgType.UNKNOWN));
for (int i = 0; i < predecessors; i++) {
addReg(regNum, ArgType.UNKNOWN);
}
}
@Override
public RegisterArg getArg(int n) {
return (RegisterArg) super.getArg(n);
}
@Override
public String toString() {
return "PHI: " + getResult() + " = " + Utils.listToString(getArguments());
}
}
@@ -437,7 +437,7 @@ public abstract class ArgType {
}
public static ArgType merge(ArgType a, ArgType b) {
if (b == null || a == null) {
if (a == null || b == null) {
return null;
}
if (a.equals(b)) {
@@ -2,12 +2,16 @@ package jadx.core.dex.instructions.args;
import jadx.core.dex.info.FieldInfo;
// TODO: don't extend RegisterArg (now used as a result of instruction)
public final class FieldArg extends RegisterArg {
private final FieldInfo field;
// regArg equal 'null' for static fields
private final RegisterArg regArg;
public FieldArg(FieldInfo field, int regNum) {
super(regNum, field.getType());
public FieldArg(FieldInfo field, RegisterArg reg) {
super(-1);
this.regArg = reg;
this.field = field;
}
@@ -15,8 +19,12 @@ public final class FieldArg extends RegisterArg {
return field;
}
public RegisterArg getRegisterArg() {
return regArg;
}
public boolean isStatic() {
return regNum == -1;
return regArg == null;
}
@Override
@@ -29,6 +37,11 @@ public final class FieldArg extends RegisterArg {
return false;
}
@Override
public void setType(ArgType type) {
this.type = type;
}
@Override
public String toString() {
return "(" + field + ")";
@@ -1,27 +0,0 @@
package jadx.core.dex.instructions.args;
public class ImmutableTypedVar extends TypedVar {
public ImmutableTypedVar(ArgType type) {
super(type);
}
@Override
public boolean isImmutable() {
return true;
}
@Override
public void forceSetType(ArgType newType) {
}
@Override
public boolean merge(TypedVar typedVar) {
return false;
}
@Override
public boolean merge(ArgType type) {
return false;
}
}
@@ -26,10 +26,8 @@ public abstract class InsnArg extends Typed {
return reg(InsnUtils.getArg(insn, argNum), type);
}
public static RegisterArg immutableReg(int regNum, ArgType type) {
RegisterArg r = new RegisterArg(regNum);
r.forceSetTypedVar(new ImmutableTypedVar(type));
return r;
public static RegisterArg parameterReg(int regNum, ArgType type) {
return new MthParameterArg(regNum, type);
}
public static LiteralArg lit(long literal, ArgType type) {
@@ -101,11 +99,11 @@ public abstract class InsnArg extends Typed {
break;
case CONST_STR:
arg = wrap(insn);
arg.getTypedVar().forceSetType(ArgType.STRING);
arg.setType(ArgType.STRING);
break;
case CONST_CLASS:
arg = wrap(insn);
arg.getTypedVar().forceSetType(ArgType.CLASS);
arg.setType(ArgType.CLASS);
break;
default:
arg = wrap(insn);
@@ -8,7 +8,7 @@ public final class InsnWrapArg extends InsnArg {
public InsnWrapArg(InsnNode insn) {
RegisterArg result = insn.getResult();
this.typedVar = new TypedVar((result != null ? result.getType() : ArgType.VOID));
this.type = result != null ? result.getType() : ArgType.VOID;
this.wrappedInsn = insn;
}
@@ -29,6 +29,6 @@ public final class InsnWrapArg extends InsnArg {
@Override
public String toString() {
return "(wrap: " + typedVar + "\n " + wrappedInsn + ")";
return "(wrap: " + type + "\n " + wrappedInsn + ")";
}
}
@@ -24,7 +24,7 @@ public final class LiteralArg extends InsnArg {
}
}
this.literal = value;
this.typedVar = new TypedVar(type);
this.type = type;
}
public long getLiteral() {
@@ -37,7 +37,7 @@ public final class LiteralArg extends InsnArg {
}
public boolean isInteger() {
PrimitiveType type = typedVar.getType().getPrimitiveType();
PrimitiveType type = this.type.getPrimitiveType();
return (type == PrimitiveType.INT
|| type == PrimitiveType.BYTE
|| type == PrimitiveType.CHAR
@@ -69,10 +69,10 @@ public final class LiteralArg extends InsnArg {
if (getType().equals(ArgType.BOOLEAN) && (value.equals("true") || value.equals("false"))) {
return value;
}
return "(" + value + " " + typedVar + ")";
return "(" + value + " " + type + ")";
} catch (JadxRuntimeException ex) {
// can't convert literal to string
return "(" + literal + " " + typedVar + ")";
return "(" + literal + " " + type + ")";
}
}
}
@@ -0,0 +1,17 @@
package jadx.core.dex.instructions.args;
public class MthParameterArg extends RegisterArg {
public MthParameterArg(int rn, ArgType type) {
super(rn, type);
}
@Override
public boolean isTypeImmutable() {
return true;
}
@Override
public void setType(ArgType type) {
}
}
@@ -0,0 +1,8 @@
package jadx.core.dex.instructions.args;
public interface Named {
String getName();
void setName(String name);
}
@@ -1,12 +1,12 @@
package jadx.core.dex.instructions.args;
public final class NamedArg extends InsnArg {
public final class NamedArg extends InsnArg implements Named {
private String name;
public NamedArg(String name, ArgType type) {
this.name = name;
this.typedVar = new TypedVar(type);
this.type = type;
}
public String getName() {
@@ -24,6 +24,6 @@ public final class NamedArg extends InsnArg {
@Override
public String toString() {
return "(" + name + " " + typedVar + ")";
return "(" + name + " " + type + ")";
}
}
@@ -6,25 +6,32 @@ import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.PhiInsn;
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.utils.exceptions.JadxRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RegisterArg extends InsnArg {
public class RegisterArg extends InsnArg implements Named {
private static final Logger LOG = LoggerFactory.getLogger(RegisterArg.class);
protected final int regNum;
protected SSAVar sVar;
protected String name;
public RegisterArg(int rn) {
this.regNum = rn;
}
public RegisterArg(int rn, ArgType type) {
this.typedVar = new TypedVar(type);
this.type = type;
this.regNum = rn;
}
@@ -37,18 +44,33 @@ public class RegisterArg extends InsnArg {
return true;
}
public InsnNode getAssignInsn() {
for (InsnArg arg : getTypedVar().getUseList()) {
InsnNode assignInsn = arg.getParentInsn();
if (assignInsn == null) {
// assign as function argument
return null;
} else if (assignInsn.getResult() != null
&& assignInsn.getResult().getRegNum() == regNum) {
return assignInsn;
}
public SSAVar getSVar() {
return sVar;
}
void setSVar(SSAVar sVar) {
this.sVar = sVar;
}
@Override
public void setType(ArgType type) {
if (sVar != null) {
sVar.setType(type);
} else {
throw new JadxRuntimeException("SSA variable equals null");
}
return null;
}
public void forceType(ArgType type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String newName) {
this.name = newName;
}
/**
@@ -58,54 +80,81 @@ public class RegisterArg extends InsnArg {
*/
public Object getConstValue(DexNode dex) {
InsnNode parInsn = getAssignInsn();
if (parInsn != null) {
InsnType insnType = parInsn.getType();
switch (insnType) {
case CONST:
return parInsn.getArg(0);
case CONST_STR:
return ((ConstStringNode) parInsn).getString();
case CONST_CLASS:
return ((ConstClassNode) parInsn).getClsType();
case SGET:
FieldInfo f = (FieldInfo) ((IndexInsnNode) parInsn).getIndex();
FieldNode fieldNode = dex.resolveField(f);
if (fieldNode != null) {
FieldValueAttr attr = (FieldValueAttr) fieldNode.getAttributes().get(AttributeType.FIELD_VALUE);
if (attr != null) {
return attr.getValue();
}
} else {
LOG.warn("Field {} not found in dex {}", f, dex);
if (parInsn == null) {
return null;
}
InsnType insnType = parInsn.getType();
switch (insnType) {
case CONST:
return parInsn.getArg(0);
case CONST_STR:
return ((ConstStringNode) parInsn).getString();
case CONST_CLASS:
return ((ConstClassNode) parInsn).getClsType();
case SGET:
FieldInfo f = (FieldInfo) ((IndexInsnNode) parInsn).getIndex();
FieldNode fieldNode = dex.resolveField(f);
if (fieldNode != null) {
FieldValueAttr attr = (FieldValueAttr) fieldNode.getAttributes().get(AttributeType.FIELD_VALUE);
if (attr != null) {
return attr.getValue();
}
break;
}
} else {
LOG.warn("Field {} not found in dex {}", f, dex);
}
break;
}
return null;
}
@Override
public boolean isThis() {
if (isRegister()) {
String name = getTypedVar().getName();
if ("this".equals(name)) {
if ("this".equals(name)) {
return true;
}
// maybe it was moved from 'this' register
InsnNode ai = getAssignInsn();
if (ai != null && ai.getType() == InsnType.MOVE) {
InsnArg arg = ai.getArg(0);
if (arg != this
&& arg.isRegister()
&& "this".equals(((RegisterArg) arg).getName())) {
return true;
}
// maybe it was moved from 'this' register
InsnNode ai = getAssignInsn();
if (ai != null && ai.getType() == InsnType.MOVE) {
InsnArg arg = ai.getArg(0);
if (arg != this && "this".equals(arg.getTypedVar().getName())) {
return true;
}
}
}
return false;
}
public InsnNode getAssignInsn() {
RegisterArg assign = sVar.getAssign();
if (assign != null) {
return assign.getParentInsn();
}
return null;
}
public InsnNode getPhiAssignInsn() {
PhiInsn usePhi = sVar.getUsedInPhi();
if (usePhi != null) {
return usePhi;
}
RegisterArg assign = sVar.getAssign();
if (assign != null) {
InsnNode parent = assign.getParentInsn();
if (parent != null && parent.getType() == InsnType.PHI) {
return parent;
}
}
return null;
}
public boolean equalRegisterAndType(RegisterArg arg) {
return regNum == arg.regNum && type.equals(arg.type);
}
@Override
public int hashCode() {
return regNum * 31 + typedVar.hashCode();
return (regNum * 31 + type.hashCode()) * 31 + (sVar != null ? sVar.hashCode() : 0);
}
@Override
@@ -116,15 +165,36 @@ public class RegisterArg extends InsnArg {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
if (!(obj instanceof RegisterArg)) {
return false;
}
RegisterArg other = (RegisterArg) obj;
return regNum == other.regNum && typedVar.equals(other.typedVar);
if (regNum != other.regNum) {
return false;
}
if (!type.equals(other.type)) {
return false;
}
if (sVar != null && !sVar.equals(other.getSVar())) {
return false;
}
return true;
}
@Override
public String toString() {
return "(r" + regNum + " " + typedVar + ")";
StringBuilder sb=new StringBuilder();
sb.append("(r");
sb.append(regNum);
if(sVar!=null){
sb.append("_").append(sVar.getVersion());
}
if(name!=null){
sb.append(" '").append(name).append("'");
}
sb.append(" ");
sb.append(type);
sb.append(")");
return sb.toString();
}
}
@@ -0,0 +1,137 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.instructions.PhiInsn;
import java.util.ArrayList;
import java.util.List;
public class SSAVar {
private final int regNum;
private final int version;
private RegisterArg assign;
private final List<RegisterArg> useList = new ArrayList<RegisterArg>(2);
private PhiInsn usedInPhi;
private ArgType type;
public SSAVar(int regNum, int v, RegisterArg assign) {
this.regNum = regNum;
this.version = v;
this.assign = assign;
if (assign != null) {
mergeName(assign);
assign.setSVar(this);
}
}
public int getRegNum() {
return regNum;
}
public int getVersion() {
return version;
}
public RegisterArg getAssign() {
return assign;
}
public void setAssign(RegisterArg assign) {
this.assign = assign;
}
public List<RegisterArg> getUseList() {
return useList;
}
public void use(RegisterArg arg) {
mergeName(arg);
if (arg.getSVar() != null) {
arg.getSVar().removeUse(arg);
}
arg.setSVar(this);
useList.add(arg);
}
public void removeUse(RegisterArg arg) {
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) {
if (useList.get(i) == arg) {
useList.remove(i);
break;
}
}
}
public void setUsedInPhi(PhiInsn usedInPhi) {
this.usedInPhi = usedInPhi;
}
public PhiInsn getUsedInPhi() {
return usedInPhi;
}
public boolean isUsedInPhi() {
return usedInPhi != null;
}
public int getVariableUseCount() {
if (!isUsedInPhi()) {
return useList.size();
}
return useList.size() + usedInPhi.getResult().getSVar().getUseList().size();
}
public ArgType getType() {
return type;
}
public void setType(ArgType type) {
this.type = type;
if (assign != null) {
assign.type = type;
}
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) {
useList.get(i).type = type;
}
}
public void setName(String name) {
if (assign != null) {
assign.setName(name);
}
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) {
useList.get(i).setName(name);
}
}
public void mergeName(RegisterArg arg) {
if (arg.getName() != null) {
setName(arg.getName());
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SSAVar)) {
return false;
}
SSAVar ssaVar = (SSAVar) o;
return regNum == ssaVar.regNum && version == ssaVar.version;
}
@Override
public int hashCode() {
return 31 * regNum + version;
}
@Override
public String toString() {
return "r" + regNum + "_" + version;
}
}
@@ -1,71 +1,39 @@
package jadx.core.dex.instructions.args;
import java.util.List;
public abstract class Typed {
TypedVar typedVar;
public TypedVar getTypedVar() {
return typedVar;
}
protected ArgType type;
public ArgType getType() {
return typedVar.getType();
return type;
}
public boolean merge(Typed var) {
return typedVar.merge(var.getTypedVar());
public void setType(ArgType type) {
this.type = type;
}
public boolean merge(ArgType var) {
return typedVar.merge(var);
public boolean isTypeImmutable() {
return false;
}
public void forceSetTypedVar(TypedVar arg) {
this.typedVar = arg;
}
public void mergeDebugInfo(Typed arg) {
merge(arg);
mergeName(arg);
}
protected void mergeName(Typed arg) {
getTypedVar().mergeName(arg.getTypedVar());
}
public boolean replaceTypedVar(Typed var) {
TypedVar curVar = this.typedVar;
TypedVar newVar = var.typedVar;
if (curVar == newVar) {
return false;
public boolean merge(ArgType newType) {
ArgType m = ArgType.merge(type, newType);
if (m != null && !m.equals(type)) {
setType(m);
return true;
}
if (curVar != null) {
if (curVar.isImmutable()) {
moveInternals(newVar, curVar);
} else {
newVar.merge(curVar);
moveInternals(curVar, newVar);
this.typedVar = newVar;
}
} else {
this.typedVar = newVar;
}
return true;
return false;
}
private void moveInternals(TypedVar from, TypedVar to) {
List<InsnArg> curUseList = from.getUseList();
if (curUseList.size() != 0) {
for (InsnArg arg : curUseList) {
if (arg != this) {
arg.forceSetTypedVar(to);
}
}
to.getUseList().addAll(curUseList);
curUseList.clear();
public boolean merge(InsnArg arg) {
return merge(arg.getType());
}
public void mergeDebugInfo(RegisterArg arg) {
this.type = arg.getType();
if (this instanceof Named) {
Named n = (Named) this;
n.setName(arg.getName());
}
to.mergeName(from);
}
}
@@ -1,116 +0,0 @@
package jadx.core.dex.instructions.args;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class TypedVar {
private ArgType type;
private final List<InsnArg> useList = new ArrayList<InsnArg>(2);
private String name;
public TypedVar(ArgType initType) {
this.type = initType;
}
public ArgType getType() {
return type;
}
/**
* This method must be used very carefully
*/
public void forceSetType(ArgType newType) {
type = newType;
}
public boolean merge(TypedVar typedVar) {
return merge(typedVar.getType());
}
public boolean merge(ArgType mtype) {
ArgType res = ArgType.merge(type, mtype);
if (res != null && !type.equals(res)) {
this.type = res;
return true;
} else {
return false;
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<InsnArg> getUseList() {
return useList;
}
public void removeUse(InsnArg arg) {
Iterator<InsnArg> it = useList.iterator();
while (it.hasNext()) {
InsnArg use = it.next();
if (use == arg) {
it.remove();
}
}
}
public void mergeName(TypedVar arg) {
String argName = arg.getName();
if (argName != null) {
setName(argName);
} else if (getName() != null) {
arg.setName(getName());
}
}
public boolean isImmutable() {
return false;
}
@Override
public int hashCode() {
return type.hashCode() * 31 + (name == null ? 0 : name.hashCode());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof TypedVar)) {
return false;
}
TypedVar other = (TypedVar) obj;
if (!type.equals(other.type)) {
return false;
}
if (name == null) {
if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (name != null) {
sb.append('\'').append(name).append("' ");
}
sb.append(type);
return sb.toString();
}
}
@@ -26,7 +26,6 @@ public class ConstructorInsn extends InsnNode {
this.callMth = invoke.getCallMth();
ClassInfo classType = callMth.getDeclClass();
instanceArg = (RegisterArg) invoke.getArg(0);
instanceArg.setParentInsn(this);
if (instanceArg.isThis()) {
if (classType.equals(mth.getParentClass().getClassInfo())) {
@@ -42,7 +41,10 @@ public class ConstructorInsn extends InsnNode {
} else {
callType = CallType.CONSTRUCTOR;
setResult(instanceArg);
// convert from 'use' to 'assign'
instanceArg.getSVar().setAssign(instanceArg);
}
instanceArg.getSVar().removeUse(instanceArg);
for (int i = 1; i < invoke.getArgsCount(); i++) {
addArg(invoke.getArg(i));
}
@@ -3,13 +3,13 @@ package jadx.core.dex.nodes;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.BlockRegState;
import jadx.core.dex.attributes.LoopAttr;
import jadx.core.utils.InsnUtils;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class BlockNode extends AttrNode implements IBlock {
@@ -29,10 +29,7 @@ public class BlockNode extends AttrNode implements IBlock {
// immediate dominator
private BlockNode idom;
// blocks on which dominates this block
private final List<BlockNode> dominatesOn = new ArrayList<BlockNode>(1);
private BlockRegState startState;
private BlockRegState endState;
private List<BlockNode> dominatesOn = Collections.emptyList();
public BlockNode(int id, int offset) {
this.id = id;
@@ -146,20 +143,11 @@ public class BlockNode extends AttrNode implements IBlock {
return dominatesOn;
}
public BlockRegState getStartState() {
return startState;
}
public void setStartState(BlockRegState startState) {
this.startState = startState;
}
public BlockRegState getEndState() {
return endState;
}
public void setEndState(BlockRegState endState) {
this.endState = endState;
public void addDominatesOn(BlockNode block) {
if (dominatesOn.isEmpty()) {
dominatesOn = new LinkedList<BlockNode>();
}
dominatesOn.add(block);
}
public boolean isSynthetic() {
@@ -86,7 +86,10 @@ public class InsnNode extends LineAttrNode {
for (int i = 0; i < count; i++) {
InsnArg arg = arguments.get(i);
if (arg == from) {
// TODO correct remove from use list
if (arg.isRegister()) {
RegisterArg registerArg = (RegisterArg) arg;
registerArg.getSVar().removeUse(registerArg);
}
setArg(i, to);
return true;
}
@@ -15,6 +15,7 @@ import jadx.core.dex.instructions.SwitchNode;
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;
import jadx.core.dex.nodes.parser.DebugInfoParser;
import jadx.core.dex.nodes.parser.SignatureParser;
import jadx.core.dex.regions.Region;
@@ -55,6 +56,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
private ArgType retType;
private RegisterArg thisArg;
private List<RegisterArg> argsList;
private List<SSAVar> sVars = Collections.emptyList();
private Map<ArgType, List<ArgType>> genericMap;
private List<BlockNode> blocks;
@@ -196,8 +198,8 @@ public class MethodNode extends LineAttrNode implements ILoadable {
if (accFlags.isStatic()) {
thisArg = null;
} else {
thisArg = InsnArg.immutableReg(pos - 1, parentClass.getClassInfo().getType());
thisArg.getTypedVar().setName("this");
thisArg = InsnArg.parameterReg(pos - 1, parentClass.getClassInfo().getType());
thisArg.setName("this");
}
if (args.isEmpty()) {
argsList = Collections.emptyList();
@@ -205,7 +207,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
}
argsList = new ArrayList<RegisterArg>(args.size());
for (ArgType arg : args) {
argsList.add(InsnArg.immutableReg(pos, arg));
argsList.add(InsnArg.parameterReg(pos, arg));
pos += arg.getRegCount();
}
}
@@ -472,6 +474,24 @@ public class MethodNode extends LineAttrNode implements ILoadable {
return regsCount;
}
public SSAVar makeNewSVar(int regNum, int[] versions, RegisterArg arg) {
SSAVar var = new SSAVar(regNum, versions[regNum], arg);
versions[regNum]++;
if (sVars.isEmpty()) {
sVars = new ArrayList<SSAVar>();
}
sVars.add(var);
return var;
}
public void removeSVar(SSAVar var) {
sVars.remove(var);
}
public List<SSAVar> getSVars() {
return sVars;
}
public AccessInfo getAccessFlags() {
return accFlags;
}
@@ -63,7 +63,7 @@ public class DebugInfoParser {
int id = section.readUleb128() - 1;
if (id != DexNode.NO_INDEX) {
String name = dex.getString(id);
mthArgs.get(i).getTypedVar().setName(name);
mthArgs.get(i).setName(name);
}
}
@@ -2,7 +2,6 @@ package jadx.core.dex.nodes.parser;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.TypedVar;
import jadx.core.dex.nodes.DexNode;
import jadx.core.utils.InsnUtils;
@@ -19,7 +18,7 @@ final class LocalVar extends RegisterArg {
private int endAddr;
public LocalVar(DexNode dex, int rn, int nameId, int typeId, int signId) {
super(rn);
super(rn, ArgType.UNKNOWN);
String name = (nameId == DexNode.NO_INDEX ? null : dex.getString(nameId));
ArgType type = (typeId == DexNode.NO_INDEX ? null : dex.getType(typeId));
String sign = (signId == DexNode.NO_INDEX ? null : dex.getString(signId));
@@ -28,8 +27,8 @@ final class LocalVar extends RegisterArg {
}
public LocalVar(RegisterArg arg) {
super(arg.getRegNum());
init(arg.getTypedVar().getName(), arg.getType(), null);
super(arg.getRegNum(), arg.getType());
init(arg.getName(), arg.getType(), null);
}
private void init(String name, ArgType type, String sign) {
@@ -43,9 +42,8 @@ final class LocalVar extends RegisterArg {
LOG.error("Can't parse signature for local variable: " + sign, e);
}
}
TypedVar tv = new TypedVar(type);
tv.setName(name);
forceSetTypedVar(tv);
setName(name);
forceType(type);
}
private boolean checkSignature(ArgType type, String sign, ArgType gType) {
@@ -184,8 +184,8 @@ public final class IfCondition {
if (a.isRegister()) {
list.add((RegisterArg) a);
}
InsnArg b = compare.getA();
if (a.isRegister()) {
InsnArg b = compare.getB();
if (b.isRegister()) {
list.add((RegisterArg) b);
}
} else {
@@ -79,7 +79,7 @@ public final class LoopRegion extends AbstractRegion {
return false;
} else {
RegisterArg res = insn.getResult();
if (res.getTypedVar().getUseList().size() > 2) {
if (res.getSVar().getUseList().size() > 1) {
return false;
}
boolean found = false;
@@ -99,10 +99,6 @@ public class TryCatchBlock {
return insns;
}
public int getInsnsCount() {
return insns.size();
}
public CatchAttr getCatchAttr() {
return attr;
}
@@ -119,7 +115,7 @@ public class TryCatchBlock {
List<InsnNode> finalBlockInsns = new ArrayList<InsnNode>(insns);
setFinalBlock(new InsnContainer(finalBlockInsns));
InstructionRemover.unbindInsnList(finalBlockInsns);
InstructionRemover.unbindInsnList(mth, finalBlockInsns);
// remove these instructions from other handlers
for (ExceptionHandler h : getHandlers()) {
@@ -52,13 +52,13 @@ public class BlockMakerVisitor extends AbstractVisitor {
return;
}
mth.initBasicBlocks();
makeBasicBlocks(mth);
splitBasicBlocks(mth);
processBlocksTree(mth);
BlockProcessingHelper.visit(mth);
mth.finishBasicBlocks();
}
private static void makeBasicBlocks(MethodNode mth) {
private static void splitBasicBlocks(MethodNode mth) {
nextBlockId = 0;
InsnNode prevInsn = null;
@@ -71,15 +71,13 @@ public class BlockMakerVisitor extends AbstractVisitor {
boolean startNew = false;
if (prevInsn != null) {
InsnType type = prevInsn.getType();
if (type == InsnType.RETURN
|| type == InsnType.GOTO
if (type == InsnType.GOTO
|| type == InsnType.THROW
|| SEPARATE_INSNS.contains(type)) {
if (type == InsnType.RETURN || type == InsnType.THROW) {
mth.addExitBlock(curBlock);
}
BlockNode block = startNewBlock(mth, insn.getOffset());
if (type == InsnType.MONITOR_ENTER || type == InsnType.MONITOR_EXIT) {
connect(curBlock, block);
@@ -87,38 +85,9 @@ public class BlockMakerVisitor extends AbstractVisitor {
curBlock = block;
startNew = true;
} else {
type = insn.getType();
startNew = SEPARATE_INSNS.contains(type);
List<IAttribute> pjumps = prevInsn.getAttributes().getAll(AttributeType.JUMP);
if (pjumps.size() > 0) {
for (IAttribute j : pjumps) {
JumpAttribute jump = (JumpAttribute) j;
if (jump.getSrc() == prevInsn.getOffset()) {
startNew = true;
}
}
}
List<IAttribute> cjumps = insn.getAttributes().getAll(AttributeType.JUMP);
if (cjumps.size() > 0) {
for (IAttribute j : cjumps) {
JumpAttribute jump = (JumpAttribute) j;
if (jump.getDest() == insn.getOffset()) {
startNew = true;
}
}
}
// split 'do-while' block (last instruction: 'if', target this block)
if (type == InsnType.IF) {
IfNode ifs = (IfNode) (insn);
BlockNode targBlock = blocksMap.get(ifs.getTarget());
if (targBlock == curBlock) {
startNew = true;
}
}
startNew = isSplitByJump(prevInsn, insn)
|| SEPARATE_INSNS.contains(insn.getType())
|| isDoWhile(blocksMap, curBlock, insn);
if (startNew) {
BlockNode block = startNewBlock(mth, insn.getOffset());
connect(curBlock, block);
@@ -126,7 +95,6 @@ public class BlockMakerVisitor extends AbstractVisitor {
}
}
}
// for try/catch make empty block for connect handlers
if (insn.getAttributes().contains(AttributeFlag.TRY_ENTER)) {
BlockNode block;
@@ -146,12 +114,14 @@ public class BlockMakerVisitor extends AbstractVisitor {
} else {
blocksMap.put(insn.getOffset(), curBlock);
}
curBlock.getInstructions().add(insn);
prevInsn = insn;
}
// setup missing connections
setupConnections(mth, blocksMap);
}
private static void setupConnections(MethodNode mth, Map<Integer, BlockNode> blocksMap) {
for (BlockNode block : mth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) {
List<IAttribute> jumps = insn.getAttributes().getAll(AttributeType.JUMP);
@@ -164,17 +134,15 @@ public class BlockMakerVisitor extends AbstractVisitor {
// connect exception handlers
CatchAttr catches = (CatchAttr) insn.getAttributes().get(AttributeType.CATCH_BLOCK);
if (catches != null) {
// get synthetic block for handlers
IAttribute spl = block.getAttributes().get(AttributeType.SPLITTER_BLOCK);
if (spl != null) {
BlockNode connBlock = ((SplitterBlockAttr) spl).getBlock();
for (ExceptionHandler h : catches.getTryBlock().getHandlers()) {
BlockNode destBlock = getBlock(h.getHandleOffset(), blocksMap);
// skip self loop in handler
if (connBlock != destBlock) {
connect(connBlock, destBlock);
}
// get synthetic block for handlers
IAttribute spl = block.getAttributes().get(AttributeType.SPLITTER_BLOCK);
if (catches != null && spl != null) {
BlockNode connBlock = ((SplitterBlockAttr) spl).getBlock();
for (ExceptionHandler h : catches.getTryBlock().getHandlers()) {
BlockNode destBlock = getBlock(h.getHandleOffset(), blocksMap);
// skip self loop in handler
if (connBlock != destBlock) {
connect(connBlock, destBlock);
}
}
}
@@ -182,6 +150,36 @@ public class BlockMakerVisitor extends AbstractVisitor {
}
}
private static boolean isSplitByJump(InsnNode prevInsn, InsnNode currentInsn) {
List<IAttribute> pJumps = prevInsn.getAttributes().getAll(AttributeType.JUMP);
for (IAttribute j : pJumps) {
JumpAttribute jump = (JumpAttribute) j;
if (jump.getSrc() == prevInsn.getOffset()) {
return true;
}
}
List<IAttribute> cJumps = currentInsn.getAttributes().getAll(AttributeType.JUMP);
for (IAttribute j : cJumps) {
JumpAttribute jump = (JumpAttribute) j;
if (jump.getDest() == currentInsn.getOffset()) {
return true;
}
}
return false;
}
private static boolean isDoWhile(Map<Integer, BlockNode> blocksMap, BlockNode curBlock, InsnNode insn) {
// split 'do-while' block (last instruction: 'if', target this block)
if (insn.getType() == InsnType.IF) {
IfNode ifs = (IfNode) (insn);
BlockNode targetBlock = blocksMap.get(ifs.getTarget());
if (targetBlock == curBlock) {
return true;
}
}
return false;
}
private static void processBlocksTree(MethodNode mth) {
computeDominators(mth);
markReturnBlocks(mth);
@@ -189,16 +187,16 @@ public class BlockMakerVisitor extends AbstractVisitor {
int i = 0;
while (modifyBlocksTree(mth)) {
// revert calculations
cleanDomTree(mth);
clearBlocksState(mth);
// recalculate dominators tree
computeDominators(mth);
markReturnBlocks(mth);
i++;
if (i > 100) {
if (i++ > 100) {
throw new AssertionError("Can't fix method cfg: " + mth);
}
}
computeDominanceFrontier(mth);
registerLoops(mth);
}
@@ -277,32 +275,26 @@ public class BlockMakerVisitor extends AbstractVisitor {
if (block == entryBlock) {
continue;
}
BlockNode idom;
List<BlockNode> preds = block.getPredecessors();
if (preds.size() == 1) {
block.setIDom(preds.get(0));
idom = preds.get(0);
} else {
BitSet bs = new BitSet(block.getDoms().length());
bs.or(block.getDoms());
for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
BlockNode dom = basicBlocks.get(i);
bs.andNot(dom.getDoms());
}
int c = bs.cardinality();
if (c == 1) {
int id = bs.nextSetBit(0);
BlockNode idom = basicBlocks.get(id);
block.setIDom(idom);
idom.getDominatesOn().add(block);
} else {
if (bs.cardinality() != 1) {
throw new JadxRuntimeException("Can't find immediate dominator for block " + block
+ " in " + bs + " preds:" + preds);
}
idom = basicBlocks.get(bs.nextSetBit(0));
}
block.setIDom(idom);
idom.addDominatesOn(block);
}
computeDominanceFrontier(mth);
}
private static void computeDominanceFrontier(MethodNode mth) {
@@ -310,18 +302,15 @@ public class BlockMakerVisitor extends AbstractVisitor {
exit.setDomFrontier(EMPTY_BITSET);
}
for (BlockNode block : mth.getBasicBlocks()) {
computeBlockDF(block);
computeBlockDF(mth, block);
}
}
private static void computeBlockDF(BlockNode block) {
if (block.getDomFrontier() != null) {
return;
private static void computeBlockDF(MethodNode mth, BlockNode block) {
for (BlockNode c : block.getDominatesOn()) {
computeBlockDF(mth, c);
}
BitSet domFrontier = null;
BitSet doms = block.getDoms();
int id = block.getId();
for (BlockNode s : block.getSuccessors()) {
if (s.getIDom() != block) {
if (domFrontier == null) {
@@ -330,20 +319,14 @@ public class BlockMakerVisitor extends AbstractVisitor {
domFrontier.set(s.getId());
}
}
for (BlockNode node : block.getDominatesOn()) {
if (node.getIDom() == block) {
BitSet frontier = node.getDomFrontier();
if (frontier == null) {
computeBlockDF(node);
frontier = node.getDomFrontier();
}
for (int w = frontier.nextSetBit(0); w >= 0; w = frontier.nextSetBit(w + 1)) {
if (id == w || !doms.get(w)) {
if (domFrontier == null) {
domFrontier = new BitSet();
}
domFrontier.set(w);
for (BlockNode c : block.getDominatesOn()) {
BitSet frontier = c.getDomFrontier();
for (int p = frontier.nextSetBit(0); p >= 0; p = frontier.nextSetBit(p + 1)) {
if (mth.getBasicBlocks().get(p).getIDom() != block) {
if (domFrontier == null) {
domFrontier = new BitSet();
}
domFrontier.set(p);
}
}
}
@@ -465,28 +448,26 @@ public class BlockMakerVisitor extends AbstractVisitor {
if (mth.getExitBlocks().size() == 1 || !mth.getReturnType().equals(ArgType.VOID)) {
return false;
}
boolean merge = false;
for (BlockNode exitBlock : mth.getExitBlocks()) {
List<BlockNode> preds = exitBlock.getPredecessors();
if (preds.size() == 1) {
BlockNode pred = preds.get(0);
for (BlockNode otherExitBlock : mth.getExitBlocks()) {
if (exitBlock != otherExitBlock
&& otherExitBlock.isDominator(pred)
&& otherExitBlock.getPredecessors().size() == 1) {
// merge
BlockNode otherPred = otherExitBlock.getPredecessors().get(0);
removeConnection(otherPred, otherExitBlock);
connect(otherPred, exitBlock);
merge = true;
}
if (preds.size() != 1) {
continue;
}
BlockNode pred = preds.get(0);
for (BlockNode otherExitBlock : mth.getExitBlocks()) {
if (exitBlock != otherExitBlock
&& otherExitBlock.isDominator(pred)
&& otherExitBlock.getPredecessors().size() == 1) {
// merge
BlockNode otherPred = otherExitBlock.getPredecessors().get(0);
removeConnection(otherPred, otherExitBlock);
connect(otherPred, exitBlock);
cleanExitNodes(mth);
return true;
}
}
}
if (merge) {
cleanExitNodes(mth);
}
return merge;
return false;
}
/**
@@ -545,7 +526,7 @@ public class BlockMakerVisitor extends AbstractVisitor {
private static InsnNode duplicateReturnInsn(InsnNode returnInsn) {
InsnNode insn = new InsnNode(returnInsn.getType(), returnInsn.getArgsCount());
if (returnInsn.getArgsCount() != 0) {
if (returnInsn.getArgsCount() == 1) {
RegisterArg arg = (RegisterArg) returnInsn.getArg(0);
insn.addArg(InsnArg.reg(arg.getRegNum(), arg.getType()));
}
@@ -554,7 +535,7 @@ public class BlockMakerVisitor extends AbstractVisitor {
return insn;
}
private static void cleanDomTree(MethodNode mth) {
private static void clearBlocksState(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
AttributesList attrs = block.getAttributes();
attrs.remove(AttributeType.LOOP);
@@ -563,6 +544,7 @@ public class BlockMakerVisitor extends AbstractVisitor {
block.setDoms(null);
block.setIDom(null);
block.setDomFrontier(null);
block.getDominatesOn().clear();
}
}
@@ -60,8 +60,8 @@ public class BlockProcessingHelper {
type = excHandler.getCatchType().getType();
excArg.setName("e");
}
resArg.getTypedVar().forceSetType(type);
excArg.getTypedVar().forceSetType(type);
resArg.forceType(type);
excArg.setType(type);
excHandler.setArg(excArg);
block.getAttributes().add(handlerAttr);
@@ -79,7 +79,7 @@ public class BlockProcessingHelper {
}
for (BlockNode excBlock : excHandler.getBlocks()) {
// remove 'monitor-exit' from exception handler blocks
InstructionRemover remover = new InstructionRemover(excBlock.getInstructions());
InstructionRemover remover = new InstructionRemover(mth, excBlock);
for (InsnNode insn : excBlock.getInstructions()) {
if (insn.getType() == InsnType.MONITOR_ENTER) {
break;
@@ -99,10 +99,10 @@ public class ClassModifier extends AbstractVisitor {
return false;
}
mth.removeFirstArgument();
InstructionRemover.remove(block, insn);
InstructionRemover.remove(mth, block, insn);
// other arg usage -> wrap with IGET insn
List<InsnArg> useList = arg.getTypedVar().getUseList();
if (useList.size() > 1) {
List<RegisterArg> useList = arg.getSVar().getUseList();
if (useList.size() > 0) {
InsnNode iget = new IndexInsnNode(InsnType.IGET, fieldInfo, 1);
iget.addArg(insn.getArg(1));
for (InsnArg insnArg : useList) {
@@ -5,6 +5,7 @@ import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode;
@@ -15,6 +16,7 @@ import jadx.core.utils.InsnList;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
@@ -102,21 +104,26 @@ public class CodeShrinker extends AbstractVisitor {
if (start > to) {
throw new JadxRuntimeException("Invalid inline insn positions: " + start + " - " + to);
}
BitSet args = new BitSet();
for (RegisterArg arg : movedArgs) {
args.set(arg.getRegNum());
}
for (int i = start; i < to; i++) {
ArgsInfo argsInfo = argsList.get(i);
if (argsInfo.getInlinedInsn() == this) {
continue;
}
InsnNode curInsn = argsInfo.insn;
if (!curInsn.canReorder() || usedArgAssign(curInsn, movedArgs)) {
if (!curInsn.canReorder() || usedArgAssign(curInsn, args)) {
return false;
}
}
return true;
}
private static boolean usedArgAssign(InsnNode insn, List<RegisterArg> args) {
return insn.getResult() != null && args.contains(insn.getResult());
private static boolean usedArgAssign(InsnNode insn, BitSet args) {
RegisterArg result = insn.getResult();
return result != null && args.get(result.getRegNum());
}
public WrapInfo inline(int assignInsnPos, RegisterArg arg) {
@@ -181,14 +188,15 @@ public class CodeShrinker extends AbstractVisitor {
List<RegisterArg> args = argsInfo.getArgs();
for (ListIterator<RegisterArg> it = args.listIterator(args.size()); it.hasPrevious(); ) {
RegisterArg arg = it.previous();
List<InsnArg> useList = arg.getTypedVar().getUseList();
if (useList.size() != 2) {
// if (arg.getName() != null) {
// continue;
// }
SSAVar sVar = arg.getSVar();
if (sVar.getAssign() == null || sVar.getVariableUseCount() != 1) {
continue;
}
InsnNode assignInsn = selectOther(useList, arg).getParentInsn();
if (assignInsn == null
|| assignInsn.getResult() == null
|| assignInsn.getResult().getRegNum() != arg.getRegNum()) {
InsnNode assignInsn = sVar.getAssign().getParentInsn();
if (assignInsn == null) {
continue;
}
int assignPos = insnList.getIndex(assignInsn);
@@ -228,7 +236,11 @@ public class CodeShrinker extends AbstractVisitor {
return false;
}
List<RegisterArg> args = ArgsInfo.getArgs(assignInsn);
List<RegisterArg> argsList = ArgsInfo.getArgs(assignInsn);
BitSet args = new BitSet();
for (RegisterArg arg : argsList) {
args.set(arg.getRegNum());
}
boolean startCheck = false;
for (InsnNode insn : assignBlock.getInstructions()) {
if (startCheck && (!insn.canReorder() || ArgsInfo.usedArgAssign(insn, args))) {
@@ -290,9 +302,4 @@ public class CodeShrinker extends AbstractVisitor {
return arg.wrapInstruction(assignInsn);
}
private static InsnArg selectOther(List<InsnArg> list, RegisterArg insn) {
InsnArg first = list.get(0);
return insn == first ? list.get(1) : first;
}
}
@@ -7,15 +7,15 @@ import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
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;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class ConstInlinerVisitor extends AbstractVisitor {
@@ -25,7 +25,7 @@ public class ConstInlinerVisitor extends AbstractVisitor {
return;
}
for (BlockNode block : mth.getBasicBlocks()) {
InstructionRemover remover = new InstructionRemover(block.getInstructions());
InstructionRemover remover = new InstructionRemover(mth, block);
for (InsnNode insn : block.getInstructions()) {
if (checkInsn(mth, block, insn)) {
remover.add(insn);
@@ -45,63 +45,42 @@ public class ConstInlinerVisitor extends AbstractVisitor {
arg.merge(resType);
}
long lit = ((LiteralArg) arg).getLiteral();
return replaceConst(mth, block, insn, lit);
return replaceConst(mth, insn, lit);
}
}
return false;
}
private static boolean replaceConst(MethodNode mth, BlockNode block, InsnNode insn, long literal) {
List<InsnArg> use = insn.getResult().getTypedVar().getUseList();
private static boolean replaceConst(MethodNode mth, InsnNode insn, long literal) {
List<RegisterArg> use = new ArrayList<RegisterArg>(insn.getResult().getSVar().getUseList());
int replaceCount = 0;
int assignCount = 0;
for (InsnArg arg : use) {
for (RegisterArg arg : use) {
// if (arg.getSVar().isUsedInPhi()) {
// continue;
// }
InsnNode useInsn = arg.getParentInsn();
if (arg == insn.getResult() || useInsn == null) {
assignCount++;
if (useInsn.getType() == InsnType.PHI) {
continue;
}
BlockNode useBlock = BlockUtils.getBlockByInsn(mth, useInsn);
boolean isDominator = useBlock == block || useBlock.isDominator(block);
if (isDominator && !registerReassignOnPath(block, useBlock, insn)) {
LiteralArg litArg;
if (use.size() == 2) {
// arg used only in one place
litArg = InsnArg.lit(literal, arg.getType());
} else {
// in most cases type not equal arg.getType()
// just set unknown type and run type fixer
litArg = InsnArg.lit(literal, ArgType.UNKNOWN);
}
if (useInsn.replaceArg(arg, litArg)) {
fixTypes(mth, useInsn, litArg);
replaceCount++;
}
LiteralArg litArg;
if (use.size() == 1 || arg.isTypeImmutable()) {
// arg used only in one place
litArg = InsnArg.lit(literal, arg.getType());
} else {
// in most cases type not equal arg.getType()
// just set unknown type and run type fixer
litArg = InsnArg.lit(literal, ArgType.UNKNOWN);
}
if (useInsn.replaceArg(arg, litArg)) {
fixTypes(mth, useInsn, litArg);
replaceCount++;
}
}
return replaceCount == use.size() - assignCount;
}
private static boolean registerReassignOnPath(BlockNode block, BlockNode useBlock, InsnNode assignInsn) {
if (block == useBlock) {
return false;
}
Set<BlockNode> blocks = BlockUtils.getAllPathsBlocks(block, useBlock);
int regNum = assignInsn.getResult().getRegNum();
for (BlockNode b : blocks) {
for (InsnNode insn : b.getInstructions()) {
if (insn.getResult() != null
&& insn != assignInsn
&& insn.getResult().getRegNum() == regNum) {
return true;
}
}
}
return false;
return replaceCount == use.size();
}
/**
* This is method similar to PostTypeResolver.visit method,
* This is method similar to PostTypeInference.visit method,
* but contains some expensive operations needed only after constant inline
*/
private static void fixTypes(MethodNode mth, InsnNode insn, LiteralArg litArg) {
@@ -24,7 +24,7 @@ import java.util.Set;
public class DotGraphVisitor extends AbstractVisitor {
private static final String NL = "\\l";
private static final boolean PRINT_DOMINATORS = false;
private static final boolean PRINT_DOMINATORS = true;
private final File dir;
private final boolean useRegions;
@@ -49,8 +49,8 @@ public class DotGraphVisitor extends AbstractVisitor {
}
private class DumpDotGraph {
private CodeWriter dot = new CodeWriter();
private CodeWriter conn = new CodeWriter();
private final CodeWriter dot = new CodeWriter();
private final CodeWriter conn = new CodeWriter();
public void process(MethodNode mth) {
dot.startLine("digraph \"CFG for");
@@ -168,16 +168,21 @@ public class DotGraphVisitor extends AbstractVisitor {
}
if (PRINT_DOMINATORS) {
for (BlockNode dom : BlockUtils.bitSetToBlocks(mth, block.getDoms())) {
String style = "[color=green]";
if (dom == block.getIDom()) {
style = "[style=dashed, color=green]";
}
addEdge(block, dom, style);
for (BlockNode c : block.getDominatesOn()) {
conn.startLine(block.getId() + " -> " + c.getId() + "[color=green];");
//
}
// for (BlockNode dom : BlockUtils.bitSetToBlocks(mth, block.getDoms())) {
// if (dom == block.getIDom()) {
// conn.startLine(dom.getId() + " -> " + block.getId() + "[style=dashed, color=green];");
//// addEdge(block, dom, "[style=dashed, color=green]");
// } else {
//// addEdge(block, dom, "[color=green]");
// }
// }
for (BlockNode dom : BlockUtils.bitSetToBlocks(mth, block.getDomFrontier())) {
addEdge(block, dom, "[color=blue]");
conn.startLine("f_" + block.getId() + " -> f_" + dom.getId() + "[color=blue];");
// addEdge(block, dom, "[color=blue]");
}
}
}
@@ -54,7 +54,7 @@ public class ModVisitor extends AbstractVisitor {
private static void replaceStep(MethodNode mth) {
ClassNode parentClass = mth.getParentClass();
for (BlockNode block : mth.getBasicBlocks()) {
InstructionRemover remover = new InstructionRemover(block.getInstructions());
InstructionRemover remover = new InstructionRemover(mth, block);
int size = block.getInstructions().size();
for (int i = 0; i < size; i++) {
@@ -65,6 +65,7 @@ public class ModVisitor extends AbstractVisitor {
MethodInfo callMth = inv.getCallMth();
if (callMth.isConstructor()) {
ConstructorInsn co = new ConstructorInsn(mth, inv);
removeInsnForArg(remover, co.getInstanceArg());
boolean remove = false;
if (co.isSuper() && (co.getArgsCount() == 0 || parentClass.isEnum())) {
remove = true;
@@ -149,7 +150,7 @@ public class ModVisitor extends AbstractVisitor {
*/
private static void removeStep(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
InstructionRemover remover = new InstructionRemover(block.getInstructions());
InstructionRemover remover = new InstructionRemover(mth, block);
int size = block.getInstructions().size();
for (int i = 0; i < size; i++) {
@@ -211,7 +212,7 @@ public class ModVisitor extends AbstractVisitor {
&& size > 0
&& insns.get(size - 1).getType() == InsnType.THROW) {
InstructionRemover.remove(excBlock, size - 1);
InstructionRemover.remove(mth, excBlock, size - 1);
// move not removed instructions to 'finally' block
if (insns.size() != 0) {
@@ -228,8 +229,8 @@ public class ModVisitor extends AbstractVisitor {
if (blockInsns.size() > 0) {
InsnNode insn = blockInsns.get(0);
if (insn.getType() == InsnType.MOVE_EXCEPTION
&& insn.getResult().getTypedVar().getUseList().size() <= 1) {
InstructionRemover.remove(block, 0);
&& insn.getResult().getSVar().getUseList().isEmpty()) {
InstructionRemover.remove(mth, block, 0);
}
}
@@ -252,12 +253,22 @@ public class ModVisitor extends AbstractVisitor {
block.getInstructions().set(i, insn);
}
/**
* In argument not used in other instructions then remove assign instruction.
*/
private static void removeInsnForArg(InstructionRemover remover, RegisterArg arg) {
if (arg.getSVar().getUseList().isEmpty()
&& arg.getAssignInsn() != null) {
remover.add(arg.getAssignInsn());
}
}
private static void checkArgsNames(MethodNode mth) {
for (RegisterArg arg : mth.getArguments(false)) {
String name = arg.getTypedVar().getName();
String name = arg.getName();
if (name != null && NameMapper.isReserved(name)) {
name = name + "_";
arg.getTypedVar().setName(name);
arg.getSVar().setName(name);
}
}
}
@@ -64,10 +64,10 @@ public class PrepareForCodeGen extends AbstractVisitor {
InsnNode insn = list.get(i);
// replace 'move' with inner wrapped instruction
if (insn.getType() == InsnType.MOVE
&& insn.getArg(0).isInsnWrap()
&& !insn.getAttributes().contains(AttributeFlag.DECLARE_VAR)) {
&& insn.getArg(0).isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) insn.getArg(0)).getWrapInsn();
wrapInsn.setResult(insn.getResult());
wrapInsn.getAttributes().addAll(insn.getAttributes());
list.set(i, wrapInsn);
}
}
@@ -115,14 +115,23 @@ public class PrepareForCodeGen extends AbstractVisitor {
List<InsnNode> list = block.getInstructions();
for (int i = 0; i < list.size(); i++) {
InsnNode insn = list.get(i);
if (insn.getType() == InsnType.ARITH) {
ArithNode arith = (ArithNode) insn;
RegisterArg res = arith.getResult();
InsnArg arg = arith.getArg(0);
if (res.equals(arg)) {
ArithNode newArith = new ArithNode(arith.getOp(), res, arith.getArg(1));
list.set(i, newArith);
}
if (insn.getType() != InsnType.ARITH) {
continue;
}
ArithNode arith = (ArithNode) insn;
RegisterArg res = arith.getResult();
InsnArg arg = arith.getArg(0);
boolean replace = false;
if (res.equals(arg)) {
replace = true;
} else if (arg.isRegister()) {
RegisterArg regArg = (RegisterArg) arg;
replace = res.equalRegisterAndType(regArg);
}
if (replace) {
ArithNode newArith = new ArithNode(arith.getOp(), res, arith.getArg(1));
list.set(i, newArith);
}
}
}
@@ -58,123 +58,18 @@ public class SimplifyVisitor extends AbstractVisitor {
}
switch (insn.getType()) {
case ARITH:
ArithNode arith = (ArithNode) insn;
if (arith.getArgsCount() == 2) {
InsnArg litArg = null;
if (arith.getArg(1).isInsnWrap()) {
InsnNode wr = ((InsnWrapArg) arith.getArg(1)).getWrapInsn();
if (wr.getType() == InsnType.CONST) {
litArg = wr.getArg(0);
}
} else if (arith.getArg(1).isLiteral()) {
litArg = arith.getArg(1);
}
if (litArg != null) {
long lit = ((LiteralArg) litArg).getLiteral();
boolean invert = false;
if (arith.getOp() == ArithOp.ADD && lit < 0) {
invert = true;
}
// fix 'c + (-1)' => 'c - (1)'
if (invert) {
return new ArithNode(ArithOp.SUB,
arith.getResult(), insn.getArg(0),
InsnArg.lit(-lit, litArg.getType()));
}
}
}
break;
return simplifyArith(insn);
case IF:
// simplify 'cmp' instruction in if condition
IfNode ifb = (IfNode) insn;
InsnArg f = ifb.getArg(0);
if (f.isInsnWrap()) {
InsnNode wi = ((InsnWrapArg) f).getWrapInsn();
if (wi.getType() == InsnType.CMP_L || wi.getType() == InsnType.CMP_G) {
if (ifb.getArg(1).isLiteral()
&& ((LiteralArg) ifb.getArg(1)).getLiteral() == 0) {
ifb.changeCondition(ifb.getOp(), wi.getArg(0), wi.getArg(1));
} else {
LOG.warn("TODO: cmp" + ifb);
}
}
}
simplifyIf((IfNode) insn);
break;
case INVOKE:
MethodInfo callMth = ((InvokeNode) insn).getCallMth();
if (callMth.getDeclClass().getFullName().equals(Consts.CLASS_STRING_BUILDER)
&& callMth.getShortId().equals(Consts.MTH_TOSTRING_SIGNATURE)
&& insn.getArg(0).isInsnWrap()) {
try {
List<InsnNode> chain = flattenInsnChain(insn);
if (chain.size() > 1 && chain.get(0).getType() == InsnType.CONSTRUCTOR) {
ConstructorInsn constr = (ConstructorInsn) chain.get(0);
if (constr.getClassType().getFullName().equals(Consts.CLASS_STRING_BUILDER)
&& constr.getArgsCount() == 0) {
int len = chain.size();
InsnNode concatInsn = new InsnNode(InsnType.STR_CONCAT, len - 1);
for (int i = 1; i < len; i++) {
concatInsn.addArg(chain.get(i).getArg(1));
}
concatInsn.setResult(insn.getResult());
return concatInsn;
}
}
} catch (Throwable e) {
LOG.debug("Can't convert string concatenation: {} insn: {}", mth, insn, e);
}
}
break;
return convertInvoke(mth, insn);
case IPUT:
case SPUT:
// convert field arith operation to arith instruction
// (IPUT = ARITH (IGET, lit) -> ARITH (fieldArg <op>= lit))
InsnArg arg = insn.getArg(0);
if (arg.isInsnWrap()) {
InsnNode wrap = ((InsnWrapArg) arg).getWrapInsn();
InsnType wrapType = wrap.getType();
if ((wrapType == InsnType.ARITH || wrapType == InsnType.STR_CONCAT) && wrap.getArg(0).isInsnWrap()) {
InsnNode get = ((InsnWrapArg) wrap.getArg(0)).getWrapInsn();
InsnType getType = get.getType();
if (getType == InsnType.IGET || getType == InsnType.SGET) {
FieldInfo field = (FieldInfo) ((IndexInsnNode) insn).getIndex();
FieldInfo innerField = (FieldInfo) ((IndexInsnNode) get).getIndex();
if (field.equals(innerField)) {
try {
RegisterArg reg = null;
if (getType == InsnType.IGET) {
reg = ((RegisterArg) get.getArg(0));
}
RegisterArg fArg = new FieldArg(field, reg != null ? reg.getRegNum() : -1);
if (reg != null) {
fArg.replaceTypedVar(get.getArg(0));
}
if (wrapType == InsnType.ARITH) {
ArithNode ar = (ArithNode) wrap;
return new ArithNode(ar.getOp(), fArg, fArg, ar.getArg(1));
} else {
int argsCount = wrap.getArgsCount();
InsnNode concat = new InsnNode(InsnType.STR_CONCAT, argsCount - 1);
for (int i = 1; i < argsCount; i++) {
concat.addArg(wrap.getArg(i));
}
return new ArithNode(ArithOp.ADD, fArg, fArg, InsnArg.wrapArg(concat));
}
} catch (Throwable e) {
LOG.debug("Can't convert field arith insn: {}, mth: {}", insn, mth, e);
}
}
}
}
}
break;
return convertFieldArith(mth, insn);
case CHECK_CAST:
InsnArg castArg = insn.getArg(0);
@@ -193,6 +88,129 @@ public class SimplifyVisitor extends AbstractVisitor {
return null;
}
/**
* Simplify 'cmp' instruction in if condition
*/
private static void simplifyIf(IfNode insn) {
InsnArg f = insn.getArg(0);
if (f.isInsnWrap()) {
InsnNode wi = ((InsnWrapArg) f).getWrapInsn();
if (wi.getType() == InsnType.CMP_L || wi.getType() == InsnType.CMP_G) {
if (insn.getArg(1).isLiteral()
&& ((LiteralArg) insn.getArg(1)).getLiteral() == 0) {
insn.changeCondition(insn.getOp(), wi.getArg(0), wi.getArg(1));
} else {
LOG.warn("TODO: cmp" + insn);
}
}
}
}
private static InsnNode convertInvoke(MethodNode mth, InsnNode insn) {
MethodInfo callMth = ((InvokeNode) insn).getCallMth();
if (callMth.getDeclClass().getFullName().equals(Consts.CLASS_STRING_BUILDER)
&& callMth.getShortId().equals(Consts.MTH_TOSTRING_SIGNATURE)
&& insn.getArg(0).isInsnWrap()) {
try {
List<InsnNode> chain = flattenInsnChain(insn);
if (chain.size() > 1 && chain.get(0).getType() == InsnType.CONSTRUCTOR) {
ConstructorInsn constr = (ConstructorInsn) chain.get(0);
if (constr.getClassType().getFullName().equals(Consts.CLASS_STRING_BUILDER)
&& constr.getArgsCount() == 0) {
int len = chain.size();
InsnNode concatInsn = new InsnNode(InsnType.STR_CONCAT, len - 1);
for (int i = 1; i < len; i++) {
concatInsn.addArg(chain.get(i).getArg(1));
}
concatInsn.setResult(insn.getResult());
return concatInsn;
}
}
} catch (Throwable e) {
LOG.debug("Can't convert string concatenation: {} insn: {}", mth, insn, e);
}
}
return null;
}
private static InsnNode simplifyArith(InsnNode insn) {
ArithNode arith = (ArithNode) insn;
if (arith.getArgsCount() != 2) {
return null;
}
InsnArg litArg = null;
InsnArg secondArg = arith.getArg(1);
if (secondArg.isInsnWrap()) {
InsnNode wr = ((InsnWrapArg) secondArg).getWrapInsn();
if (wr.getType() == InsnType.CONST) {
litArg = wr.getArg(0);
}
} else if (secondArg.isLiteral()) {
litArg = secondArg;
}
if (litArg != null) {
long lit = ((LiteralArg) litArg).getLiteral();
// fix 'c + (-1)' => 'c - (1)'
if (arith.getOp() == ArithOp.ADD && lit < 0) {
return new ArithNode(ArithOp.SUB,
arith.getResult(), insn.getArg(0),
InsnArg.lit(-lit, litArg.getType()));
}
}
return null;
}
/**
* Convert field arith operation to arith instruction
* (IPUT = ARITH (IGET, lit) -> ARITH (fieldArg <op>= lit))
*/
private static InsnNode convertFieldArith(MethodNode mth, InsnNode insn) {
InsnArg arg = insn.getArg(0);
if (!arg.isInsnWrap()) {
return null;
}
InsnNode wrap = ((InsnWrapArg) arg).getWrapInsn();
InsnType wrapType = wrap.getType();
if ((wrapType != InsnType.ARITH && wrapType != InsnType.STR_CONCAT)
|| !wrap.getArg(0).isInsnWrap()) {
return null;
}
InsnNode get = ((InsnWrapArg) wrap.getArg(0)).getWrapInsn();
InsnType getType = get.getType();
if (getType != InsnType.IGET && getType != InsnType.SGET) {
return null;
}
FieldInfo field = (FieldInfo) ((IndexInsnNode) insn).getIndex();
FieldInfo innerField = (FieldInfo) ((IndexInsnNode) get).getIndex();
if (!field.equals(innerField)) {
return null;
}
try {
RegisterArg reg = null;
if (getType == InsnType.IGET) {
reg = ((RegisterArg) get.getArg(0));
}
FieldArg fArg = new FieldArg(field, reg);
if (reg != null) {
fArg.setType(get.getArg(0).getType());
}
if (wrapType == InsnType.ARITH) {
ArithNode ar = (ArithNode) wrap;
return new ArithNode(ar.getOp(), fArg, ar.getArg(1));
} else {
int argsCount = wrap.getArgsCount();
InsnNode concat = new InsnNode(InsnType.STR_CONCAT, argsCount - 1);
for (int i = 1; i < argsCount; i++) {
concat.addArg(wrap.getArg(i));
}
return new ArithNode(ArithOp.ADD, fArg, InsnArg.wrapArg(concat));
}
} catch (Throwable e) {
LOG.debug("Can't convert field arith insn: {}, mth: {}", insn, mth, e);
}
return null;
}
private static List<InsnNode> flattenInsnChain(InsnNode insn) {
List<InsnNode> chain = new ArrayList<InsnNode>();
InsnArg i = insn.getArg(0);
@@ -60,6 +60,9 @@ public class IfRegionVisitor extends AbstractVisitor implements IRegionVisitor,
tryInvertIfRegion(ifRegion);
}
}
if (RegionUtils.isEmpty(ifRegion.getThenRegion())) {
tryInvertIfRegion(ifRegion);
}
}
private static void moveReturnToThenBlock(MethodNode mth, IfRegion ifRegion) {
@@ -101,7 +101,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
// search dominator blocks in this region (don't need to go deeper)
for (BlockNode dominator : tryBlocksMap.keySet()) {
if (region.getSubBlocks().contains(dominator)) {
wrapBlocks(mth, region, dominator);
wrapBlocks(region, dominator);
tryBlocksMap.remove(dominator);
// if region is modified rerun this method
leaveRegion(mth, region);
@@ -113,7 +113,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
/**
* Extract all block dominated by 'dominator' to separate region and mark as try/catch block
*/
private void wrapBlocks(MethodNode mth, IRegion region, BlockNode dominator) {
private void wrapBlocks(IRegion region, BlockNode dominator) {
Region newRegion = new Region(region);
TryCatchBlock tb = tryBlocksMap.get(dominator);
assert tb != null;
@@ -3,6 +3,7 @@ package jadx.core.dex.visitors.regions;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.DeclareVariablesAttr;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer;
@@ -28,6 +29,38 @@ import org.slf4j.LoggerFactory;
public class ProcessVariables extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(ProcessVariables.class);
private static class Variable {
private final int regNum;
private final ArgType type;
public Variable(RegisterArg arg) {
this.regNum = arg.getRegNum();
this.type = arg.getType();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Variable variable = (Variable) o;
return regNum == variable.regNum && type.equals(variable.type);
}
@Override
public int hashCode() {
return 31 * regNum + type.hashCode();
}
@Override
public String toString() {
return regNum + " " + type;
}
}
private static class Usage {
private RegisterArg arg;
private IRegion argRegion;
@@ -66,7 +99,7 @@ public class ProcessVariables extends AbstractVisitor {
@Override
public void visit(MethodNode mth) throws JadxException {
final Map<RegisterArg, Usage> usageMap = new LinkedHashMap<RegisterArg, Usage>();
final Map<Variable, Usage> usageMap = new LinkedHashMap<Variable, Usage>();
// collect all variables usage
IRegionVisitor collect = new TracedRegionVisitor() {
@@ -79,11 +112,7 @@ public class ProcessVariables extends AbstractVisitor {
// result
RegisterArg result = insn.getResult();
if (result != null && result.isRegister()) {
Usage u = usageMap.get(result);
if (u == null) {
u = new Usage();
usageMap.put(result, u);
}
Usage u = addToUsageMap(result, usageMap);
if (u.getArg() == null) {
u.setArg(result);
u.setArgRegion(curRegion);
@@ -94,11 +123,7 @@ public class ProcessVariables extends AbstractVisitor {
args.clear();
insn.getRegisterArgs(args);
for (RegisterArg arg : args) {
Usage u = usageMap.get(arg);
if (u == null) {
u = new Usage();
usageMap.put(arg, u);
}
Usage u = addToUsageMap(arg, usageMap);
u.getUseRegions().add(curRegion);
}
}
@@ -109,11 +134,11 @@ public class ProcessVariables extends AbstractVisitor {
// reduce assigns map
List<RegisterArg> mthArgs = mth.getArguments(true);
for (RegisterArg arg : mthArgs) {
usageMap.remove(arg);
usageMap.remove(new Variable(arg));
}
for (Iterator<Entry<RegisterArg, Usage>> it = usageMap.entrySet().iterator(); it.hasNext(); ) {
Entry<RegisterArg, Usage> entry = it.next();
for (Iterator<Entry<Variable, Usage>> it = usageMap.entrySet().iterator(); it.hasNext(); ) {
Entry<Variable, Usage> entry = it.next();
Usage u = entry.getValue();
// if no assigns => remove
@@ -134,7 +159,7 @@ public class ProcessVariables extends AbstractVisitor {
}
// apply
for (Entry<RegisterArg, Usage> entry : usageMap.entrySet()) {
for (Entry<Variable, Usage> entry : usageMap.entrySet()) {
Usage u = entry.getValue();
// find region which contain all usage regions
@@ -168,6 +193,16 @@ public class ProcessVariables extends AbstractVisitor {
}
}
Usage addToUsageMap(RegisterArg arg, Map<Variable, Usage> usageMap) {
Variable varId = new Variable(arg);
Usage usage = usageMap.get(varId);
if (usage == null) {
usage = new Usage();
usageMap.put(varId, usage);
}
return usage;
}
private static void declareVar(IContainer region, RegisterArg arg) {
DeclareVariablesAttr dv = (DeclareVariablesAttr) region.getAttributes().get(AttributeType.DECLARE_VARIABLES);
if (dv == null) {
@@ -129,12 +129,10 @@ public class RegionMaker {
}
}
}
if (!processed) {
r.getSubBlocks().add(block);
next = BlockUtils.getNextBlock(block);
}
if (next != null && !stack.containsExit(block) && !stack.containsExit(next)) {
return next;
} else {
@@ -153,12 +151,12 @@ public class RegionMaker {
if (nextStart != null && exitBlocksSet.remove(nextStart)) {
exitBlocks.add(nextStart);
}
if (exitBlocksSet.remove(loop.getEnd())) {
exitBlocks.add(loop.getEnd());
}
if (exitBlocksSet.remove(loopStart)) {
exitBlocks.add(loopStart);
}
if (exitBlocksSet.remove(loop.getEnd())) {
exitBlocks.add(loop.getEnd());
}
exitBlocks.addAll(exitBlocksSet);
exitBlocksSet = null;
@@ -185,7 +183,7 @@ public class RegionMaker {
loopRegion = new LoopRegion(curRegion, condBlock, condBlock == loop.getEnd());
boolean found = true;
if (condBlock != loopStart) {
if (condBlock != loopStart && condBlock != loop.getEnd()) {
if (condBlock.getPredecessors().contains(loopStart)) {
loopRegion.setPreCondition(loopStart);
// if we can't merge pre-condition this is not correct header
@@ -332,7 +330,7 @@ public class RegionMaker {
traverseMonitorExits(synchRegion, insn.getArg(0), block, exits, cacheSet);
for (InsnNode exitInsn : synchRegion.getExitInsns()) {
InstructionRemover.unbindInsn(exitInsn);
InstructionRemover.unbindInsn(mth, exitInsn);
}
block = BlockUtils.getNextBlock(block);
@@ -430,13 +428,13 @@ public class RegionMaker {
}
// invert condition (compiler often do it)
ifnode.invertCondition();
BlockNode tmp = bThen;
bThen = bElse;
bElse = tmp;
bThen = ifnode.getThenBlock();
bElse = ifnode.getElseBlock();
thenBlock = bThen;
// select else and exit blocks
if (block.getDominatesOn().size() == 2) {
if (block.getDominatesOn().size() == 2
&& !BlockUtils.isPathExists(bThen, bElse)) {
elseBlock = bElse;
} else {
if (bElse.getPredecessors().size() != 1) {
@@ -571,14 +569,15 @@ public class RegionMaker {
pass = false;
break;
}
List<InsnArg> useList = res.getTypedVar().getUseList();
if (useList.size() != 2) {
List<RegisterArg> useList = res.getSVar().getUseList();
if (useList.size() != 1) {
pass = false;
break;
} else {
InsnArg arg = useList.get(1);
InsnArg arg = useList.get(0);
InsnNode usePlace = arg.getParentInsn();
if (!BlockUtils.blockContains(block, usePlace) && !BlockUtils.blockContains(next, usePlace)) {
if (!BlockUtils.blockContains(block, usePlace)
&& !BlockUtils.blockContains(next, usePlace)) {
pass = false;
break;
}
@@ -638,8 +637,9 @@ public class RegionMaker {
for (int i = domsOn.nextSetBit(0); i >= 0; i = domsOn.nextSetBit(i + 1)) {
BlockNode b = mth.getBasicBlocks().get(i);
for (BlockNode s : b.getCleanSuccessors()) {
if (domsOn.get(s.getId())) {
domsOn.clear(s.getId());
int id = s.getId();
if (domsOn.get(id)) {
domsOn.clear(id);
}
}
}
@@ -657,11 +657,12 @@ public class RegionMaker {
stack.push(sw);
if (out != null) {
stack.addExit(out);
} else {
for (BlockNode e : BlockUtils.bitSetToBlocks(mth, domsOn)) {
stack.addExit(e);
}
}
// else {
// for (BlockNode e : BlockUtils.bitSetToBlocks(mth, domsOn)) {
// stack.addExit(e);
// }
// }
if (!stack.containsExit(defCase)) {
sw.setDefaultCase(makeRegion(defCase, stack));
@@ -4,6 +4,7 @@ import jadx.core.dex.attributes.AttributeFlag;
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.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
@@ -15,8 +16,6 @@ import jadx.core.dex.regions.TernaryRegion;
import jadx.core.dex.visitors.CodeShrinker;
import jadx.core.utils.InsnList;
import java.util.List;
public class TernaryMod {
private TernaryMod() {
@@ -41,12 +40,14 @@ public class TernaryMod {
InsnNode e = eb.getInstructions().get(0);
if (t.getResult() != null && e.getResult() != null
&& t.getResult().getTypedVar() == e.getResult().getTypedVar()) {
&& t.getResult().equalRegisterAndType(e.getResult())
&& t.getResult().getSVar().isUsedInPhi()) {
InsnList.remove(tb, t);
InsnList.remove(eb, e);
RegisterArg resArg = t.getResult().getSVar().getUsedInPhi().getResult();
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(),
t.getResult(), InsnArg.wrapArg(t), InsnArg.wrapArg(e));
resArg, InsnArg.wrapArg(t), InsnArg.wrapArg(e));
TernaryRegion tern = new TernaryRegion(ifRegion, header);
// TODO: add api for replace regions
ifRegion.setTernRegion(tern);
@@ -55,12 +56,6 @@ public class TernaryMod {
header.getInstructions().clear();
header.getInstructions().add(ternInsn);
// unbind result args
List<InsnArg> useList = ternInsn.getResult().getTypedVar().getUseList();
useList.remove(t.getResult());
useList.remove(e.getResult());
useList.add(ternInsn.getResult());
// shrink method again
CodeShrinker.shrinkMethod(mth);
return;
@@ -0,0 +1,89 @@
package jadx.core.dex.visitors.ssa;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.PhiListAttr;
import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.exceptions.JadxException;
import java.util.Iterator;
import java.util.List;
public class EliminatePhiNodes extends AbstractVisitor {
@Override
public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode()) {
return;
}
enumerateSVars(mth);
removePhiInstructions(mth);
}
public static void enumerateSVars(MethodNode mth) {
for (SSAVar sVar : mth.getSVars()) {
if (sVar.isUsedInPhi()) {
sVar.mergeName(sVar.getUsedInPhi().getResult());
}
}
}
// public static void enumerateSVars(MethodNode mth) {
// List<SSAVar> vars = mth.getSVars();
// int varsSize = vars.size();
// Deque<SSAVar> workList = new LinkedList<SSAVar>();
// for (int i = 0; i < varsSize; i++) {
// SSAVar ssaVar = vars.get(i);
// ssaVar.setVarId(i);
// if (ssaVar.isUsedInPhi()) {
// workList.add(ssaVar);
// }
// }
//
// int k = 0;
// while (!workList.isEmpty()) {
// SSAVar var = workList.pop();
// RegisterArg assignVar = var.getUsedInPhi().getResult();
// // set same name and variable ID
// var.mergeName(assignVar);
// SSAVar assignSVar = assignVar.getSVar();
// int varId = assignSVar.getVarId();
// var.setVarId(varId);
//
// if (assignSVar.isUsedInPhi()) {
// PhiInsn assignPhi = assignSVar.getUsedInPhi();
// SSAVar asVar = assignPhi.getResult().getSVar();
// if (asVar.getVarId() != varId) {
// asVar.setVarId(varId);
// for (int i = 0; i < assignPhi.getArgsCount(); i++) {
// workList.push(assignPhi.getArg(i).getSVar());
// }
// }
// }
// if (k++ > 1000) {
// throw new JadxRuntimeException("Can't calculate variable id");
// }
// }
// }
private static void removePhiInstructions(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
PhiListAttr phiList = (PhiListAttr) block.getAttributes().get(AttributeType.PHI_LIST);
if (phiList == null) {
continue;
}
List<PhiInsn> list = phiList.getList();
for (PhiInsn phiInsn : list) {
for (Iterator<InsnNode> iterator = block.getInstructions().iterator(); iterator.hasNext(); ) {
InsnNode insn = iterator.next();
if (insn == phiInsn) {
iterator.remove();
}
}
}
}
}
}
@@ -0,0 +1,109 @@
package jadx.core.dex.visitors.ssa;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.BitSet;
import java.util.List;
public class LiveVarAnalysis {
private final MethodNode mth;
private BitSet[] uses;
private BitSet[] defs;
private BitSet[] liveIn;
private BitSet[] assignBlocks;
public LiveVarAnalysis(MethodNode mth) {
this.mth = mth;
runAnalysis();
}
public void runAnalysis() {
int bbCount = mth.getBasicBlocks().size();
int regsCount = mth.getRegsCount();
this.uses = initBitSetArray(bbCount, regsCount);
this.defs = initBitSetArray(bbCount, regsCount);
this.assignBlocks = initBitSetArray(regsCount, bbCount);
fillBasicBlockInfo();
processLiveInfo();
}
public BitSet getAssignBlocks(int regNum) {
return assignBlocks[regNum];
}
public boolean isLive(int blockId, int regNum) {
return liveIn[blockId].get(regNum);
}
private void fillBasicBlockInfo() {
for (BlockNode block : mth.getBasicBlocks()) {
int blockId = block.getId();
BitSet gen = uses[blockId];
BitSet kill = defs[blockId];
for (InsnNode insn : block.getInstructions()) {
for (InsnArg arg : insn.getArguments()) {
if (arg.isRegister()) {
int regNum = ((RegisterArg) arg).getRegNum();
if (!kill.get(regNum)) {
gen.set(regNum);
}
}
}
RegisterArg result = insn.getResult();
if (result != null) {
int regNum = result.getRegNum();
kill.set(regNum);
assignBlocks[regNum].set(blockId);
}
}
}
}
private void processLiveInfo() {
int bbCount = mth.getBasicBlocks().size();
int regsCount = mth.getRegsCount();
BitSet[] liveIn = initBitSetArray(bbCount, regsCount);
List<BlockNode> blocks = mth.getBasicBlocks();
int blocksSize = blocks.size();
boolean changed;
int k = 0;
do {
changed = false;
for (int i = 0; i < blocksSize; i++) {
BlockNode block = blocks.get(i);
int blockId = block.getId();
BitSet prevIn = liveIn[blockId];
BitSet newIn = new BitSet(regsCount);
List<BlockNode> successors = block.getSuccessors();
for (int s = 0, successorsSize = successors.size(); s < successorsSize; s++) {
newIn.or(liveIn[successors.get(s).getId()]);
}
newIn.andNot(defs[blockId]);
newIn.or(uses[blockId]);
if (!prevIn.equals(newIn)) {
changed = true;
liveIn[blockId] = newIn;
}
}
if (k++ > 1000) {
throw new JadxRuntimeException("Live variable analysis reach iterations limit");
}
} while (changed);
this.liveIn = liveIn;
}
private static BitSet[] initBitSetArray(int length, int bitsCount) {
BitSet[] array = new BitSet[length];
for (int i = 0; i < length; i++) {
array[i] = new BitSet(bitsCount);
}
return array;
}
}
@@ -0,0 +1,216 @@
package jadx.core.dex.visitors.ssa;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.PhiListAttr;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.InstructionRemover;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
public class SSATransform extends AbstractVisitor {
@Override
public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode()) {
return;
}
process(mth);
}
public void process(MethodNode mth) {
LiveVarAnalysis la = new LiveVarAnalysis(mth);
la.runAnalysis();
for (int i = 0; i < mth.getRegsCount(); i++) {
placePhi(mth, i, la);
}
renameVariables(mth);
removeUselessPhi(mth);
}
private void placePhi(MethodNode mth, int regNum, LiveVarAnalysis la) {
List<BlockNode> blocks = mth.getBasicBlocks();
int blocksCount = blocks.size();
BitSet hasPhi = new BitSet(blocksCount);
BitSet processed = new BitSet(blocksCount);
Deque<BlockNode> workList = new LinkedList<BlockNode>();
BitSet assignBlocks = la.getAssignBlocks(regNum);
for (int id = assignBlocks.nextSetBit(0); id >= 0; id = assignBlocks.nextSetBit(id + 1)) {
processed.set(id);
workList.add(blocks.get(id));
}
while (!workList.isEmpty()) {
BlockNode block = workList.pop();
BitSet domFrontier = block.getDomFrontier();
for (int id = domFrontier.nextSetBit(0); id >= 0; id = domFrontier.nextSetBit(id + 1)) {
if (!hasPhi.get(id) && la.isLive(id, regNum)) {
BlockNode df = blocks.get(id);
addPhi(df, regNum);
hasPhi.set(id);
if (!processed.get(id)) {
processed.set(id);
workList.add(df);
}
}
}
}
}
private void addPhi(BlockNode block, int regNum) {
PhiListAttr phiList = (PhiListAttr) block.getAttributes().get(AttributeType.PHI_LIST);
if (phiList == null) {
phiList = new PhiListAttr();
block.getAttributes().add(phiList);
}
PhiInsn phiInsn = new PhiInsn(regNum, block.getPredecessors().size());
phiList.getList().add(phiInsn);
block.getInstructions().add(0, phiInsn);
}
private void renameVariables(MethodNode mth) {
int regsCount = mth.getRegsCount();
SSAVar[] vars = new SSAVar[regsCount];
int[] versions = new int[regsCount];
// init method arguments
for (RegisterArg arg : mth.getArguments(true)) {
int regNum = arg.getRegNum();
vars[regNum] = mth.makeNewSVar(regNum, versions, arg);
}
renameVar(mth, vars, versions, mth.getEnterBlock());
}
private void renameVar(MethodNode mth, SSAVar[] vars, int[] vers, BlockNode block) {
SSAVar[] inputVars = Arrays.copyOf(vars, vars.length);
for (InsnNode insn : block.getInstructions()) {
if (insn.getType() != InsnType.PHI) {
for (InsnArg arg : insn.getArguments()) {
if (arg.isRegister()) {
RegisterArg reg = (RegisterArg) arg;
int regNum = reg.getRegNum();
SSAVar var = vars[regNum];
if (var == null) {
var = mth.makeNewSVar(regNum, vers, null);
vars[regNum] = var;
}
var.use(reg);
}
}
}
RegisterArg result = insn.getResult();
if (result != null) {
int regNum = result.getRegNum();
vars[regNum] = mth.makeNewSVar(regNum, vers, result);
}
}
for (BlockNode s : block.getSuccessors()) {
PhiListAttr phiList = (PhiListAttr) s.getAttributes().get(AttributeType.PHI_LIST);
if (phiList != null) {
int j = s.getPredecessors().indexOf(block);
if (j == -1) {
throw new JadxRuntimeException("Can't find predecessor for " + block + " " + s);
}
for (PhiInsn phiInsn : phiList.getList()) {
int regNum = phiInsn.getResult().getRegNum();
SSAVar var = vars[regNum];
if (var == null) {
var = mth.makeNewSVar(regNum, vers, null);
vars[regNum] = var;
}
var.use(phiInsn.getArg(j));
var.setUsedInPhi(phiInsn);
}
}
}
for (BlockNode domOn : block.getDominatesOn()) {
renameVar(mth, vars, vers, domOn);
}
System.arraycopy(inputVars, 0, vars, 0, vars.length);
}
private void removeUselessPhi(MethodNode mth) {
List<PhiInsn> insnToRemove = new ArrayList<PhiInsn>();
for (SSAVar var : mth.getSVars()) {
// phi result not used
if (var.getUseList().isEmpty()) {
InsnNode assignInsn = var.getAssign().getParentInsn();
if (assignInsn != null && assignInsn.getType() == InsnType.PHI) {
insnToRemove.add((PhiInsn) assignInsn);
}
}
}
for (BlockNode block : mth.getBasicBlocks()) {
PhiListAttr phiList = (PhiListAttr) block.getAttributes().get(AttributeType.PHI_LIST);
if (phiList == null) {
continue;
}
for (PhiInsn phi : phiList.getList()) {
removePhiWithSameArgs(phi, insnToRemove);
}
}
removePhiList(mth, insnToRemove);
}
private void removePhiWithSameArgs(PhiInsn phi, List<PhiInsn> insnToRemove) {
if (phi.getArgsCount() <= 1) {
insnToRemove.add(phi);
return;
}
boolean allSame = true;
SSAVar var = phi.getArg(0).getSVar();
for (int i = 1; i < phi.getArgsCount(); i++) {
if (var != phi.getArg(i).getSVar()) {
allSame = false;
break;
}
}
if (allSame) {
// replace
insnToRemove.add(phi);
SSAVar assign = phi.getResult().getSVar();
for (RegisterArg arg : new ArrayList<RegisterArg>(assign.getUseList())) {
assign.removeUse(arg);
var.use(arg);
}
}
}
private void removePhiList(MethodNode mth, List<PhiInsn> insnToRemove) {
if (insnToRemove.isEmpty()) {
return;
}
for (BlockNode block : mth.getBasicBlocks()) {
PhiListAttr phiList = (PhiListAttr) block.getAttributes().get(AttributeType.PHI_LIST);
if (phiList == null) {
continue;
}
List<PhiInsn> list = phiList.getList();
for (PhiInsn phiInsn : insnToRemove) {
if (list.remove(phiInsn)) {
for (InsnArg arg : phiInsn.getArguments()) {
((RegisterArg) arg).getSVar().setUsedInPhi(null);
}
InstructionRemover.remove(mth, block, phiInsn);
}
}
if (list.isEmpty()) {
block.getAttributes().remove(AttributeType.PHI_LIST);
}
}
insnToRemove.clear();
}
}
@@ -1,4 +1,4 @@
package jadx.core.dex.visitors.typeresolver.finish;
package jadx.core.dex.visitors.typeinference;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.InsnNode;
@@ -1,14 +1,11 @@
package jadx.core.dex.visitors.typeresolver;
package jadx.core.dex.visitors.typeinference;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.typeresolver.finish.CheckTypeVisitor;
import jadx.core.dex.visitors.typeresolver.finish.PostTypeResolver;
import jadx.core.dex.visitors.typeresolver.finish.SelectTypeVisitor;
public class FinishTypeResolver extends AbstractVisitor {
public class FinishTypeInference extends AbstractVisitor {
@Override
public void visit(MethodNode mth) {
@@ -22,7 +19,7 @@ public class FinishTypeResolver extends AbstractVisitor {
change = false;
for (BlockNode block : mth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) {
if (PostTypeResolver.visit(mth, insn)) {
if (PostTypeInference.visit(mth, insn)) {
change = true;
}
}
@@ -1,4 +1,4 @@
package jadx.core.dex.visitors.typeresolver.finish;
package jadx.core.dex.visitors.typeinference;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.IndexInsnNode;
@@ -7,17 +7,13 @@ import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.TypedVar;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PostTypeResolver {
private static final Logger LOG = LoggerFactory.getLogger(PostTypeResolver.class);
public class PostTypeInference {
public static boolean visit(MethodNode mth, InsnNode insn) {
switch (insn.getType()) {
@@ -30,12 +26,11 @@ public class PostTypeResolver {
// incorrect literal value for object
ArgType type = (lit == 1 ? ArgType.BOOLEAN : ArgType.INT);
// can't merge with object -> force it
litArg.getTypedVar().forceSetType(type);
res.getTypedVar().forceSetType(type);
litArg.setType(type);
res.getSVar().setType(type);
return true;
}
}
// return litArg.getTypedVar().forceSetType(res.getType());
return litArg.merge(res);
case MOVE: {
@@ -79,7 +74,7 @@ public class PostTypeResolver {
ArgType argType = args.get(i);
InsnArg insnArg = inv.getArg(j--);
if (insnArg.isRegister() && !argType.equals(insnArg.getType())) {
insnArg.getTypedVar().forceSetType(argType);
insnArg.setType(argType);
change = true;
}
}
@@ -89,12 +84,12 @@ public class PostTypeResolver {
case CHECK_CAST: {
ArgType castType = (ArgType) ((IndexInsnNode) insn).getIndex();
TypedVar typedVar = insn.getResult().getTypedVar();
SSAVar sVar = insn.getResult().getSVar();
// don't override generic types of same base class
boolean skip = castType.isObject() && castType.getObject().equals(typedVar.getType().getObject());
boolean skip = castType.isObject() && castType.getObject().equals(sVar.getType().getObject());
if (!skip) {
// workaround for compiler bug (see TestDuplicateCast)
typedVar.forceSetType(castType);
sVar.setType(castType);
}
return true;
}
@@ -106,6 +101,14 @@ public class PostTypeResolver {
}
static void setType(InsnArg arg, ArgType type) {
if (arg.isRegister()) {
((RegisterArg) arg).getSVar().setType(type);
} else {
arg.setType(type);
}
}
private static boolean fixArrayTypes(InsnArg array, InsnArg elem) {
boolean change = false;
if (!elem.getType().isTypeKnown() && elem.merge(array.getType().getArrayElement())) {
@@ -116,4 +119,5 @@ public class PostTypeResolver {
}
return change;
}
}
@@ -1,4 +1,4 @@
package jadx.core.dex.visitors.typeresolver.finish;
package jadx.core.dex.visitors.typeinference;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
@@ -11,7 +11,6 @@ public class SelectTypeVisitor {
if (res != null && !res.getType().isTypeKnown()) {
selectType(res);
}
for (InsnArg arg : insn.getArguments()) {
if (!arg.getType().isTypeKnown()) {
selectType(arg);
@@ -21,8 +20,8 @@ public class SelectTypeVisitor {
private static void selectType(InsnArg arg) {
ArgType t = arg.getType();
ArgType nt = t.selectFirst();
arg.getTypedVar().merge(nt);
ArgType newType = ArgType.merge(t, t.selectFirst());
arg.setType(newType);
}
}
@@ -0,0 +1,105 @@
package jadx.core.dex.visitors.typeinference;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.instructions.PhiInsn;
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;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.List;
public class TypeInference extends AbstractVisitor {
@Override
public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode()) {
return;
}
for (SSAVar var : mth.getSVars()) {
// inference variable type
ArgType type = processType(var);
if (type == null) {
type = ArgType.UNKNOWN;
}
var.setType(type);
// search variable name
String name = processVarName(var);
if (name != null) {
var.setName(name);
}
}
// fix type for vars used only in Phi nodes
for (SSAVar sVar : mth.getSVars()) {
if (sVar.isUsedInPhi()) {
processPhiNode(sVar.getUsedInPhi());
}
}
}
private ArgType processType(SSAVar var) {
RegisterArg assign = var.getAssign();
List<RegisterArg> useList = var.getUseList();
if (assign != null
&& (useList.isEmpty() || assign.isTypeImmutable())) {
return assign.getType();
}
ArgType type;
if (assign != null) {
type = assign.getType();
} else {
type = ArgType.UNKNOWN;
}
for (RegisterArg arg : useList) {
ArgType useType = arg.getType();
if (useType.isTypeKnown()) {
type = ArgType.merge(type, useType);
}
if (arg.getParentInsn().getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
throw new JadxRuntimeException("not removed arg");
}
}
return type;
}
private void processPhiNode(PhiInsn phi) {
ArgType type = phi.getResult().getType();
if (!type.isTypeKnown()) {
for (InsnArg arg : phi.getArguments()) {
if (arg.getType().isTypeKnown()) {
type = arg.getType();
break;
}
}
}
phi.getResult().setType(type);
for (int i = 0; i < phi.getArgsCount(); i++) {
RegisterArg arg = phi.getArg(i);
arg.setType(type);
arg.getSVar().mergeName(phi.getResult());
}
}
private String processVarName(SSAVar var) {
String name = null;
if (var.getAssign() != null) {
name = var.getAssign().getName();
}
if (name != null) {
return name;
}
for (RegisterArg arg : var.getUseList()) {
String vName = arg.getName();
if (vName != null) {
name = vName;
}
}
return name;
}
}
@@ -1,115 +0,0 @@
package jadx.core.dex.visitors.typeresolver;
import jadx.core.dex.attributes.BlockRegState;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.AbstractVisitor;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TypeResolver extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(TypeResolver.class);
@Override
public void visit(MethodNode mth) {
if (mth.isNoCode()) {
return;
}
visitBlocks(mth);
visitEdges(mth);
// clear register states
for (BlockNode block : mth.getBasicBlocks()) {
block.setStartState(null);
block.setEndState(null);
}
}
private static void visitBlocks(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
BlockRegState state = new BlockRegState(mth);
if (block == mth.getEnterBlock()) {
for (RegisterArg arg : mth.getArguments(true)) {
state.assignReg(arg);
}
}
block.setStartState(new BlockRegState(state));
for (InsnNode insn : block.getInstructions()) {
for (InsnArg arg : insn.getArguments()) {
if (arg.isRegister()) {
state.use((RegisterArg) arg);
}
}
if (insn.getResult() != null) {
state.assignReg(insn.getResult());
}
}
block.setEndState(new BlockRegState(state));
}
}
private void visitEdges(MethodNode mth) {
List<BlockNode> preds = mth.getBasicBlocks();
boolean changed;
do {
changed = false;
for (BlockNode block : preds) {
for (BlockNode pred : block.getPredecessors()) {
if (connectEdges(mth, pred, block, true)) {
changed = true;
}
}
}
} while (changed);
int i = 0;
do {
changed = false;
for (BlockNode block : mth.getBasicBlocks()) {
for (BlockNode dest : block.getSuccessors()) {
if (connectEdges(mth, block, dest, false)) {
changed = true;
}
}
}
i++;
if (i > 10) {
LOG.warn("Can't resolve types (forward connectEdges pass) in method: {}", mth);
break;
}
} while (changed && i < 10);
}
private static boolean connectEdges(MethodNode mth, BlockNode from, BlockNode to, boolean back) {
BlockRegState end = from.getEndState();
BlockRegState start = to.getStartState();
boolean changed = false;
for (int r = 0; r < mth.getRegsCount(); r++) {
RegisterArg sr = start.getRegister(r);
RegisterArg er = end.getRegister(r);
if (back) {
if (er.getTypedVar() == null && sr.getTypedVar() != null) {
er.replaceTypedVar(sr);
changed = true;
}
} else {
if (sr.getTypedVar() != null && er.getTypedVar() != null) {
sr.replaceTypedVar(er);
changed = true;
}
}
}
return changed;
}
}
@@ -4,6 +4,8 @@ import java.util.BitSet;
public class EmptyBitSet extends BitSet {
private static final long serialVersionUID = -1194884945157778639L;
public EmptyBitSet() {
super(0);
}
@@ -1,6 +1,10 @@
package jadx.core.utils;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
@@ -9,16 +13,27 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Helper class for correct instructions removing,
* can be used while iterating over instructions list
*/
public class InstructionRemover {
private static final Logger LOG = LoggerFactory.getLogger(InstructionRemover.class);
private final MethodNode mth;
private final List<InsnNode> insns;
private final List<InsnNode> toRemove;
public InstructionRemover(List<InsnNode> instructions) {
public InstructionRemover(MethodNode mth, BlockNode block) {
this(mth, block.getInstructions());
}
public InstructionRemover(MethodNode mth, List<InsnNode> instructions) {
this.mth = mth;
this.insns = instructions;
this.toRemove = new ArrayList<InsnNode>();
}
@@ -28,48 +43,48 @@ public class InstructionRemover {
}
public void perform() {
if (toRemove.isEmpty()) {
return;
}
removeAll(insns, toRemove);
toRemove.clear();
}
public static void unbindInsnList(List<InsnNode> unbind) {
public static void unbindInsnList(MethodNode mth, List<InsnNode> unbind) {
for (InsnNode rem : unbind) {
unbindInsn(rem);
unbindInsn(mth, rem);
}
}
public static void unbindInsn(InsnNode insn) {
if (insn.getResult() != null) {
InsnArg res = insn.getResult();
res.getTypedVar().removeUse(res);
public static void unbindInsn(MethodNode mth, InsnNode insn) {
RegisterArg r = insn.getResult();
if (r != null && r.getSVar() != null) {
if (Consts.DEBUG && !r.getSVar().getUseList().isEmpty()) {
LOG.debug("Unbind insn with result: {}", insn);
}
mth.removeSVar(r.getSVar());
}
for (InsnArg arg : insn.getArguments()) {
if (arg.isRegister()) {
arg.getTypedVar().removeUse(arg);
if (arg instanceof RegisterArg) {
RegisterArg reg = (RegisterArg) arg;
SSAVar sVar = reg.getSVar();
if (sVar != null) {
sVar.removeUse(reg);
}
}
}
}
public static void removeAll(BlockNode block, List<InsnNode> toRemove) {
removeAll(block.getInstructions(), toRemove);
insn.getAttributes().add(AttributeFlag.INCONSISTENT_CODE);
}
// Don't use 'insns.removeAll(toRemove)' because it will remove instructions by content
// and here can be several instructions with same content
public static void removeAll(List<InsnNode> insns, List<InsnNode> toRemove) {
if (insns == toRemove) {
for (InsnNode rem : toRemove) {
unbindInsn(rem);
}
return;
}
private void removeAll(List<InsnNode> insns, List<InsnNode> toRemove) {
for (InsnNode rem : toRemove) {
unbindInsn(rem);
for (Iterator<InsnNode> it = insns.iterator(); it.hasNext(); ) {
InsnNode insn = it.next();
if (insn == rem) {
it.remove();
unbindInsn(mth, rem);
int insnsCount = insns.size();
for (int i = 0; i < insnsCount; i++) {
if (insns.get(i) == rem) {
insns.remove(i);
break;
}
}
@@ -79,12 +94,12 @@ public class InstructionRemover {
public static void remove(MethodNode mth, InsnNode insn) {
BlockNode block = BlockUtils.getBlockByInsn(mth, insn);
if (block != null) {
remove(block, insn);
remove(mth, block, insn);
}
}
public static void remove(BlockNode block, InsnNode insn) {
unbindInsn(insn);
public static void remove(MethodNode mth, BlockNode block, InsnNode insn) {
unbindInsn(mth, insn);
// remove by pointer (don't use equals)
Iterator<InsnNode> it = block.getInstructions().iterator();
while (it.hasNext()) {
@@ -96,17 +111,10 @@ public class InstructionRemover {
}
}
public static void removeAllByContent(BlockNode block, List<InsnNode> toRemove) {
for (InsnNode rem : toRemove) {
unbindInsn(rem);
}
block.getInstructions().removeAll(toRemove);
}
public static void remove(BlockNode block, int index) {
InsnNode insn = block.getInstructions().get(index);
unbindInsn(insn);
block.getInstructions().remove(index);
public static void remove(MethodNode mth, BlockNode block, int index) {
List<InsnNode> instructions = block.getInstructions();
unbindInsn(mth, instructions.get(index));
instructions.remove(index);
}
}
@@ -32,6 +32,10 @@ public class RegionUtils {
}
}
public static boolean isEmpty(IContainer container) {
return !notEmpty(container);
}
public static boolean notEmpty(IContainer container) {
if (container instanceof BlockNode) {
return ((BlockNode) container).getInstructions().size() != 0;
@@ -22,6 +22,7 @@ public class TestWrongCode extends InternalJadxTest {
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, not(containsString("return false.length;")));
assertThat(code, containsString("return null.length;"));
@@ -1,4 +1,4 @@
package jadx.tests.internal;
package jadx.tests.internal.annotations;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
@@ -0,0 +1,41 @@
package jadx.tests.internal.annotations;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat;
public class TestAnnotations2 extends InternalJadxTest {
public static class TestCls {
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public static @interface A {
int i();
float f();
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("@Target({ElementType.TYPE})"));
assertThat(code, containsString("@Retention(RetentionPolicy.RUNTIME)"));
assertThat(code, containsString("public static @interface A {"));
assertThat(code, containsString("float f();"));
assertThat(code, containsString("int i();"));
}
}
@@ -5,13 +5,10 @@ import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestConditions2 extends InternalJadxTest {
public static class TestCls {
int c;
String d;
String f;
@@ -33,7 +30,7 @@ public class TestConditions2 extends InternalJadxTest {
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("return;"));
assertThat(code, not(containsString("else")));
// assertThat(code, containsString("return;"));
// assertThat(code, not(containsString("else")));
}
}
@@ -0,0 +1,34 @@
package jadx.tests.internal.conditions;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import java.util.List;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestConditions6 extends InternalJadxTest {
public static class TestCls {
public boolean test(List<String> l1, List<String> l2) {
if (l2.size() > 0) {
l1.clear();
}
return l1.size() == 0;
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("return l1.size() == 0;"));
assertThat(code, not(containsString("else")));
}
}
@@ -0,0 +1,31 @@
package jadx.tests.internal.conditions;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestConditions7 extends InternalJadxTest {
public static class TestCls {
public void test(int[] a, int i) {
if (i >= 0 && i < a.length) {
a[i]++;
}
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("if (i >= 0 && i < a.length) {"));
assertThat(code, not(containsString("||")));
}
}
@@ -1,4 +1,4 @@
package jadx.tests.internal;
package jadx.tests.internal.conditions;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
@@ -34,9 +34,12 @@ public class TestElseIf extends InternalJadxTest {
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("} else if (str.equals(\"b\")) {"));
assertThat(code, containsString("} else {"));
assertThat(code, containsString("int r;"));
assertThat(code, containsString("r = 1;"));
assertThat(code, containsString("r = -1;"));
// no ternary operator
assertThat(code, not(containsString("?")));
@@ -6,7 +6,6 @@ import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.either;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -31,11 +30,11 @@ public class TestTernary extends InternalJadxTest {
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, not(containsString("else")));
assertThat(code, containsString("return a != 2;"));
assertThat(code, containsString("assertTrue(a == 3)"));
assertThat(code, either(containsString("return a > 0 ? 1 : (a + 2) * 3;"))
.or(containsString("return (a > 0) ? 1 : (a + 2) * 3;")));
assertThat(code, containsString("return a > 0 ? 1 : (a + 2) * 3;"));
}
}
@@ -0,0 +1,31 @@
package jadx.tests.internal.conditions;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
public class TestTernary2 extends InternalJadxTest {
public static class TestCls {
public void test() {
assertTrue(f(1, 0) == 0);
}
private int f(int a, int b) {
return a + b;
}
}
//@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("assertTrue(f(1, 0) == 0);"));
}
}
@@ -30,6 +30,7 @@ public class TestInline3 extends InternalJadxTest {
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("this(b1, b2, 0, 0, 0);"));
assertThat(code, containsString("super(a, a);"));
@@ -43,6 +43,7 @@ public class TestSyntheticInline extends InternalJadxTest {
assertThat(code, not(containsString("access$")));
assertThat(code, not(containsString("x0")));
assertThat(code, containsString("f = v;"));
// assertThat(code, containsString("return f;"));
// assertThat(code, containsString("return func();"));
// Temporary solution
@@ -33,7 +33,7 @@ public class TestBreakInLoop extends InternalJadxTest {
String code = cls.getCode().toString();
System.out.println(code);
assertEquals(count(code, "this.f++;"), 1);
assertEquals(1, count(code, "this.f++;"));
assertThat(code, containsString("if (i < b) {"));
assertThat(code, containsString("break;"));
}
@@ -6,6 +6,7 @@ import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestLoopDetection extends InternalJadxTest {
@@ -33,5 +34,9 @@ public class TestLoopDetection extends InternalJadxTest {
assertThat(code, containsString("while (i < a.length && i < b) {"));
assertThat(code, containsString("while (i < a.length) {"));
assertThat(code, containsString("int i = 0;"));
assertThat(code, not(containsString("i_2")));
assertThat(code, containsString("i++;"));
}
}
@@ -0,0 +1,41 @@
package jadx.tests.internal.loops;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestLoopDetection2 extends InternalJadxTest {
public static class TestCls {
public int test(int a, int b) {
int c = a + b;
for (int i = a; i < b; i++) {
if (i == 7) {
c += 2;
} else {
c *= 2;
}
}
c--;
return c;
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("while (i < b) {"));
assertThat(code, containsString("int c = a + b;"));
assertThat(code, not(containsString("c_2")));
assertThat(code, containsString("i++"));
}
}
@@ -1,4 +1,4 @@
package jadx.tests.internal;
package jadx.tests.internal.synchronize;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
@@ -0,0 +1,31 @@
package jadx.tests.internal.synchronize;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat;
public class TestSynchronized2 extends InternalJadxTest {
public static class TestCls {
private static synchronized boolean test(Object obj) {
return obj.toString() != null;
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("private static synchronized boolean test(Object obj) {"));
assertThat(code, containsString("obj.toString() != null;"));
// TODO
// assertThat(code, containsString("return obj.toString() != null;"));
// assertThat(code, not(containsString("synchronized (")));
}
}
@@ -1,4 +1,4 @@
package jadx.tests.internal;
package jadx.tests.internal.trycatch;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
@@ -0,0 +1,45 @@
package jadx.tests.internal.trycatch;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat;
public class TestTryCatch2 extends InternalJadxTest {
public static class TestCls {
private final static Object obj = new Object();
private static boolean test() {
try {
synchronized (obj) {
obj.wait(5);
}
} catch (InterruptedException e) {
return false;
}
return true;
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("try {"));
assertThat(code, containsString("synchronized (obj) {"));
assertThat(code, containsString("obj.wait(5);"));
assertThat(code, containsString("} catch (InterruptedException e) {"));
// TODO
assertThat(code, containsString(" = false;"));
assertThat(code, containsString(" = true;"));
// assertThat(code, containsString("return false;"));
// assertThat(code, containsString("return true;"));
}
}
@@ -0,0 +1,33 @@
package jadx.tests.internal.types;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestTypeResolver extends InternalJadxTest {
public static class TestCls {
public TestCls(int b1, int b2) {
// test 'this' move and constructor invocation on moved register
this(b1, b2, 0, 0, 0);
}
public TestCls(int a1, int a2, int a3, int a4, int a5) {
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("this(b1, b2, 0, 0, 0);"));
assertThat(code, not(containsString("= this;")));
}
}
@@ -29,8 +29,9 @@ public class RunTests {
Collections.sort(clsList);
int passed = 0;
for (String cls : clsList) {
if (runTest(cls))
if (runTest(cls)) {
passed++;
}
}
int failed = clsList.size() - passed;
System.err.println("---");
@@ -67,9 +68,9 @@ public class RunTests {
+ (pass ? "PASS" : "FAIL") + "\t"
+ clsName
+ (msg == null ? "" : "\t - " + msg));
if (exc != null)
if (exc != null) {
exc.printStackTrace();
}
return pass;
} catch (ClassNotFoundException e) {
System.err.println("Class '" + clsName + "' not found");