fix: reimplement variable declaration visitor
This commit is contained in:
@@ -36,9 +36,9 @@ import jadx.core.dex.visitors.debuginfo.DebugInfoParseVisitor;
|
||||
import jadx.core.dex.visitors.regions.CheckRegions;
|
||||
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
||||
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
|
||||
import jadx.core.dex.visitors.regions.ProcessVariables;
|
||||
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
|
||||
import jadx.core.dex.visitors.regions.ReturnVisitor;
|
||||
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
|
||||
import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
|
||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||
@@ -97,16 +97,15 @@ public class Jadx {
|
||||
passes.add(new ExtractFieldInit());
|
||||
passes.add(new ClassModifier());
|
||||
passes.add(new EnumVisitor());
|
||||
passes.add(new PrepareForCodeGen());
|
||||
passes.add(new LoopRegionVisitor());
|
||||
passes.add(new ProcessVariables());
|
||||
|
||||
passes.add(new ProcessVariables());
|
||||
passes.add(new PrepareForCodeGen());
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRegions());
|
||||
}
|
||||
|
||||
passes.add(new DependencyCollector());
|
||||
|
||||
passes.add(new RenameVisitor());
|
||||
}
|
||||
return passes;
|
||||
|
||||
@@ -45,6 +45,8 @@ public class ClsSet {
|
||||
|
||||
private static final String STRING_CHARSET = "US-ASCII";
|
||||
|
||||
private static final NClass[] EMPTY_NCLASS_ARRAY = new NClass[0];
|
||||
|
||||
private NClass[] classes;
|
||||
|
||||
public void load(RootNode root) {
|
||||
@@ -93,7 +95,11 @@ public class ClsSet {
|
||||
parents.add(c);
|
||||
}
|
||||
}
|
||||
return parents.toArray(new NClass[parents.size()]);
|
||||
int size = parents.size();
|
||||
if (size == 0) {
|
||||
return EMPTY_NCLASS_ARRAY;
|
||||
}
|
||||
return parents.toArray(new NClass[size]);
|
||||
}
|
||||
|
||||
private static NClass getCls(String fullName, Map<String, NClass> names) {
|
||||
|
||||
@@ -33,6 +33,7 @@ import jadx.core.dex.instructions.InvokeType;
|
||||
import jadx.core.dex.instructions.NewArrayNode;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.FieldArg;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
@@ -122,12 +123,16 @@ public class InsnGen {
|
||||
}
|
||||
|
||||
public void declareVar(CodeWriter code, RegisterArg arg) {
|
||||
if (arg.getSVar().contains(AFlag.FINAL)) {
|
||||
declareVar(code, arg.getSVar().getCodeVar());
|
||||
}
|
||||
|
||||
public void declareVar(CodeWriter code, CodeVar codeVar) {
|
||||
if (codeVar.isFinal()) {
|
||||
code.add("final ");
|
||||
}
|
||||
useType(code, arg.getType());
|
||||
useType(code, codeVar.getType());
|
||||
code.add(' ');
|
||||
code.add(mgen.getNameGen().assignArg(arg));
|
||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
||||
}
|
||||
|
||||
private String lit(LiteralArg arg) {
|
||||
|
||||
@@ -4,8 +4,6 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.android.dx.rop.code.AccessFlags;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.utils.Utils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -13,8 +11,10 @@ import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
@@ -22,8 +22,8 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
|
||||
@@ -108,10 +108,7 @@ public class MethodGen {
|
||||
} else if (args.size() > 2) {
|
||||
args = args.subList(2, args.size());
|
||||
} else {
|
||||
LOG.warn(ErrorsCounter.formatMsg(mth,
|
||||
"Incorrect number of args for enum constructor: " + args.size()
|
||||
+ " (expected >= 2)"
|
||||
));
|
||||
mth.addComment("JADX WARN: Incorrect number of args for enum constructor: " + args.size() + " (expected >= 2)");
|
||||
}
|
||||
}
|
||||
addMethodArguments(code, args);
|
||||
@@ -121,40 +118,48 @@ public class MethodGen {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addMethodArguments(CodeWriter argsCode, List<RegisterArg> args) {
|
||||
private void addMethodArguments(CodeWriter code, List<RegisterArg> args) {
|
||||
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS);
|
||||
int i = 0;
|
||||
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext(); ) {
|
||||
RegisterArg arg = it.next();
|
||||
ArgType argType = arg.getInitType();
|
||||
Iterator<RegisterArg> it = args.iterator();
|
||||
while (it.hasNext()) {
|
||||
RegisterArg mthArg = it.next();
|
||||
SSAVar ssaVar = mthArg.getSVar();
|
||||
CodeVar var;
|
||||
if (ssaVar == null) {
|
||||
// null for abstract or interface methods
|
||||
var = CodeVar.fromMthArg(mthArg);
|
||||
} else {
|
||||
var = ssaVar.getCodeVar();
|
||||
}
|
||||
ArgType argType = var.getType();
|
||||
|
||||
// add argument annotation
|
||||
if (paramsAnnotation != null) {
|
||||
annotationGen.addForParameter(argsCode, paramsAnnotation, i);
|
||||
annotationGen.addForParameter(code, paramsAnnotation, i);
|
||||
}
|
||||
SSAVar argSVar = arg.getSVar();
|
||||
if (argSVar != null && argSVar.contains(AFlag.FINAL)) {
|
||||
argsCode.add("final ");
|
||||
if (var.isFinal()) {
|
||||
code.add("final ");
|
||||
}
|
||||
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
|
||||
// change last array argument to varargs
|
||||
if (argType.isArray()) {
|
||||
ArgType elType = argType.getArrayElement();
|
||||
classGen.useType(argsCode, elType);
|
||||
argsCode.add("...");
|
||||
classGen.useType(code, elType);
|
||||
code.add("...");
|
||||
} else {
|
||||
LOG.warn(ErrorsCounter.formatMsg(mth, "Last argument in varargs method not array"));
|
||||
classGen.useType(argsCode, argType);
|
||||
mth.addComment("JADX INFO: Last argument in varargs method is not array: " + var);
|
||||
classGen.useType(code, argType);
|
||||
}
|
||||
} else {
|
||||
classGen.useType(argsCode, argType);
|
||||
classGen.useType(code, argType);
|
||||
}
|
||||
argsCode.add(' ');
|
||||
argsCode.add(nameGen.assignArg(arg));
|
||||
code.add(' ');
|
||||
code.add(nameGen.assignArg(var));
|
||||
|
||||
i++;
|
||||
if (it.hasNext()) {
|
||||
argsCode.add(", ");
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -11,6 +12,7 @@ import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
@@ -45,7 +47,8 @@ public class NameGen {
|
||||
"java.lang.Float", "f",
|
||||
"java.lang.Long", "l",
|
||||
"java.lang.Double", "d",
|
||||
"java.lang.StringBuilder", "sb"
|
||||
"java.lang.StringBuilder", "sb",
|
||||
"java.lang.Exception", "exc"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,13 +57,13 @@ public class NameGen {
|
||||
this.fallback = fallback;
|
||||
}
|
||||
|
||||
public String assignArg(RegisterArg arg) {
|
||||
String name = makeArgName(arg);
|
||||
public String assignArg(CodeVar var) {
|
||||
String name = makeArgName(var);
|
||||
if (fallback) {
|
||||
return name;
|
||||
}
|
||||
name = getUniqueVarName(name);
|
||||
arg.setName(name);
|
||||
var.setName(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -100,52 +103,50 @@ public class NameGen {
|
||||
return r;
|
||||
}
|
||||
|
||||
private String makeArgName(RegisterArg arg) {
|
||||
private String makeArgName(CodeVar var) {
|
||||
if (fallback) {
|
||||
return getFallbackName(arg);
|
||||
return getFallbackName(var);
|
||||
}
|
||||
if (arg.isThis()) {
|
||||
if (var.isThis()) {
|
||||
return RegisterArg.THIS_ARG_NAME;
|
||||
}
|
||||
String name = arg.getName();
|
||||
String varName = name != null ? name : guessName(arg);
|
||||
String name = var.getName();
|
||||
String varName = name != null ? name : guessName(var);
|
||||
if (NameMapper.isReserved(varName)) {
|
||||
varName = varName + "R";
|
||||
}
|
||||
if (!NameMapper.isValidIdentifier(varName)) {
|
||||
varName = getFallbackName(arg);
|
||||
varName = getFallbackName(var);
|
||||
}
|
||||
return varName;
|
||||
}
|
||||
|
||||
private String getFallbackName(RegisterArg arg) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('r').append(arg.getRegNum());
|
||||
SSAVar sVar = arg.getSVar();
|
||||
if (sVar != null) {
|
||||
sb.append('v').append(sVar.getVersion());
|
||||
}
|
||||
return sb.toString();
|
||||
private String getFallbackName(CodeVar var) {
|
||||
return getFallbackName(var.getSsaVars().get(0).getAssign());
|
||||
}
|
||||
|
||||
private String guessName(RegisterArg arg) {
|
||||
SSAVar sVar = arg.getSVar();
|
||||
if (sVar != null && sVar.getName() == null) {
|
||||
RegisterArg assignArg = sVar.getAssign();
|
||||
InsnNode assignInsn = assignArg.getParentInsn();
|
||||
if (assignInsn != null) {
|
||||
String name = makeNameFromInsn(assignInsn);
|
||||
if (name != null && !NameMapper.isReserved(name)) {
|
||||
assignArg.setName(name);
|
||||
return name;
|
||||
private String getFallbackName(RegisterArg arg) {
|
||||
return "r" + arg.getRegNum();
|
||||
}
|
||||
|
||||
private String guessName(CodeVar var) {
|
||||
List<SSAVar> ssaVars = var.getSsaVars();
|
||||
if (ssaVars != null && !ssaVars.isEmpty()) {
|
||||
// TODO: use all vars for better name generation
|
||||
SSAVar ssaVar = ssaVars.get(0);
|
||||
if (ssaVar != null && ssaVar.getName() == null) {
|
||||
RegisterArg assignArg = ssaVar.getAssign();
|
||||
InsnNode assignInsn = assignArg.getParentInsn();
|
||||
if (assignInsn != null) {
|
||||
String name = makeNameFromInsn(assignInsn);
|
||||
if (name != null && !NameMapper.isReserved(name)) {
|
||||
assignArg.setName(name);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ArgType type = arg.getType();
|
||||
if (!type.isTypeKnown() && arg.getInitType().isTypeKnown()) {
|
||||
type = arg.getInitType();
|
||||
}
|
||||
return makeNameForType(type);
|
||||
return makeNameForType(var.getType());
|
||||
}
|
||||
|
||||
private String makeNameForType(ArgType type) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
@@ -75,7 +76,7 @@ public class RegionGen extends InsnGen {
|
||||
private void declareVars(CodeWriter code, IContainer cont) {
|
||||
DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES);
|
||||
if (declVars != null) {
|
||||
for (RegisterArg v : declVars.getVars()) {
|
||||
for (CodeVar v : declVars.getVars()) {
|
||||
code.startLine();
|
||||
declareVar(code, v);
|
||||
code.add(';');
|
||||
@@ -323,7 +324,8 @@ public class RegionGen extends InsnGen {
|
||||
code.add(' ');
|
||||
InsnArg arg = handler.getArg();
|
||||
if (arg instanceof RegisterArg) {
|
||||
code.add(mgen.getNameGen().assignArg((RegisterArg) arg));
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
code.add(mgen.getNameGen().assignArg(reg.getSVar().getCodeVar()));
|
||||
} else if (arg instanceof NamedArg) {
|
||||
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ public enum AFlag {
|
||||
ANONYMOUS_CONSTRUCTOR,
|
||||
ANONYMOUS_CLASS,
|
||||
THIS,
|
||||
METHOD_ARGUMENT, // RegisterArg attribute for method arguments
|
||||
CUSTOM_DECLARE, // variable for this register don't need declaration
|
||||
|
||||
ELSE_IF_CHAIN,
|
||||
|
||||
|
||||
@@ -25,6 +25,6 @@ public class AttrList<T> implements IAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Utils.listToString(list);
|
||||
return Utils.listToString(list, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
public interface IAttribute {
|
||||
<T extends IAttribute> AType<T> getType();
|
||||
AType<? extends IAttribute> getType();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
/**
|
||||
@@ -13,13 +13,13 @@ import jadx.core.utils.Utils;
|
||||
*/
|
||||
public class DeclareVariablesAttr implements IAttribute {
|
||||
|
||||
private final List<RegisterArg> vars = new LinkedList<>();
|
||||
private final List<CodeVar> vars = new ArrayList<>();
|
||||
|
||||
public Iterable<RegisterArg> getVars() {
|
||||
public Iterable<CodeVar> getVars() {
|
||||
return vars;
|
||||
}
|
||||
|
||||
public void addVar(RegisterArg arg) {
|
||||
public void addVar(CodeVar arg) {
|
||||
vars.add(arg);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class CodeVar {
|
||||
private String name;
|
||||
private ArgType type;
|
||||
private List<SSAVar> ssaVars;
|
||||
|
||||
private boolean isFinal;
|
||||
private boolean isThis;
|
||||
private boolean isDeclared;
|
||||
|
||||
public static CodeVar fromMthArg(RegisterArg mthArg) {
|
||||
CodeVar var = new CodeVar();
|
||||
var.setType(mthArg.getInitType());
|
||||
var.setName(mthArg.getName());
|
||||
var.setDeclared(true);
|
||||
var.setThis(mthArg.isThis());
|
||||
var.setSsaVars(Collections.singletonList(new SSAVar(mthArg.getRegNum(), 0, mthArg)));
|
||||
return var;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(ArgType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public List<SSAVar> getSsaVars() {
|
||||
return ssaVars;
|
||||
}
|
||||
|
||||
public void setSsaVars(List<SSAVar> ssaVars) {
|
||||
if (ssaVars.size() == 1) {
|
||||
this.ssaVars = Collections.singletonList(ssaVars.get(0));
|
||||
} else {
|
||||
this.ssaVars = ssaVars;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFinal() {
|
||||
return isFinal;
|
||||
}
|
||||
|
||||
public void setFinal(boolean aFinal) {
|
||||
isFinal = aFinal;
|
||||
}
|
||||
|
||||
public boolean isThis() {
|
||||
return isThis;
|
||||
}
|
||||
|
||||
public void setThis(boolean aThis) {
|
||||
isThis = aThis;
|
||||
}
|
||||
|
||||
public boolean isDeclared() {
|
||||
return isDeclared;
|
||||
}
|
||||
|
||||
public void setDeclared(boolean declared) {
|
||||
isDeclared = declared;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge flags with OR operator
|
||||
*/
|
||||
public void mergeFlagsFrom(CodeVar other) {
|
||||
if (other.isDeclared()) {
|
||||
setDeclared(true);
|
||||
}
|
||||
if (other.isThis()) {
|
||||
setThis(true);
|
||||
}
|
||||
if (other.isFinal()) {
|
||||
setFinal(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (isFinal ? "final " : "") + type + " " + name;
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
@Override
|
||||
public void setType(ArgType type) {
|
||||
if (sVar != null) {
|
||||
sVar.getTypeInfo().setType(type);
|
||||
sVar.setType(type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,10 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
return regNum == arg.regNum && type.equals(arg.type);
|
||||
}
|
||||
|
||||
public boolean sameCodeVar(RegisterArg arg) {
|
||||
return this.getSVar().getCodeVar() == arg.getSVar().getCodeVar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return regNum;
|
||||
|
||||
@@ -15,19 +15,22 @@ import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||
import jadx.core.dex.instructions.PhiInsn;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInfo;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class SSAVar extends AttrNode {
|
||||
private final int regNum;
|
||||
private final int version;
|
||||
|
||||
@NotNull
|
||||
private RegisterArg assign;
|
||||
private final List<RegisterArg> useList = new ArrayList<>(2);
|
||||
@Nullable
|
||||
private PhiInsn usedInPhi;
|
||||
|
||||
private TypeInfo typeInfo = new TypeInfo();
|
||||
private VarName varName;
|
||||
|
||||
@Nullable("Set in EliminatePhiNodes pass")
|
||||
private CodeVar codeVar;
|
||||
|
||||
public SSAVar(int regNum, int v, @NotNull RegisterArg assign) {
|
||||
this.regNum = regNum;
|
||||
@@ -62,6 +65,13 @@ public class SSAVar extends AttrNode {
|
||||
return useList.size();
|
||||
}
|
||||
|
||||
public void setType(ArgType type) {
|
||||
typeInfo.setType(type);
|
||||
if (codeVar != null) {
|
||||
codeVar.setType(type);
|
||||
}
|
||||
}
|
||||
|
||||
public void use(RegisterArg arg) {
|
||||
if (arg.getSVar() != null) {
|
||||
arg.getSVar().removeUse(arg);
|
||||
@@ -101,34 +111,38 @@ public class SSAVar extends AttrNode {
|
||||
|
||||
public void setName(String name) {
|
||||
if (name != null) {
|
||||
if (varName == null) {
|
||||
varName = new VarName();
|
||||
if (codeVar == null) {
|
||||
throw new JadxRuntimeException("CodeVar not initialized for name set in SSAVar: " + this);
|
||||
}
|
||||
varName.setName(name);
|
||||
codeVar.setName(name);
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
if (varName == null) {
|
||||
if (codeVar == null) {
|
||||
return null;
|
||||
}
|
||||
return varName.getName();
|
||||
}
|
||||
|
||||
public VarName getVarName() {
|
||||
return varName;
|
||||
}
|
||||
|
||||
public void setVarName(VarName varName) {
|
||||
this.varName = varName;
|
||||
return codeVar.getName();
|
||||
}
|
||||
|
||||
public TypeInfo getTypeInfo() {
|
||||
return typeInfo;
|
||||
}
|
||||
|
||||
public void setTypeInfo(TypeInfo typeInfo) {
|
||||
this.typeInfo = typeInfo;
|
||||
@NotNull
|
||||
public CodeVar getCodeVar() {
|
||||
if (codeVar == null) {
|
||||
throw new JadxRuntimeException("Code variable not set in " + this);
|
||||
}
|
||||
return codeVar;
|
||||
}
|
||||
|
||||
public void setCodeVar(@NotNull CodeVar codeVar) {
|
||||
this.codeVar = codeVar;
|
||||
}
|
||||
|
||||
public boolean isCodeVarSet() {
|
||||
return codeVar != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -148,9 +162,15 @@ public class SSAVar extends AttrNode {
|
||||
return 31 * regNum + version;
|
||||
}
|
||||
|
||||
public String toShortString() {
|
||||
return "r" + regNum + ":" + version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "r" + regNum + ":" + version + " " + typeInfo.getType();
|
||||
return toShortString()
|
||||
+ (StringUtils.notEmpty(getName()) ? " '" + getName() + "' " : "")
|
||||
+ " " + typeInfo.getType();
|
||||
}
|
||||
|
||||
public String getDetailedVarInfo(MethodNode mth) {
|
||||
|
||||
@@ -230,7 +230,9 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
|
||||
}
|
||||
argsList = new ArrayList<>(args.size());
|
||||
for (ArgType arg : args) {
|
||||
argsList.add(InsnArg.typeImmutableReg(pos, arg));
|
||||
TypeImmutableArg regArg = InsnArg.typeImmutableReg(pos, arg);
|
||||
regArg.add(AFlag.METHOD_ARGUMENT);
|
||||
argsList.add(regArg);
|
||||
pos += arg.getRegCount();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ public final class ForEachLoop extends LoopType {
|
||||
public ForEachLoop(RegisterArg varArg, InsnArg iterableArg) {
|
||||
this.varArg = varArg;
|
||||
this.iterableArg = iterableArg;
|
||||
|
||||
// will be declared at codegen
|
||||
varArg.getSVar().getCodeVar().setDeclared(true);
|
||||
}
|
||||
|
||||
public RegisterArg getVarArg() {
|
||||
|
||||
@@ -236,6 +236,7 @@ public class ModVisitor extends AbstractVisitor {
|
||||
SSAVar sVar = reg.getSVar();
|
||||
if (sVar != null) {
|
||||
sVar.add(AFlag.FINAL);
|
||||
sVar.getCodeVar().setFinal(true);
|
||||
sVar.add(AFlag.DONT_INLINE);
|
||||
}
|
||||
reg.add(AFlag.SKIP_ARG);
|
||||
|
||||
@@ -14,6 +14,7 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
/**
|
||||
@@ -24,7 +25,7 @@ import jadx.core.utils.exceptions.JadxException;
|
||||
@JadxVisitor(
|
||||
name = "PrepareForCodeGen",
|
||||
desc = "Prepare instructions for code generation pass",
|
||||
runAfter = {CodeShrinker.class, ClassModifier.class}
|
||||
runAfter = {CodeShrinker.class, ClassModifier.class, ProcessVariables.class}
|
||||
)
|
||||
public class PrepareForCodeGen extends AbstractVisitor {
|
||||
|
||||
@@ -141,11 +142,11 @@ public class PrepareForCodeGen extends AbstractVisitor {
|
||||
replace = true;
|
||||
} else if (arg.isRegister()) {
|
||||
RegisterArg regArg = (RegisterArg) arg;
|
||||
replace = res.equalRegisterAndType(regArg);
|
||||
replace = res.sameCodeVar(regArg);
|
||||
}
|
||||
if (replace) {
|
||||
insn.add(AFlag.ARITH_ONEARG);
|
||||
insn.getResult().mergeName(arg);
|
||||
// insn.getResult().mergeName(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ public class BlockExceptionHandler extends AbstractVisitor {
|
||||
resArg.copyAttributesFrom(me);
|
||||
me.setResult(resArg);
|
||||
me.add(AFlag.DONT_INLINE);
|
||||
resArg.add(AFlag.CUSTOM_DECLARE);
|
||||
excHandler.setArg(resArg);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -34,10 +34,16 @@ import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.regions.loops.LoopType;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.CodeShrinker;
|
||||
import jadx.core.dex.visitors.JadxVisitor;
|
||||
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "LoopRegionVisitor",
|
||||
desc = "Convert 'while' loops to 'for' loops (indexed or for-each)",
|
||||
runBefore = {ProcessVariables.class}
|
||||
)
|
||||
public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LoopRegionVisitor.class);
|
||||
|
||||
@@ -65,9 +71,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
if (checkForIndexedLoop(mth, loopRegion, condition)) {
|
||||
return;
|
||||
}
|
||||
if (checkIterableForEach(mth, loopRegion, condition)) {
|
||||
return;
|
||||
}
|
||||
checkIterableForEach(mth, loopRegion, condition);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,9 +123,9 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
LoopType arrForEach = checkArrayForEach(mth, initInsn, incrInsn, condition);
|
||||
if (arrForEach != null) {
|
||||
loopRegion.setType(arrForEach);
|
||||
return true;
|
||||
} else {
|
||||
loopRegion.setType(new ForLoop(initInsn, incrInsn));
|
||||
}
|
||||
loopRegion.setType(new ForLoop(initInsn, incrInsn));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -189,10 +193,15 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
|
||||
// array for each loop confirmed
|
||||
len.add(AFlag.DONT_GENERATE);
|
||||
incrInsn.getResult().add(AFlag.DONT_GENERATE);
|
||||
condArg.add(AFlag.DONT_GENERATE);
|
||||
bCondArg.add(AFlag.DONT_GENERATE);
|
||||
arrGetInsn.add(AFlag.DONT_GENERATE);
|
||||
InstructionRemover.unbindInsn(mth, len);
|
||||
|
||||
// inline array variable
|
||||
if (arrayArg.isRegister()) {
|
||||
((RegisterArg) arrayArg).getSVar().removeUse((RegisterArg) arrGetInsn.getArg(0));
|
||||
}
|
||||
CodeShrinker.shrinkMethod(mth);
|
||||
if (arrGetInsn.contains(AFlag.WRAPPED)) {
|
||||
InsnArg wrapArg = BlockUtils.searchWrappedInsnParent(mth, arrGetInsn);
|
||||
@@ -215,18 +224,15 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
if (sVar == null || sVar.isUsedInPhi()) {
|
||||
return false;
|
||||
}
|
||||
List<RegisterArg> useList = sVar.getUseList();
|
||||
List<RegisterArg> itUseList = sVar.getUseList();
|
||||
InsnNode assignInsn = iteratorArg.getAssignInsn();
|
||||
if (useList.size() != 2
|
||||
|| assignInsn == null
|
||||
|| !checkInvoke(assignInsn, null, "iterator()Ljava/util/Iterator;", 0)) {
|
||||
if (itUseList.size() != 2 || !checkInvoke(assignInsn, null, "iterator()Ljava/util/Iterator;", 0)) {
|
||||
return false;
|
||||
}
|
||||
InsnArg iterableArg = assignInsn.getArg(0);
|
||||
InsnNode hasNextCall = useList.get(0).getParentInsn();
|
||||
InsnNode nextCall = useList.get(1).getParentInsn();
|
||||
if (hasNextCall == null || nextCall == null
|
||||
|| !checkInvoke(hasNextCall, "java.util.Iterator", "hasNext()Z", 0)
|
||||
InsnNode hasNextCall = itUseList.get(0).getParentInsn();
|
||||
InsnNode nextCall = itUseList.get(1).getParentInsn();
|
||||
if (!checkInvoke(hasNextCall, "java.util.Iterator", "hasNext()Z", 0)
|
||||
|| !checkInvoke(nextCall, "java.util.Iterator", "next()Ljava/lang/Object;", 0)) {
|
||||
return false;
|
||||
}
|
||||
@@ -269,6 +275,9 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
for (InsnNode insnNode : toSkip) {
|
||||
insnNode.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
for (RegisterArg itArg : itUseList) {
|
||||
itArg.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
loopRegion.setType(new ForEachLoop(iterVar, iterableArg));
|
||||
return true;
|
||||
}
|
||||
@@ -314,6 +323,9 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
* Check if instruction is a interface invoke with corresponding parameters.
|
||||
*/
|
||||
private static boolean checkInvoke(InsnNode insn, String declClsFullName, String mthId, int argsCount) {
|
||||
if (insn == null) {
|
||||
return false;
|
||||
}
|
||||
if (insn.getType() == InsnType.INVOKE) {
|
||||
InvokeNode inv = (InvokeNode) insn;
|
||||
MethodInfo callMth = inv.getCallMth();
|
||||
|
||||
@@ -1,331 +0,0 @@
|
||||
package jadx.core.dex.visitors.regions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.VarName;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.loops.ForLoop;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.regions.loops.LoopType;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
public class ProcessVariables extends AbstractVisitor {
|
||||
|
||||
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 "r" + regNum + ":" + type;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Usage {
|
||||
private RegisterArg arg;
|
||||
private VarName varName;
|
||||
private IRegion argRegion;
|
||||
private final Set<IRegion> uses = new LinkedHashSet<>(2);
|
||||
private final Set<IRegion> assigns = new LinkedHashSet<>(2);
|
||||
|
||||
public void setArg(RegisterArg arg) {
|
||||
this.arg = arg;
|
||||
}
|
||||
|
||||
public RegisterArg getArg() {
|
||||
return arg;
|
||||
}
|
||||
|
||||
public VarName getVarName() {
|
||||
return varName;
|
||||
}
|
||||
|
||||
public void setVarName(VarName varName) {
|
||||
this.varName = varName;
|
||||
}
|
||||
|
||||
public void setArgRegion(IRegion argRegion) {
|
||||
this.argRegion = argRegion;
|
||||
}
|
||||
|
||||
public IRegion getArgRegion() {
|
||||
return argRegion;
|
||||
}
|
||||
|
||||
public Set<IRegion> getAssigns() {
|
||||
return assigns;
|
||||
}
|
||||
|
||||
public Set<IRegion> getUseRegions() {
|
||||
return uses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return arg + ", a:" + assigns + ", u:" + uses;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CollectUsageRegionVisitor extends TracedRegionVisitor {
|
||||
private final List<RegisterArg> args;
|
||||
private final Map<Variable, Usage> usageMap;
|
||||
|
||||
public CollectUsageRegionVisitor(Map<Variable, Usage> usageMap) {
|
||||
this.usageMap = usageMap;
|
||||
this.args = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processBlockTraced(MethodNode mth, IBlock container, IRegion curRegion) {
|
||||
regionProcess(curRegion);
|
||||
int len = container.getInstructions().size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
InsnNode insn = container.getInstructions().get(i);
|
||||
if (insn.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
args.clear();
|
||||
processInsn(insn, curRegion);
|
||||
}
|
||||
}
|
||||
|
||||
private void regionProcess(IRegion region) {
|
||||
if (region instanceof LoopRegion) {
|
||||
LoopRegion loopRegion = (LoopRegion) region;
|
||||
LoopType loopType = loopRegion.getType();
|
||||
if (loopType instanceof ForLoop) {
|
||||
ForLoop forLoop = (ForLoop) loopType;
|
||||
processInsn(forLoop.getInitInsn(), region);
|
||||
processInsn(forLoop.getIncrInsn(), region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processInsn(InsnNode insn, IRegion curRegion) {
|
||||
if (insn == null) {
|
||||
return;
|
||||
}
|
||||
// result
|
||||
RegisterArg result = insn.getResult();
|
||||
if (result != null && result.isRegister()) {
|
||||
Usage u = addToUsageMap(result, usageMap);
|
||||
if (u.getArg() == null) {
|
||||
u.setArg(result);
|
||||
u.setArgRegion(curRegion);
|
||||
}
|
||||
u.getAssigns().add(curRegion);
|
||||
}
|
||||
// args
|
||||
args.clear();
|
||||
insn.getRegisterArgs(args);
|
||||
for (RegisterArg arg : args) {
|
||||
Usage u = addToUsageMap(arg, usageMap);
|
||||
u.getUseRegions().add(curRegion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
List<RegisterArg> mthArguments = mth.getArguments(true);
|
||||
|
||||
Map<Variable, Usage> usageMap = new LinkedHashMap<>();
|
||||
for (RegisterArg arg : mthArguments) {
|
||||
addToUsageMap(arg, usageMap);
|
||||
}
|
||||
|
||||
// collect all variables usage
|
||||
IRegionVisitor collect = new CollectUsageRegionVisitor(usageMap);
|
||||
DepthRegionTraversal.traverse(mth, collect);
|
||||
|
||||
// reduce assigns map
|
||||
for (RegisterArg arg : mthArguments) {
|
||||
usageMap.remove(new Variable(arg));
|
||||
}
|
||||
|
||||
Iterator<Entry<Variable, Usage>> umIt = usageMap.entrySet().iterator();
|
||||
while (umIt.hasNext()) {
|
||||
Entry<Variable, Usage> entry = umIt.next();
|
||||
Usage u = entry.getValue();
|
||||
// if no assigns => remove
|
||||
if (u.getAssigns().isEmpty()) {
|
||||
umIt.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
// variable declared at 'catch' clause
|
||||
InsnNode parentInsn = u.getArg().getParentInsn();
|
||||
if (parentInsn == null || parentInsn.getType() == InsnType.MOVE_EXCEPTION) {
|
||||
umIt.remove();
|
||||
}
|
||||
}
|
||||
if (usageMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Iterator<Entry<Variable, Usage>> it = usageMap.entrySet().iterator(); it.hasNext(); ) {
|
||||
Entry<Variable, Usage> entry = it.next();
|
||||
Usage u = entry.getValue();
|
||||
// check if variable can be declared at current assigns
|
||||
for (IRegion assignRegion : u.getAssigns()) {
|
||||
if (u.getArgRegion() == assignRegion
|
||||
&& canDeclareInRegion(u, assignRegion)
|
||||
&& declareAtAssign(u)) {
|
||||
it.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (usageMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// apply
|
||||
for (Entry<Variable, Usage> entry : usageMap.entrySet()) {
|
||||
Usage u = entry.getValue();
|
||||
|
||||
// find region which contain all usage regions
|
||||
Set<IRegion> set = u.getUseRegions();
|
||||
for (Iterator<IRegion> it = set.iterator(); it.hasNext(); ) {
|
||||
IRegion r = it.next();
|
||||
IRegion parent = r.getParent();
|
||||
if (parent != null && set.contains(parent)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
IRegion region = null;
|
||||
if (!set.isEmpty()) {
|
||||
region = set.iterator().next();
|
||||
} else if (!u.getAssigns().isEmpty()) {
|
||||
region = u.getAssigns().iterator().next();
|
||||
}
|
||||
if (region == null) {
|
||||
continue;
|
||||
}
|
||||
IRegion parent = region;
|
||||
boolean declared = false;
|
||||
while (parent != null) {
|
||||
if (canDeclareInRegion(u, region)) {
|
||||
declareVar(region, u.getArg());
|
||||
declared = true;
|
||||
break;
|
||||
}
|
||||
region = parent;
|
||||
parent = region.getParent();
|
||||
}
|
||||
if (!declared) {
|
||||
declareVar(mth.getRegion(), u.getArg());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Usage addToUsageMap(RegisterArg arg, Map<Variable, Usage> usageMap) {
|
||||
Variable varId = new Variable(arg);
|
||||
Usage usage = usageMap.computeIfAbsent(varId, v -> new Usage());
|
||||
// merge variables names
|
||||
if (usage.getVarName() == null) {
|
||||
VarName argVN = arg.getSVar().getVarName();
|
||||
if (argVN == null) {
|
||||
argVN = new VarName();
|
||||
arg.getSVar().setVarName(argVN);
|
||||
}
|
||||
usage.setVarName(argVN);
|
||||
} else {
|
||||
arg.getSVar().setVarName(usage.getVarName());
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
|
||||
private static boolean declareAtAssign(Usage u) {
|
||||
RegisterArg arg = u.getArg();
|
||||
InsnNode parentInsn = arg.getParentInsn();
|
||||
if (parentInsn == null) {
|
||||
return false;
|
||||
}
|
||||
if (!arg.equals(parentInsn.getResult())) {
|
||||
return false;
|
||||
}
|
||||
parentInsn.add(AFlag.DECLARE_VAR);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void declareVar(IContainer region, RegisterArg arg) {
|
||||
DeclareVariablesAttr dv = region.get(AType.DECLARE_VARIABLES);
|
||||
if (dv == null) {
|
||||
dv = new DeclareVariablesAttr();
|
||||
region.addAttr(dv);
|
||||
}
|
||||
dv.addVar(arg);
|
||||
}
|
||||
|
||||
private static boolean canDeclareInRegion(Usage u, IRegion region) {
|
||||
// workaround for declare variables used in several loops
|
||||
if (region instanceof LoopRegion) {
|
||||
for (IRegion r : u.getAssigns()) {
|
||||
if (!RegionUtils.isRegionContainsRegion(region, r)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// can't declare in else-if chain between 'else' and next 'if'
|
||||
if (region.contains(AFlag.ELSE_IF_CHAIN)) {
|
||||
return false;
|
||||
}
|
||||
// TODO: make index for faster search
|
||||
return isAllRegionsAfter(region, u.getAssigns())
|
||||
&& isAllRegionsAfter(region, u.getUseRegions());
|
||||
}
|
||||
|
||||
private static boolean isAllRegionsAfter(IRegion region, Set<IRegion> others) {
|
||||
for (IRegion r : others) {
|
||||
if (!RegionUtils.isRegionContainsRegion(region, r)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ import jadx.core.utils.exceptions.JadxException;
|
||||
public class RegionMakerVisitor extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RegionMakerVisitor.class);
|
||||
|
||||
private static final PostRegionVisitor POST_REGION_VISITOR = new PostRegionVisitor();
|
||||
private static final IRegionVisitor POST_REGION_VISITOR = new PostRegionVisitor();
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package jadx.core.dex.visitors.regions.variables;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.loops.ForLoop;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.regions.loops.LoopType;
|
||||
import jadx.core.dex.visitors.regions.TracedRegionVisitor;
|
||||
|
||||
class CollectUsageRegionVisitor extends TracedRegionVisitor {
|
||||
private final List<RegisterArg> args;
|
||||
private final Map<SSAVar, VarUsage> usageMap;
|
||||
|
||||
public CollectUsageRegionVisitor() {
|
||||
this.usageMap = new LinkedHashMap<>();
|
||||
this.args = new ArrayList<>();
|
||||
}
|
||||
|
||||
public Map<SSAVar, VarUsage> getUsageMap() {
|
||||
return usageMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processBlockTraced(MethodNode mth, IBlock block, IRegion curRegion) {
|
||||
UsePlace usePlace = new UsePlace(curRegion, block);
|
||||
regionProcess(usePlace);
|
||||
int len = block.getInstructions().size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
InsnNode insn = block.getInstructions().get(i);
|
||||
if (insn.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
processInsn(insn, usePlace);
|
||||
}
|
||||
}
|
||||
|
||||
private void regionProcess(UsePlace usePlace) {
|
||||
IRegion region = usePlace.getRegion();
|
||||
if (region instanceof LoopRegion) {
|
||||
LoopRegion loopRegion = (LoopRegion) region;
|
||||
LoopType loopType = loopRegion.getType();
|
||||
if (loopType instanceof ForLoop) {
|
||||
ForLoop forLoop = (ForLoop) loopType;
|
||||
processInsn(forLoop.getInitInsn(), usePlace);
|
||||
processInsn(forLoop.getIncrInsn(), usePlace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processInsn(InsnNode insn, UsePlace usePlace) {
|
||||
if (insn == null) {
|
||||
return;
|
||||
}
|
||||
// result
|
||||
RegisterArg result = insn.getResult();
|
||||
if (result != null && result.isRegister()) {
|
||||
if (!result.contains(AFlag.DONT_GENERATE)) {
|
||||
VarUsage usage = getUsage(result.getSVar());
|
||||
usage.getAssigns().add(usePlace);
|
||||
}
|
||||
}
|
||||
// args
|
||||
args.clear();
|
||||
insn.getRegisterArgs(args);
|
||||
for (RegisterArg arg : args) {
|
||||
if (arg.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
VarUsage usage = getUsage(arg.getSVar());
|
||||
usage.getUses().add(usePlace);
|
||||
}
|
||||
}
|
||||
|
||||
private VarUsage getUsage(SSAVar ssaVar) {
|
||||
return usageMap.computeIfAbsent(ssaVar, VarUsage::new);
|
||||
}
|
||||
}
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
package jadx.core.dex.visitors.regions.variables;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.regions.DepthRegionTraversal;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
public class ProcessVariables extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ProcessVariables.class);
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
if (mth.isNoCode() || mth.getSVars().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<CodeVar> codeVars = collectCodeVars(mth);
|
||||
if (codeVars.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// TODO: reduce code vars by name if debug info applied. Need checks for variable scopes before reduce
|
||||
|
||||
// collect all variables usage
|
||||
CollectUsageRegionVisitor usageCollector = new CollectUsageRegionVisitor();
|
||||
DepthRegionTraversal.traverse(mth, usageCollector);
|
||||
Map<SSAVar, VarUsage> ssaUsageMap = usageCollector.getUsageMap();
|
||||
if (ssaUsageMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<CodeVar, List<VarUsage>> codeVarUsage = mergeUsageMaps(codeVars, ssaUsageMap);
|
||||
|
||||
for (Entry<CodeVar, List<VarUsage>> entry : codeVarUsage.entrySet()) {
|
||||
declareVar(mth, entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void declareVar(MethodNode mth, CodeVar codeVar, List<VarUsage> usageList) {
|
||||
if (codeVar.isDeclared()) {
|
||||
return;
|
||||
}
|
||||
|
||||
VarUsage mergedUsage = new VarUsage(null);
|
||||
for (VarUsage varUsage : usageList) {
|
||||
mergedUsage.getAssigns().addAll(varUsage.getAssigns());
|
||||
mergedUsage.getUses().addAll(varUsage.getUses());
|
||||
}
|
||||
if (mergedUsage.getAssigns().isEmpty() && mergedUsage.getUses().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if variable can be declared at one of assigns
|
||||
if (checkDeclareAtAssign(usageList, mergedUsage)) {
|
||||
return;
|
||||
}
|
||||
// search closest region for declare
|
||||
if (searchDeclareRegion(mergedUsage, codeVar)) {
|
||||
return;
|
||||
}
|
||||
// region not found, declare at method start
|
||||
declareVarInRegion(mth.getRegion(), codeVar);
|
||||
}
|
||||
|
||||
private List<CodeVar> collectCodeVars(MethodNode mth) {
|
||||
Map<CodeVar, List<SSAVar>> codeVars = new LinkedHashMap<>();
|
||||
for (SSAVar ssaVar : mth.getSVars()) {
|
||||
if (ssaVar.getCodeVar().isThis()) {
|
||||
continue;
|
||||
}
|
||||
CodeVar codeVar = ssaVar.getCodeVar();
|
||||
List<SSAVar> list = codeVars.computeIfAbsent(codeVar, k -> new ArrayList<>());
|
||||
list.add(ssaVar);
|
||||
}
|
||||
|
||||
for (Entry<CodeVar, List<SSAVar>> entry : codeVars.entrySet()) {
|
||||
CodeVar codeVar = entry.getKey();
|
||||
List<SSAVar> list = entry.getValue();
|
||||
for (SSAVar ssaVar : list) {
|
||||
CodeVar localCodeVar = ssaVar.getCodeVar();
|
||||
codeVar.mergeFlagsFrom(localCodeVar);
|
||||
}
|
||||
if (list.size() > 1) {
|
||||
for (SSAVar ssaVar : list) {
|
||||
ssaVar.setCodeVar(codeVar);
|
||||
}
|
||||
}
|
||||
codeVar.setSsaVars(list);
|
||||
}
|
||||
return new ArrayList<>(codeVars.keySet());
|
||||
}
|
||||
|
||||
private Map<CodeVar, List<VarUsage>> mergeUsageMaps(List<CodeVar> codeVars, Map<SSAVar, VarUsage> ssaUsageMap) {
|
||||
Map<CodeVar, List<VarUsage>> codeVarUsage = new LinkedHashMap<>(codeVars.size());
|
||||
for (CodeVar codeVar : codeVars) {
|
||||
List<VarUsage> list = new ArrayList<>();
|
||||
for (SSAVar ssaVar : codeVar.getSsaVars()) {
|
||||
VarUsage usage = ssaUsageMap.get(ssaVar);
|
||||
if (usage != null) {
|
||||
list.add(usage);
|
||||
}
|
||||
}
|
||||
if (!list.isEmpty()) {
|
||||
codeVarUsage.put(codeVar, list);
|
||||
}
|
||||
}
|
||||
return codeVarUsage;
|
||||
}
|
||||
|
||||
private boolean checkDeclareAtAssign(List<VarUsage> list, VarUsage mergedUsage) {
|
||||
if (mergedUsage.getAssigns().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (VarUsage u : list) {
|
||||
for (UsePlace assign : u.getAssigns()) {
|
||||
if (canDeclareAt(mergedUsage, assign)) {
|
||||
return checkDeclareAtAssign(u.getVar());
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean canDeclareAt(VarUsage usage, UsePlace usePlace) {
|
||||
IRegion region = usePlace.getRegion();
|
||||
// workaround for declare variables used in several loops
|
||||
if (region instanceof LoopRegion) {
|
||||
for (UsePlace use : usage.getAssigns()) {
|
||||
if (!RegionUtils.isRegionContainsRegion(region, use.getRegion())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// can't declare in else-if chain between 'else' and next 'if'
|
||||
if (region.contains(AFlag.ELSE_IF_CHAIN)) {
|
||||
return false;
|
||||
}
|
||||
return isAllUseAfter(usePlace, usage.getAssigns())
|
||||
&& isAllUseAfter(usePlace, usage.getUses());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if all {@code usePlaces} are after {@code checkPlace}
|
||||
*/
|
||||
private static boolean isAllUseAfter(UsePlace checkPlace, List<UsePlace> usePlaces) {
|
||||
|
||||
IRegion region = checkPlace.getRegion();
|
||||
IBlock block = checkPlace.getBlock();
|
||||
Set<UsePlace> toCheck = new HashSet<>(usePlaces);
|
||||
boolean blockFound = false;
|
||||
for (IContainer subBlock : region.getSubBlocks()) {
|
||||
if (!blockFound && subBlock == block) {
|
||||
blockFound = true;
|
||||
}
|
||||
if (blockFound) {
|
||||
toCheck.removeIf(usePlace -> isContainerContainsUsePlace(subBlock, usePlace));
|
||||
if (toCheck.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isContainerContainsUsePlace(IContainer subBlock, UsePlace usePlace) {
|
||||
if (subBlock == usePlace.getBlock()) {
|
||||
return true;
|
||||
}
|
||||
if (subBlock instanceof IRegion) {
|
||||
// TODO: make index for faster check
|
||||
return RegionUtils.isRegionContainsRegion(subBlock, usePlace.getRegion());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean checkDeclareAtAssign(SSAVar var) {
|
||||
RegisterArg arg = var.getAssign();
|
||||
InsnNode parentInsn = arg.getParentInsn();
|
||||
if (parentInsn == null) {
|
||||
return false;
|
||||
}
|
||||
if (!arg.equals(parentInsn.getResult())) {
|
||||
return false;
|
||||
}
|
||||
parentInsn.add(AFlag.DECLARE_VAR);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean searchDeclareRegion(VarUsage u, CodeVar codeVar) {
|
||||
/*
|
||||
Set<IRegion> set = u.getUseRegions();
|
||||
for (Iterator<IRegion> it = set.iterator(); it.hasNext(); ) {
|
||||
IRegion r = it.next();
|
||||
IRegion parent = r.getParent();
|
||||
if (parent != null && set.contains(parent)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
IRegion region = null;
|
||||
if (!set.isEmpty()) {
|
||||
region = set.iterator().next();
|
||||
} else if (!u.getAssigns().isEmpty()) {
|
||||
region = u.getAssigns().iterator().next();
|
||||
}
|
||||
if (region == null) {
|
||||
return false;
|
||||
}
|
||||
IRegion parent = region;
|
||||
while (parent != null) {
|
||||
if (canDeclareAt(u, region)) {
|
||||
declareVarInRegion(region, codeVar);
|
||||
return true;
|
||||
}
|
||||
region = parent;
|
||||
parent = region.getParent();
|
||||
}
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void declareVarInRegion(IContainer region, CodeVar var) {
|
||||
if (var.isDeclared()) {
|
||||
LOG.warn("Try to declare already declared variable: {}", var);
|
||||
return;
|
||||
}
|
||||
DeclareVariablesAttr dv = region.get(AType.DECLARE_VARIABLES);
|
||||
if (dv == null) {
|
||||
dv = new DeclareVariablesAttr();
|
||||
region.addAttr(dv);
|
||||
}
|
||||
dv.addVar(var);
|
||||
var.setDeclared(true);
|
||||
}
|
||||
|
||||
private static boolean isAllRegionsAfter(IRegion region, Set<IRegion> others) {
|
||||
IRegion parent = region.getParent();
|
||||
if (parent == null) {
|
||||
return true;
|
||||
}
|
||||
// lazy init for
|
||||
int regionIndex = -2;
|
||||
List<IContainer> subBlocks = Collections.emptyList();
|
||||
for (IRegion r : others) {
|
||||
if (parent == r.getParent()) {
|
||||
// on same level, check order by index
|
||||
if (regionIndex == -2) {
|
||||
subBlocks = parent.getSubBlocks();
|
||||
regionIndex = subBlocks.indexOf(region);
|
||||
}
|
||||
int rIndex = subBlocks.indexOf(r);
|
||||
if (regionIndex > rIndex) {
|
||||
return false;
|
||||
}
|
||||
} else if (!RegionUtils.isRegionContainsRegion(region, r)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package jadx.core.dex.visitors.regions.variables;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
|
||||
public class UsePlace {
|
||||
public final IRegion region;
|
||||
public final IBlock block;
|
||||
|
||||
public UsePlace(IRegion region, IBlock block) {
|
||||
this.region = region;
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
public IRegion getRegion() {
|
||||
return region;
|
||||
}
|
||||
|
||||
public IBlock getBlock() {
|
||||
return block;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
UsePlace usePlace = (UsePlace) o;
|
||||
return Objects.equals(region, usePlace.region)
|
||||
&& Objects.equals(block, usePlace.block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(region, block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UsePlace{" +
|
||||
"region=" + region +
|
||||
", block=" + block +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package jadx.core.dex.visitors.regions.variables;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
|
||||
class VarUsage {
|
||||
private final SSAVar var;
|
||||
private final List<UsePlace> assigns = new ArrayList<>(3);
|
||||
private final List<UsePlace> uses = new ArrayList<>(3);
|
||||
|
||||
VarUsage(SSAVar var) {
|
||||
this.var = var;
|
||||
}
|
||||
|
||||
public SSAVar getVar() {
|
||||
return var;
|
||||
}
|
||||
|
||||
public List<UsePlace> getAssigns() {
|
||||
return assigns;
|
||||
}
|
||||
|
||||
public List<UsePlace> getUses() {
|
||||
return uses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" + (var == null ? "-" : var.toShortString()) + ", a:" + assigns + ", u:" + uses + "}";
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
package jadx.core.dex.visitors.ssa;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.PhiInsn;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
@@ -31,6 +35,7 @@ public class EliminatePhiNodes extends AbstractVisitor {
|
||||
}
|
||||
replaceMergeInstructions(mth);
|
||||
removePhiInstructions(mth);
|
||||
initCodeVars(mth);
|
||||
}
|
||||
|
||||
private static void removePhiInstructions(MethodNode mth) {
|
||||
@@ -56,6 +61,7 @@ public class EliminatePhiNodes extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
LOG.warn("Phi node not removed: {}, mth: {}", phiInsn, mth);
|
||||
phiInsn.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
|
||||
private void replaceMergeInstructions(MethodNode mth) {
|
||||
@@ -128,4 +134,62 @@ public class EliminatePhiNodes extends AbstractVisitor {
|
||||
phiInsn.bindArg(newArg.duplicate(),
|
||||
BlockUtils.selectOtherSafe(assignPred, block.getPredecessors()));
|
||||
}
|
||||
|
||||
private void initCodeVars(MethodNode mth) {
|
||||
for (RegisterArg mthArg : mth.getArguments(true)) {
|
||||
initCodeVar(mthArg.getSVar());
|
||||
}
|
||||
for (SSAVar ssaVar : mth.getSVars()) {
|
||||
initCodeVar(ssaVar);
|
||||
}
|
||||
}
|
||||
|
||||
private void initCodeVar(SSAVar ssaVar) {
|
||||
if (ssaVar.isCodeVarSet()) {
|
||||
return;
|
||||
}
|
||||
CodeVar codeVar = new CodeVar();
|
||||
codeVar.setType(ssaVar.getTypeInfo().getType());
|
||||
RegisterArg assignArg = ssaVar.getAssign();
|
||||
if (assignArg.contains(AFlag.THIS)) {
|
||||
codeVar.setName(RegisterArg.THIS_ARG_NAME);
|
||||
codeVar.setThis(true);
|
||||
}
|
||||
if (assignArg.contains(AFlag.METHOD_ARGUMENT) || assignArg.contains(AFlag.CUSTOM_DECLARE)) {
|
||||
codeVar.setDeclared(true);
|
||||
}
|
||||
|
||||
setCodeVar(ssaVar, codeVar);
|
||||
}
|
||||
|
||||
private static void setCodeVar(SSAVar ssaVar, CodeVar codeVar) {
|
||||
ssaVar.setCodeVar(codeVar);
|
||||
PhiInsn usedInPhi = ssaVar.getUsedInPhi();
|
||||
if (usedInPhi != null) {
|
||||
Set<SSAVar> vars = new HashSet<>();
|
||||
collectConnectedVars(usedInPhi, vars);
|
||||
vars.forEach(var -> {
|
||||
if (var.isCodeVarSet()) {
|
||||
codeVar.mergeFlagsFrom(var.getCodeVar());
|
||||
}
|
||||
var.setCodeVar(codeVar);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void collectConnectedVars(PhiInsn phiInsn, Set<SSAVar> vars) {
|
||||
if (phiInsn == null) {
|
||||
return;
|
||||
}
|
||||
SSAVar resultVar = phiInsn.getResult().getSVar();
|
||||
if (vars.add(resultVar)) {
|
||||
collectConnectedVars(resultVar.getUsedInPhi(), vars);
|
||||
}
|
||||
phiInsn.getArguments().forEach(arg -> {
|
||||
SSAVar sVar = ((RegisterArg) arg).getSVar();
|
||||
if (vars.add(sVar)) {
|
||||
collectConnectedVars(sVar.getUsedInPhi(), vars);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,7 +419,6 @@ public class SSATransform extends AbstractVisitor {
|
||||
return;
|
||||
}
|
||||
arg.add(AFlag.THIS);
|
||||
arg.setName(RegisterArg.THIS_ARG_NAME);
|
||||
// mark all moved 'this'
|
||||
InsnNode parentInsn = arg.getParentInsn();
|
||||
if (parentInsn != null
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package jadx.core.dex.visitors.typeinference;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
@@ -14,5 +16,5 @@ public interface ITypeListener {
|
||||
* @param arg apply suggested type for this arg
|
||||
* @param candidateType suggest new type
|
||||
*/
|
||||
TypeUpdateResult update(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType);
|
||||
TypeUpdateResult update(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, @NotNull ArgType candidateType);
|
||||
}
|
||||
|
||||
+7
-3
@@ -58,9 +58,13 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
TypeInfo typeInfo = var.getTypeInfo();
|
||||
ArgType type = typeInfo.getType();
|
||||
if (type != null && !type.isTypeKnown()) {
|
||||
boolean changed = tryAllTypes(var, type);
|
||||
if (!changed) {
|
||||
mth.addComment("JADX WARNING: type inference failed for: " + var.getDetailedVarInfo(mth));
|
||||
if (var.getAssign().isTypeImmutable()) {
|
||||
mth.addComment("JADX WARNING: type rejected for immutable type: " + var.getDetailedVarInfo(mth));
|
||||
} else {
|
||||
boolean changed = tryAllTypes(var, type);
|
||||
if (!changed) {
|
||||
mth.addComment("JADX WARNING: type inference failed for: " + var.getDetailedVarInfo(mth));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.core.dex.visitors.typeinference;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -30,7 +31,7 @@ import static jadx.core.dex.visitors.typeinference.TypeUpdateResult.SAME;
|
||||
public final class TypeUpdate {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TypeUpdate.class);
|
||||
|
||||
private final TypeUpdateRegistry listenerRegistry;
|
||||
private final Map<InsnType, ITypeListener> listenerRegistry;
|
||||
private final TypeCompare comparator;
|
||||
|
||||
private ThreadLocal<Boolean> applyDebug = new ThreadLocal<>();
|
||||
@@ -79,13 +80,14 @@ public final class TypeUpdate {
|
||||
if (Objects.equals(currentType, candidateType)) {
|
||||
return SAME;
|
||||
}
|
||||
if (arg.isTypeImmutable() && currentType != ArgType.UNKNOWN) {
|
||||
return REJECT;
|
||||
}
|
||||
TypeCompareEnum compareResult = comparator.compareTypes(candidateType, currentType);
|
||||
if (compareResult == TypeCompareEnum.CONFLICT) {
|
||||
return REJECT;
|
||||
}
|
||||
if (arg.isTypeImmutable() && currentType != ArgType.UNKNOWN) {
|
||||
// don't changed type, conflict already rejected
|
||||
return SAME;
|
||||
}
|
||||
if (compareResult == TypeCompareEnum.WIDER || compareResult == TypeCompareEnum.WIDER_BY_GENERIC) {
|
||||
// allow wider types for apply from debug info
|
||||
if (applyDebug.get() != Boolean.TRUE) {
|
||||
@@ -147,21 +149,11 @@ public final class TypeUpdate {
|
||||
if (insn == null) {
|
||||
return SAME;
|
||||
}
|
||||
List<ITypeListener> listeners = listenerRegistry.getListenersForInsn(insn.getType());
|
||||
if (listeners.isEmpty()) {
|
||||
ITypeListener listener = listenerRegistry.get(insn.getType());
|
||||
if (listener == null) {
|
||||
return CHANGED;
|
||||
}
|
||||
boolean allSame = true;
|
||||
for (ITypeListener listener : listeners) {
|
||||
TypeUpdateResult updateResult = listener.update(updateInfo, insn, arg, candidateType);
|
||||
if (updateResult == REJECT) {
|
||||
return REJECT;
|
||||
}
|
||||
if (updateResult != SAME) {
|
||||
allSame = false;
|
||||
}
|
||||
}
|
||||
return allSame ? SAME : CHANGED;
|
||||
return listener.update(updateInfo, insn, arg, candidateType);
|
||||
}
|
||||
|
||||
private boolean inBounds(Set<ITypeBound> bounds, ArgType candidateType) {
|
||||
@@ -227,18 +219,18 @@ public final class TypeUpdate {
|
||||
return false;
|
||||
}
|
||||
|
||||
private TypeUpdateRegistry initListenerRegistry() {
|
||||
TypeUpdateRegistry registry = new TypeUpdateRegistry();
|
||||
registry.add(InsnType.CONST, this::sameFirstArgListener);
|
||||
registry.add(InsnType.MOVE, this::sameFirstArgListener);
|
||||
registry.add(InsnType.PHI, this::allSameListener);
|
||||
registry.add(InsnType.MERGE, this::allSameListener);
|
||||
registry.add(InsnType.AGET, this::arrayGetListener);
|
||||
registry.add(InsnType.APUT, this::arrayPutListener);
|
||||
registry.add(InsnType.IF, this::ifListener);
|
||||
registry.add(InsnType.ARITH, this::suggestAllSameListener);
|
||||
registry.add(InsnType.NEG, this::suggestAllSameListener);
|
||||
registry.add(InsnType.NOT, this::suggestAllSameListener);
|
||||
private Map<InsnType, ITypeListener> initListenerRegistry() {
|
||||
Map<InsnType, ITypeListener> registry = new EnumMap<>(InsnType.class);
|
||||
registry.put(InsnType.CONST, this::sameFirstArgListener);
|
||||
registry.put(InsnType.MOVE, this::sameFirstArgListener);
|
||||
registry.put(InsnType.PHI, this::allSameListener);
|
||||
registry.put(InsnType.MERGE, this::allSameListener);
|
||||
registry.put(InsnType.AGET, this::arrayGetListener);
|
||||
registry.put(InsnType.APUT, this::arrayPutListener);
|
||||
registry.put(InsnType.IF, this::ifListener);
|
||||
registry.put(InsnType.ARITH, this::suggestAllSameListener);
|
||||
registry.put(InsnType.NEG, this::suggestAllSameListener);
|
||||
registry.put(InsnType.NOT, this::suggestAllSameListener);
|
||||
return registry;
|
||||
}
|
||||
|
||||
@@ -314,7 +306,18 @@ public final class TypeUpdate {
|
||||
if (arrayElement == null) {
|
||||
return REJECT;
|
||||
}
|
||||
return updateTypeChecked(updateInfo, putArg, arrayElement);
|
||||
TypeUpdateResult result = updateTypeChecked(updateInfo, putArg, arrayElement);
|
||||
if (result == REJECT) {
|
||||
ArgType putType = putArg.getType();
|
||||
if (putType.isTypeKnown() && putType.isObject()) {
|
||||
TypeCompareEnum compResult = comparator.compareTypes(arrayElement, putType);
|
||||
if (compResult == TypeCompareEnum.WIDER || compResult == TypeCompareEnum.WIDER_BY_GENERIC) {
|
||||
// allow wider result (i.e allow put in Object[] any objects)
|
||||
return CHANGED;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (arrArg == putArg) {
|
||||
return updateTypeChecked(updateInfo, arrArg, ArgType.array(candidateType));
|
||||
@@ -353,6 +356,10 @@ public final class TypeUpdate {
|
||||
return insn.getResult() == arg;
|
||||
}
|
||||
|
||||
public TypeCompare getComparator() {
|
||||
return comparator;
|
||||
}
|
||||
|
||||
public Comparator<ArgType> getArgTypeComparator() {
|
||||
return comparator.getComparator();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
@@ -198,4 +199,11 @@ public class DebugUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void printMap(String desc, Map<?, ?> map) {
|
||||
LOG.debug("Map of {}, size: {}", desc, map.size());
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
LOG.debug(" {} : {}", entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +203,10 @@ public class StringUtils {
|
||||
return str != null && !str.isEmpty();
|
||||
}
|
||||
|
||||
public static boolean isEmpty(String str) {
|
||||
return str == null || str.isEmpty();
|
||||
}
|
||||
|
||||
public static int countMatches(String str, String subStr) {
|
||||
if (str == null || str.isEmpty() || subStr == null || subStr.isEmpty()) {
|
||||
return 0;
|
||||
|
||||
@@ -57,6 +57,9 @@ public class TypeCompareTest {
|
||||
public void compareArrays() {
|
||||
firstIsNarrow(array(CHAR), OBJECT);
|
||||
firstIsNarrow(array(CHAR), array(UNKNOWN));
|
||||
|
||||
firstIsNarrow(array(OBJECT), OBJECT);
|
||||
firstIsNarrow(array(OBJECT), array(UNKNOWN_OBJECT));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -97,6 +97,8 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
||||
if (!decompile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ProcessClass.process(classNode, passes, new CodeGen());
|
||||
for (IDexTreeVisitor visitor : passes) {
|
||||
DepthTraversal.visit(visitor, classNode);
|
||||
}
|
||||
|
||||
@@ -54,7 +54,11 @@ public class TestReturnWrapping extends IntegrationTest {
|
||||
|
||||
assertThat(code, containsString("return 255;"));
|
||||
assertThat(code, containsString("return arg0 + 1;"));
|
||||
assertThat(code, containsString("return i > 128 ? arg0.toString() + ret.toString() : Integer.valueOf(i);"));
|
||||
|
||||
// TODO: reduce code vars by name
|
||||
// assertThat(code, containsString("return i > 128 ? arg0.toString() + ret.toString() : Integer.valueOf(i);"));
|
||||
assertThat(code, containsString("return i2 > 128 ? arg0.toString() + ret.toString() : Integer.valueOf(i2);"));
|
||||
|
||||
assertThat(code, containsString("return arg0 + 2;"));
|
||||
assertThat(code, containsString("arg0 -= 951;"));
|
||||
}
|
||||
|
||||
+10
-18
@@ -1,10 +1,10 @@
|
||||
package jadx.tests.integration;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.visitors.SimplifyVisitor;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.junit.Assert.assertThat;
|
||||
@@ -14,7 +14,7 @@ import static org.junit.Assert.assertThat;
|
||||
*
|
||||
* @author Jan Peter Stotz
|
||||
*/
|
||||
public class SimplifyVisitorStringBuilderTest extends IntegrationTest {
|
||||
public class TestStringBuilderElimination2 extends IntegrationTest {
|
||||
|
||||
public static class TestCls1 {
|
||||
public String test() {
|
||||
@@ -24,10 +24,8 @@ public class SimplifyVisitorStringBuilderTest extends IntegrationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test1() throws JadxException {
|
||||
ClassNode cls = getClassNode(SimplifyVisitorStringBuilderTest.TestCls1.class);
|
||||
SimplifyVisitor visitor = new SimplifyVisitor();
|
||||
visitor.visit(cls);
|
||||
public void test1() {
|
||||
ClassNode cls = getClassNode(TestStringBuilderElimination2.TestCls1.class);
|
||||
String code = cls.getCode().toString();
|
||||
assertThat(code, containsString("return \"[init]\" + \"a1\" + 'c' + 2 + 0 + 1.0f + 2.0d + true;"));
|
||||
}
|
||||
@@ -49,10 +47,8 @@ public class SimplifyVisitorStringBuilderTest extends IntegrationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test2() throws JadxException {
|
||||
ClassNode cls = getClassNode(SimplifyVisitorStringBuilderTest.TestCls2.class);
|
||||
SimplifyVisitor visitor = new SimplifyVisitor();
|
||||
visitor.visit(cls);
|
||||
public void test2() {
|
||||
ClassNode cls = getClassNode(TestStringBuilderElimination2.TestCls2.class);
|
||||
String code = cls.getCode().toString();
|
||||
assertThat(code, containsString("return \"[init]\" + \"a1\" + 'c' + 1 + 2 + 1.0f + 2.0d + true;"));
|
||||
}
|
||||
@@ -68,10 +64,8 @@ public class SimplifyVisitorStringBuilderTest extends IntegrationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test3() throws JadxException {
|
||||
ClassNode cls = getClassNode(SimplifyVisitorStringBuilderTest.TestClsStringUtilsReverse.class);
|
||||
SimplifyVisitor visitor = new SimplifyVisitor();
|
||||
visitor.visit(cls);
|
||||
public void test3() {
|
||||
ClassNode cls = getClassNode(TestClsStringUtilsReverse.class);
|
||||
String code = cls.getCode().toString();
|
||||
assertThat(code, containsString("return new StringBuilder(str).reverse().toString();"));
|
||||
}
|
||||
@@ -84,10 +78,8 @@ public class SimplifyVisitorStringBuilderTest extends IntegrationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChainWithDelete() throws JadxException {
|
||||
public void testChainWithDelete() {
|
||||
ClassNode cls = getClassNode(TestClsChainWithDelete.class);
|
||||
SimplifyVisitor visitor = new SimplifyVisitor();
|
||||
visitor.visit(cls);
|
||||
String code = cls.getCode().toString();
|
||||
assertThat(code, containsString("return new StringBuilder(\"[init]\").append(\"a1\").delete(1, 2).toString();"));
|
||||
}
|
||||
@@ -5,20 +5,23 @@ import org.junit.Test;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestArith extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
|
||||
public void method(int a) {
|
||||
public int test(int a) {
|
||||
a += 2;
|
||||
use(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
public void method2(int a) {
|
||||
public int test2(int a) {
|
||||
a++;
|
||||
use(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
private static void use(int i) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -26,7 +29,19 @@ public class TestArith extends IntegrationTest {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsString("a += 2;"));
|
||||
assertThat(code, containsString("a++;"));
|
||||
// TODO: reduce code vars by name
|
||||
// assertThat(code, containsString("a += 2;"));
|
||||
// assertThat(code, containsString("a++;"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoDebug() {
|
||||
noDebugInfo();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
// TODO: simplify for variables without debug names
|
||||
// assertThat(code, containsString("i += 2;"));
|
||||
// assertThat(code, containsString("i++;"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestArrays3 extends IntegrationTest {
|
||||
@@ -17,12 +18,23 @@ public class TestArrays3 extends IntegrationTest {
|
||||
}
|
||||
|
||||
public void check() {
|
||||
assertThat(test(new byte[]{1, 2}), instanceOf(Object[].class));
|
||||
byte[] inputArr = {1, 2};
|
||||
Object result = test(inputArr);
|
||||
assertThat(result, instanceOf(Object[].class));
|
||||
assertThat(((Object[])result)[0], is(inputArr));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsOne("return new Object[]{bArr};"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoDebug() {
|
||||
noDebugInfo();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
@@ -47,4 +48,14 @@ public class TestGenerics2 extends IntegrationTest {
|
||||
assertThat(code, containsString("WeakReference<V> ref = "));
|
||||
assertThat(code, containsString("return ref.get();"));
|
||||
}
|
||||
|
||||
@Ignore("Make generic info propagation for methods (like Map.get)")
|
||||
@Test
|
||||
public void testDebug() {
|
||||
noDebugInfo();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsString("WeakReference<V> ref = "));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,12 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||
import static jadx.tests.api.utils.JadxMatchers.countString;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestInlineInLoop extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
public static void main(String[] args) throws Exception {
|
||||
public static void main(String[] args) {
|
||||
int a = 0;
|
||||
int b = 4;
|
||||
int c = 0;
|
||||
@@ -37,11 +36,12 @@ public class TestInlineInLoop extends IntegrationTest {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsOne("int c"));
|
||||
assertThat(code, containsOne("c = b + 1"));
|
||||
assertThat(code, countString(2, "c = b;"));
|
||||
assertThat(code, containsOne("b++;"));
|
||||
assertThat(code, containsOne("b = c"));
|
||||
// TODO: remove unused variables from test
|
||||
assertThat(code, containsOne("int c = b + 1"));
|
||||
assertThat(code, containsOne("int c2 = b;"));
|
||||
assertThat(code, containsOne("int c3 = b;"));
|
||||
assertThat(code, containsOne("int b2 = b + 1;"));
|
||||
assertThat(code, containsOne("b = c3"));
|
||||
assertThat(code, containsOne("a++;"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsLines;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestArrayForEach2 extends IntegrationTest {
|
||||
@@ -26,6 +28,8 @@ public class TestArrayForEach2 extends IntegrationTest {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, not(containsString("int ")));
|
||||
|
||||
assertThat(code, containsLines(2,
|
||||
"for (String s : str.split(\"\\n\")) {",
|
||||
indent(1) + "String t = s.trim();",
|
||||
|
||||
@@ -6,6 +6,7 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsLines;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestIndexForLoop extends IntegrationTest {
|
||||
@@ -19,6 +20,13 @@ public class TestIndexForLoop extends IntegrationTest {
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
public void check() {
|
||||
int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
|
||||
assertEquals(0, test(array, 0));
|
||||
assertEquals(6, test(array, 3));
|
||||
assertEquals(36, test(array, 8));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -13,7 +13,8 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.visitors.ssa.LiveVarAnalysis;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestNameAssign2 extends IntegrationTest {
|
||||
@@ -58,7 +59,6 @@ public class TestNameAssign2 extends IntegrationTest {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
// TODO:
|
||||
assertThat(code, containsOne("int id;"));
|
||||
assertThat(code, not(containsString("int id;")));
|
||||
}
|
||||
}
|
||||
|
||||
+9
-4
@@ -14,11 +14,11 @@ public class TestSwitchReturnFromCase extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
public void test(int a) {
|
||||
String s = null;
|
||||
if (a > 1000) {
|
||||
return;
|
||||
}
|
||||
switch (a % 4) {
|
||||
String s = null;
|
||||
switch (a % 10) {
|
||||
case 1:
|
||||
s = "1";
|
||||
break;
|
||||
@@ -30,9 +30,14 @@ public class TestSwitchReturnFromCase extends IntegrationTest {
|
||||
s = "4";
|
||||
break;
|
||||
case 5:
|
||||
break;
|
||||
case 6:
|
||||
return;
|
||||
}
|
||||
s = "5";
|
||||
if (s == null) {
|
||||
s = "5";
|
||||
}
|
||||
System.out.println(s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +46,7 @@ public class TestSwitchReturnFromCase extends IntegrationTest {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsString("switch (a % 4) {"));
|
||||
assertThat(code, containsString("switch (a % 10) {"));
|
||||
assertEquals(5, count(code, "case "));
|
||||
assertEquals(3, count(code, "break;"));
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.junit.Test;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
@@ -22,7 +23,7 @@ public class TestSynchronized extends IntegrationTest {
|
||||
|
||||
public int test2() {
|
||||
synchronized (this.o) {
|
||||
return i;
|
||||
return this.i;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,9 +34,9 @@ public class TestSynchronized extends IntegrationTest {
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, not(containsString("synchronized (this) {")));
|
||||
assertThat(code, containsString("public synchronized boolean test1() {"));
|
||||
assertThat(code, containsString("return this.f"));
|
||||
assertThat(code, containsString("synchronized (this.o) {"));
|
||||
assertThat(code, containsOne("public synchronized boolean test1() {"));
|
||||
assertThat(code, containsOne("return this.f"));
|
||||
assertThat(code, containsOne("synchronized (this.o) {"));
|
||||
|
||||
assertThat(code, not(containsString(indent(3) + ";")));
|
||||
assertThat(code, not(containsString("try {")));
|
||||
|
||||
@@ -63,7 +63,7 @@ public class TestTryCatch3 extends IntegrationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test2() {
|
||||
public void testNoDebug() {
|
||||
noDebugInfo();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
@@ -24,7 +24,7 @@ public class TestTryCatch7 extends IntegrationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
public void testNoDebug() {
|
||||
noDebugInfo();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package jadx.tests.integration.trycatch;
|
||||
|
||||
import java.security.ProviderException;
|
||||
import java.time.DateTimeException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestTryCatchMultiException extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
public void test() {
|
||||
try {
|
||||
System.out.println("Test");
|
||||
} catch (ProviderException | DateTimeException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
noDebugInfo();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
String catchExcVarName = "e";
|
||||
assertThat(code, containsOne("} catch (ProviderException | DateTimeException " + catchExcVarName + ") {"));
|
||||
assertThat(code, containsOne("throw new RuntimeException(" + catchExcVarName + ");"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package jadx.tests.integration.types;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestArrayTypes extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
|
||||
public void test() {
|
||||
Exception e = new Exception();
|
||||
System.out.println(e);
|
||||
use(new Object[]{e});
|
||||
}
|
||||
|
||||
public void use(Object[] arr) {}
|
||||
|
||||
public void check() {
|
||||
test();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsOne("use(new Object[]{e});"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoDebug() {
|
||||
noDebugInfo();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsOne("use(new Object[]{exc});"));
|
||||
}
|
||||
}
|
||||
@@ -10,18 +10,6 @@ import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestTypeResolver5 extends SmaliTest {
|
||||
/*
|
||||
Smali Code equivalent:
|
||||
public static class TestCls {
|
||||
public int test1(int a) {
|
||||
return ~a;
|
||||
}
|
||||
|
||||
public long test2(long b) {
|
||||
return ~b;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
@@ -30,8 +18,7 @@ public class TestTypeResolver5 extends SmaliTest {
|
||||
ClassNode cls = getClassNodeFromSmaliWithPath("types", "TestTypeResolver5");
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
// assertThat(code, containsString("return ~a;"));
|
||||
// assertThat(code, containsString("return ~b;"));
|
||||
assertThat(code, not(containsString("Object string2")));
|
||||
assertThat(code, not(containsString("r1v2")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,4 +28,13 @@ public class TestVariables2 extends IntegrationTest {
|
||||
|
||||
assertThat(code, containsString("Object store = s != null ? s : null;"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoDebug() {
|
||||
noDebugInfo();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsString("Object obj2 = obj != null ? obj : null;"));
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -22,7 +22,7 @@ public class TestVariablesDefinitions extends IntegrationTest {
|
||||
private ClassNode cls;
|
||||
private List<IDexTreeVisitor> passes;
|
||||
|
||||
public void run() {
|
||||
public void test() {
|
||||
try {
|
||||
cls.load();
|
||||
for (IDexTreeVisitor pass : this.passes) {
|
||||
@@ -41,5 +41,6 @@ public class TestVariablesDefinitions extends IntegrationTest {
|
||||
|
||||
assertThat(code, containsOne(indent(3) + "for (IDexTreeVisitor pass : this.passes) {"));
|
||||
assertThat(code, not(containsString("iterator;")));
|
||||
assertThat(code, not(containsString("Iterator")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.class public LTestTypeResolver5;
|
||||
.super Lcom/souq/app/activity/BaseContentActivity;
|
||||
.super Landroid/content/Context;
|
||||
.source "SourceFile"
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
.prologue
|
||||
.line 35
|
||||
invoke-direct {p0}, Lcom/souq/app/activity/BaseContentActivity;-><init>()V
|
||||
invoke-direct {p0}, Landroid/content/Context;-><init>()V
|
||||
|
||||
return-void
|
||||
.end method
|
||||
|
||||
Reference in New Issue
Block a user