mirror of
https://github.com/skylot/jadx.git
synced 2024-11-30 08:00:47 +00:00
fix: implement new type inference approach
This commit is contained in:
parent
6d59f77165
commit
21e11c1d47
1
.gitignore
vendored
1
.gitignore
vendored
@ -29,3 +29,4 @@ jadx-output/
|
|||||||
*.dump
|
*.dump
|
||||||
*.log
|
*.log
|
||||||
*.cfg
|
*.cfg
|
||||||
|
*.orig
|
||||||
|
@ -296,6 +296,10 @@ public final class JadxDecompiler {
|
|||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<IDexTreeVisitor> getPasses() {
|
||||||
|
return passes;
|
||||||
|
}
|
||||||
|
|
||||||
synchronized BinaryXMLParser getXmlParser() {
|
synchronized BinaryXMLParser getXmlParser() {
|
||||||
if (xmlParser == null) {
|
if (xmlParser == null) {
|
||||||
xmlParser = new BinaryXMLParser(root);
|
xmlParser = new BinaryXMLParser(root);
|
||||||
|
@ -13,7 +13,7 @@ import jadx.api.JadxArgs;
|
|||||||
import jadx.core.dex.visitors.ClassModifier;
|
import jadx.core.dex.visitors.ClassModifier;
|
||||||
import jadx.core.dex.visitors.CodeShrinker;
|
import jadx.core.dex.visitors.CodeShrinker;
|
||||||
import jadx.core.dex.visitors.ConstInlineVisitor;
|
import jadx.core.dex.visitors.ConstInlineVisitor;
|
||||||
import jadx.core.dex.visitors.DebugInfoVisitor;
|
import jadx.core.dex.visitors.ConstructorVisitor;
|
||||||
import jadx.core.dex.visitors.DependencyCollector;
|
import jadx.core.dex.visitors.DependencyCollector;
|
||||||
import jadx.core.dex.visitors.DotGraphVisitor;
|
import jadx.core.dex.visitors.DotGraphVisitor;
|
||||||
import jadx.core.dex.visitors.EnumVisitor;
|
import jadx.core.dex.visitors.EnumVisitor;
|
||||||
@ -31,6 +31,8 @@ import jadx.core.dex.visitors.blocksmaker.BlockFinallyExtract;
|
|||||||
import jadx.core.dex.visitors.blocksmaker.BlockFinish;
|
import jadx.core.dex.visitors.blocksmaker.BlockFinish;
|
||||||
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
|
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
|
||||||
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
|
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
|
||||||
|
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
|
||||||
|
import jadx.core.dex.visitors.debuginfo.DebugInfoParseVisitor;
|
||||||
import jadx.core.dex.visitors.regions.CheckRegions;
|
import jadx.core.dex.visitors.regions.CheckRegions;
|
||||||
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
||||||
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
|
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
|
||||||
@ -39,8 +41,7 @@ import jadx.core.dex.visitors.regions.RegionMakerVisitor;
|
|||||||
import jadx.core.dex.visitors.regions.ReturnVisitor;
|
import jadx.core.dex.visitors.regions.ReturnVisitor;
|
||||||
import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
|
import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
|
||||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||||
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
|
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||||
import jadx.core.dex.visitors.typeinference.TypeInference;
|
|
||||||
|
|
||||||
public class Jadx {
|
public class Jadx {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
||||||
@ -59,29 +60,27 @@ public class Jadx {
|
|||||||
if (args.isFallbackMode()) {
|
if (args.isFallbackMode()) {
|
||||||
passes.add(new FallbackModeVisitor());
|
passes.add(new FallbackModeVisitor());
|
||||||
} else {
|
} else {
|
||||||
|
passes.add(new DebugInfoParseVisitor());
|
||||||
passes.add(new BlockSplitter());
|
passes.add(new BlockSplitter());
|
||||||
|
if (args.isRawCFGOutput()) {
|
||||||
|
passes.add(DotGraphVisitor.dumpRaw());
|
||||||
|
}
|
||||||
|
|
||||||
passes.add(new BlockProcessor());
|
passes.add(new BlockProcessor());
|
||||||
passes.add(new BlockExceptionHandler());
|
passes.add(new BlockExceptionHandler());
|
||||||
passes.add(new BlockFinallyExtract());
|
passes.add(new BlockFinallyExtract());
|
||||||
passes.add(new BlockFinish());
|
passes.add(new BlockFinish());
|
||||||
|
|
||||||
passes.add(new SSATransform());
|
passes.add(new SSATransform());
|
||||||
passes.add(new DebugInfoVisitor());
|
passes.add(new ConstructorVisitor());
|
||||||
passes.add(new TypeInference());
|
|
||||||
|
|
||||||
if (args.isRawCFGOutput()) {
|
|
||||||
passes.add(DotGraphVisitor.dumpRaw());
|
|
||||||
}
|
|
||||||
|
|
||||||
passes.add(new ConstInlineVisitor());
|
passes.add(new ConstInlineVisitor());
|
||||||
passes.add(new FinishTypeInference());
|
passes.add(new TypeInferenceVisitor());
|
||||||
passes.add(new EliminatePhiNodes());
|
passes.add(new EliminatePhiNodes());
|
||||||
|
passes.add(new DebugInfoApplyVisitor());
|
||||||
|
|
||||||
passes.add(new ModVisitor());
|
passes.add(new ModVisitor());
|
||||||
|
|
||||||
passes.add(new CodeShrinker());
|
passes.add(new CodeShrinker());
|
||||||
passes.add(new ReSugarCode());
|
passes.add(new ReSugarCode());
|
||||||
|
|
||||||
if (args.isCfgOutput()) {
|
if (args.isCfgOutput()) {
|
||||||
passes.add(DotGraphVisitor.dump());
|
passes.add(DotGraphVisitor.dump());
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
|||||||
public class ClspGraph {
|
public class ClspGraph {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
||||||
|
|
||||||
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<String, Set<String>>());
|
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<>());
|
||||||
private Map<String, NClass> nameMap;
|
private Map<String, NClass> nameMap;
|
||||||
|
|
||||||
private final Set<String> missingClasses = new HashSet<>();
|
private final Set<String> missingClasses = new HashSet<>();
|
||||||
@ -58,6 +58,10 @@ public class ClspGraph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isClsKnown(String fullName) {
|
||||||
|
return nameMap.containsKey(fullName);
|
||||||
|
}
|
||||||
|
|
||||||
private NClass addClass(ClassNode cls) {
|
private NClass addClass(ClassNode cls) {
|
||||||
String rawName = cls.getRawName();
|
String rawName = cls.getRawName();
|
||||||
NClass nClass = new NClass(rawName, -1);
|
NClass nClass = new NClass(rawName, -1);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package jadx.core.codegen;
|
package jadx.core.codegen;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -484,19 +483,7 @@ public class InsnGen {
|
|||||||
case FILL_ARRAY:
|
case FILL_ARRAY:
|
||||||
fallbackOnlyInsn(insn);
|
fallbackOnlyInsn(insn);
|
||||||
FillArrayNode arrayNode = (FillArrayNode) insn;
|
FillArrayNode arrayNode = (FillArrayNode) insn;
|
||||||
Object data = arrayNode.getData();
|
String arrStr = arrayNode.dataToString();
|
||||||
String arrStr;
|
|
||||||
if (data instanceof int[]) {
|
|
||||||
arrStr = Arrays.toString((int[]) data);
|
|
||||||
} else if (data instanceof short[]) {
|
|
||||||
arrStr = Arrays.toString((short[]) data);
|
|
||||||
} else if (data instanceof byte[]) {
|
|
||||||
arrStr = Arrays.toString((byte[]) data);
|
|
||||||
} else if (data instanceof long[]) {
|
|
||||||
arrStr = Arrays.toString((long[]) data);
|
|
||||||
} else {
|
|
||||||
arrStr = "?";
|
|
||||||
}
|
|
||||||
code.add('{').add(arrStr.substring(1, arrStr.length() - 1)).add('}');
|
code.add('{').add(arrStr.substring(1, arrStr.length() - 1)).add('}');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -124,6 +124,7 @@ public class MethodGen {
|
|||||||
int i = 0;
|
int i = 0;
|
||||||
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext(); ) {
|
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext(); ) {
|
||||||
RegisterArg arg = it.next();
|
RegisterArg arg = it.next();
|
||||||
|
ArgType argType = arg.getInitType();
|
||||||
|
|
||||||
// add argument annotation
|
// add argument annotation
|
||||||
if (paramsAnnotation != null) {
|
if (paramsAnnotation != null) {
|
||||||
@ -135,17 +136,16 @@ public class MethodGen {
|
|||||||
}
|
}
|
||||||
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
|
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
|
||||||
// change last array argument to varargs
|
// change last array argument to varargs
|
||||||
ArgType type = arg.getType();
|
if (argType.isArray()) {
|
||||||
if (type.isArray()) {
|
ArgType elType = argType.getArrayElement();
|
||||||
ArgType elType = type.getArrayElement();
|
|
||||||
classGen.useType(argsCode, elType);
|
classGen.useType(argsCode, elType);
|
||||||
argsCode.add("...");
|
argsCode.add("...");
|
||||||
} else {
|
} else {
|
||||||
LOG.warn(ErrorsCounter.formatMsg(mth, "Last argument in varargs method not array"));
|
LOG.warn(ErrorsCounter.formatMsg(mth, "Last argument in varargs method not array"));
|
||||||
classGen.useType(argsCode, arg.getType());
|
classGen.useType(argsCode, argType);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
classGen.useType(argsCode, arg.getType());
|
classGen.useType(argsCode, argType);
|
||||||
}
|
}
|
||||||
argsCode.add(' ');
|
argsCode.add(' ');
|
||||||
argsCode.add(nameGen.assignArg(arg));
|
argsCode.add(nameGen.assignArg(arg));
|
||||||
|
@ -45,6 +45,7 @@ public class NameGen {
|
|||||||
OBJ_ALIAS.put("java.lang.Float", "f");
|
OBJ_ALIAS.put("java.lang.Float", "f");
|
||||||
OBJ_ALIAS.put("java.lang.Long", "l");
|
OBJ_ALIAS.put("java.lang.Long", "l");
|
||||||
OBJ_ALIAS.put("java.lang.Double", "d");
|
OBJ_ALIAS.put("java.lang.Double", "d");
|
||||||
|
OBJ_ALIAS.put("java.lang.StringBuilder", "sb");
|
||||||
}
|
}
|
||||||
|
|
||||||
public NameGen(MethodNode mth, boolean fallback) {
|
public NameGen(MethodNode mth, boolean fallback) {
|
||||||
@ -108,13 +109,22 @@ public class NameGen {
|
|||||||
String name = arg.getName();
|
String name = arg.getName();
|
||||||
String varName = name != null ? name : guessName(arg);
|
String varName = name != null ? name : guessName(arg);
|
||||||
if (NameMapper.isReserved(varName)) {
|
if (NameMapper.isReserved(varName)) {
|
||||||
return varName + "R";
|
varName = varName + "R";
|
||||||
|
}
|
||||||
|
if (!NameMapper.isValidIdentifier(varName)) {
|
||||||
|
varName = getFallbackName(arg);
|
||||||
}
|
}
|
||||||
return varName;
|
return varName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getFallbackName(RegisterArg arg) {
|
private String getFallbackName(RegisterArg arg) {
|
||||||
return "r" + arg.getRegNum();
|
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 guessName(RegisterArg arg) {
|
private String guessName(RegisterArg arg) {
|
||||||
@ -130,7 +140,11 @@ public class NameGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return makeNameForType(arg.getType());
|
ArgType type = arg.getType();
|
||||||
|
if (!type.isTypeKnown() && arg.getInitType().isTypeKnown()) {
|
||||||
|
type = arg.getInitType();
|
||||||
|
}
|
||||||
|
return makeNameForType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String makeNameForType(ArgType type) {
|
private String makeNameForType(ArgType type) {
|
||||||
@ -236,6 +250,9 @@ public class NameGen {
|
|||||||
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
|
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
|
||||||
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
|
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
|
||||||
}
|
}
|
||||||
|
if (name.startsWith("to")) {
|
||||||
|
return fromName(name.substring(2));
|
||||||
|
}
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,9 +47,16 @@ public class TypeGen {
|
|||||||
if (type == null || !type.isTypeKnown()) {
|
if (type == null || !type.isTypeKnown()) {
|
||||||
String n = Long.toString(lit);
|
String n = Long.toString(lit);
|
||||||
if (Math.abs(lit) > 100) {
|
if (Math.abs(lit) > 100) {
|
||||||
n += "; // 0x" + Long.toHexString(lit)
|
StringBuilder sb = new StringBuilder();
|
||||||
+ " float:" + Float.intBitsToFloat((int) lit)
|
sb.append(n).append("(0x").append(Long.toHexString(lit));
|
||||||
+ " double:" + Double.longBitsToDouble(lit);
|
if (type == null || type.contains(PrimitiveType.FLOAT)) {
|
||||||
|
sb.append(", float:").append(Float.intBitsToFloat((int) lit));
|
||||||
|
}
|
||||||
|
if (type == null || type.contains(PrimitiveType.DOUBLE)) {
|
||||||
|
sb.append(", double:").append(Double.longBitsToDouble(lit));
|
||||||
|
}
|
||||||
|
sb.append(')');
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,12 @@ import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr;
|
|||||||
import jadx.core.dex.attributes.nodes.JadxError;
|
import jadx.core.dex.attributes.nodes.JadxError;
|
||||||
import jadx.core.dex.attributes.nodes.JadxWarn;
|
import jadx.core.dex.attributes.nodes.JadxWarn;
|
||||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||||
|
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
||||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||||
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||||
import jadx.core.dex.trycatch.CatchAttr;
|
import jadx.core.dex.trycatch.CatchAttr;
|
||||||
@ -55,6 +57,12 @@ public class AType<T extends IAttribute> {
|
|||||||
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
|
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
|
||||||
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
|
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
|
||||||
|
|
||||||
|
// method
|
||||||
|
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
|
||||||
|
|
||||||
|
// registers
|
||||||
|
public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>();
|
||||||
|
|
||||||
private AType() {
|
private AType() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,12 @@ public abstract class AttrNode implements IAttributeNode {
|
|||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void unloadIfEmpty() {
|
||||||
|
if (storage.isEmpty() && storage != EMPTY_ATTR_STORAGE) {
|
||||||
|
storage = EMPTY_ATTR_STORAGE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(AFlag flag) {
|
public boolean contains(AFlag flag) {
|
||||||
return storage.contains(flag);
|
return storage.contains(flag);
|
||||||
@ -70,21 +76,25 @@ public abstract class AttrNode implements IAttributeNode {
|
|||||||
@Override
|
@Override
|
||||||
public void remove(AFlag flag) {
|
public void remove(AFlag flag) {
|
||||||
storage.remove(flag);
|
storage.remove(flag);
|
||||||
|
unloadIfEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T extends IAttribute> void remove(AType<T> type) {
|
public <T extends IAttribute> void remove(AType<T> type) {
|
||||||
storage.remove(type);
|
storage.remove(type);
|
||||||
|
unloadIfEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeAttr(IAttribute attr) {
|
public void removeAttr(IAttribute attr) {
|
||||||
storage.remove(attr);
|
storage.remove(attr);
|
||||||
|
unloadIfEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearAttributes() {
|
public void clearAttributes() {
|
||||||
storage.clear();
|
storage.clear();
|
||||||
|
unloadIfEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -121,6 +121,6 @@ public class AttributeStorage {
|
|||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return "A:{" + Utils.listToString(list) + "}";
|
return "A[" + Utils.listToString(list) + "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.IAttribute;
|
||||||
|
import jadx.core.dex.visitors.debuginfo.LocalVar;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
|
public class LocalVarsDebugInfoAttr implements IAttribute {
|
||||||
|
private final List<LocalVar> localVars;
|
||||||
|
|
||||||
|
public LocalVarsDebugInfoAttr(List<LocalVar> localVars) {
|
||||||
|
this.localVars = localVars;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<LocalVar> getLocalVars() {
|
||||||
|
return localVars;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<LocalVarsDebugInfoAttr> getType() {
|
||||||
|
return AType.LOCAL_VARS_DEBUG_INFO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Debug Info:\n " + Utils.listToString(localVars, "\n ");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.IAttribute;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.visitors.debuginfo.LocalVar;
|
||||||
|
|
||||||
|
public class RegDebugInfoAttr implements IAttribute {
|
||||||
|
|
||||||
|
private final ArgType type;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public RegDebugInfoAttr(LocalVar var) {
|
||||||
|
this(var.getType(), var.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegDebugInfoAttr(ArgType type, String name) {
|
||||||
|
this.type = type;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getRegType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType getType() {
|
||||||
|
return AType.REG_DEBUG_INFO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RegDebugInfoAttr that = (RegDebugInfoAttr) o;
|
||||||
|
return Objects.equals(type, that.type) && Objects.equals(name, that.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(type, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "D('" + name + "' " + type + ")";
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package jadx.core.dex.instructions;
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
|
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
|
||||||
@ -9,7 +10,6 @@ import jadx.core.dex.instructions.args.ArgType;
|
|||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.LiteralArg;
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||||
import jadx.core.dex.nodes.DexNode;
|
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ public final class FillArrayNode extends InsnNode {
|
|||||||
private ArgType elemType;
|
private ArgType elemType;
|
||||||
|
|
||||||
public FillArrayNode(int resReg, FillArrayDataPayloadDecodedInstruction payload) {
|
public FillArrayNode(int resReg, FillArrayDataPayloadDecodedInstruction payload) {
|
||||||
super(InsnType.FILL_ARRAY, 0);
|
super(InsnType.FILL_ARRAY, 1);
|
||||||
ArgType elType;
|
ArgType elType;
|
||||||
switch (payload.getElementWidthUnit()) {
|
switch (payload.getElementWidthUnit()) {
|
||||||
case 1:
|
case 1:
|
||||||
@ -39,7 +39,7 @@ public final class FillArrayNode extends InsnNode {
|
|||||||
default:
|
default:
|
||||||
throw new JadxRuntimeException("Unknown array element width: " + payload.getElementWidthUnit());
|
throw new JadxRuntimeException("Unknown array element width: " + payload.getElementWidthUnit());
|
||||||
}
|
}
|
||||||
setResult(InsnArg.reg(resReg, ArgType.array(elType)));
|
addArg(InsnArg.reg(resReg, ArgType.array(elType)));
|
||||||
|
|
||||||
this.data = payload.getData();
|
this.data = payload.getData();
|
||||||
this.size = payload.getSize();
|
this.size = payload.getSize();
|
||||||
@ -58,34 +58,27 @@ public final class FillArrayNode extends InsnNode {
|
|||||||
return elemType;
|
return elemType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void mergeElementType(DexNode dex, ArgType foundElemType) {
|
public List<LiteralArg> getLiteralArgs(ArgType type) {
|
||||||
ArgType r = ArgType.merge(dex, elemType, foundElemType);
|
|
||||||
if (r != null) {
|
|
||||||
elemType = r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<LiteralArg> getLiteralArgs() {
|
|
||||||
List<LiteralArg> list = new ArrayList<>(size);
|
List<LiteralArg> list = new ArrayList<>(size);
|
||||||
Object array = data;
|
Object array = data;
|
||||||
if (array instanceof int[]) {
|
if (array instanceof int[]) {
|
||||||
for (int b : (int[]) array) {
|
for (int b : (int[]) array) {
|
||||||
list.add(InsnArg.lit(b, elemType));
|
list.add(InsnArg.lit(b, type));
|
||||||
}
|
}
|
||||||
} else if (array instanceof byte[]) {
|
} else if (array instanceof byte[]) {
|
||||||
for (byte b : (byte[]) array) {
|
for (byte b : (byte[]) array) {
|
||||||
list.add(InsnArg.lit(b, elemType));
|
list.add(InsnArg.lit(b, type));
|
||||||
}
|
}
|
||||||
} else if (array instanceof short[]) {
|
} else if (array instanceof short[]) {
|
||||||
for (short b : (short[]) array) {
|
for (short b : (short[]) array) {
|
||||||
list.add(InsnArg.lit(b, elemType));
|
list.add(InsnArg.lit(b, type));
|
||||||
}
|
}
|
||||||
} else if (array instanceof long[]) {
|
} else if (array instanceof long[]) {
|
||||||
for (long b : (long[]) array) {
|
for (long b : (long[]) array) {
|
||||||
list.add(InsnArg.lit(b, elemType));
|
list.add(InsnArg.lit(b, type));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + elemType);
|
throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + type);
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
@ -101,4 +94,25 @@ public final class FillArrayNode extends InsnNode {
|
|||||||
FillArrayNode other = (FillArrayNode) obj;
|
FillArrayNode other = (FillArrayNode) obj;
|
||||||
return elemType.equals(other.elemType) && data == other.data;
|
return elemType.equals(other.elemType) && data == other.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String dataToString() {
|
||||||
|
if (data instanceof int[]) {
|
||||||
|
return Arrays.toString((int[]) data);
|
||||||
|
}
|
||||||
|
if (data instanceof short[]) {
|
||||||
|
return Arrays.toString((short[]) data);
|
||||||
|
}
|
||||||
|
if (data instanceof byte[]) {
|
||||||
|
return Arrays.toString((byte[]) data);
|
||||||
|
}
|
||||||
|
if (data instanceof long[]) {
|
||||||
|
return Arrays.toString((long[]) data);
|
||||||
|
}
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + ", data: " + dataToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,21 +16,21 @@ import static jadx.core.utils.BlockUtils.selectOther;
|
|||||||
|
|
||||||
public class IfNode extends GotoNode {
|
public class IfNode extends GotoNode {
|
||||||
|
|
||||||
// change default types priority
|
|
||||||
private static final ArgType ARG_TYPE = ArgType.unknown(
|
|
||||||
PrimitiveType.INT,
|
|
||||||
PrimitiveType.OBJECT, PrimitiveType.ARRAY,
|
|
||||||
PrimitiveType.BOOLEAN, PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
|
||||||
|
|
||||||
protected IfOp op;
|
protected IfOp op;
|
||||||
|
|
||||||
private BlockNode thenBlock;
|
private BlockNode thenBlock;
|
||||||
private BlockNode elseBlock;
|
private BlockNode elseBlock;
|
||||||
|
|
||||||
public IfNode(DecodedInstruction insn, IfOp op) {
|
public IfNode(DecodedInstruction insn, IfOp op) {
|
||||||
this(op, insn.getTarget(),
|
super(InsnType.IF, insn.getTarget(), 2);
|
||||||
InsnArg.reg(insn, 0, ARG_TYPE),
|
this.op = op;
|
||||||
insn.getRegisterCount() == 1 ? InsnArg.lit(0, ARG_TYPE) : InsnArg.reg(insn, 1, ARG_TYPE));
|
ArgType argType = narrowTypeByOp(op);
|
||||||
|
addArg(InsnArg.reg(insn, 0, argType));
|
||||||
|
if (insn.getRegisterCount() == 1) {
|
||||||
|
addArg(InsnArg.lit(0, argType));
|
||||||
|
} else {
|
||||||
|
addArg(InsnArg.reg(insn, 1, argType));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IfNode(IfOp op, int targetOffset, InsnArg arg1, InsnArg arg2) {
|
public IfNode(IfOp op, int targetOffset, InsnArg arg1, InsnArg arg2) {
|
||||||
@ -40,6 +40,22 @@ public class IfNode extends GotoNode {
|
|||||||
addArg(arg2);
|
addArg(arg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// change default types priority
|
||||||
|
private static final ArgType WIDE_TYPE = ArgType.unknown(
|
||||||
|
PrimitiveType.INT, PrimitiveType.BOOLEAN,
|
||||||
|
PrimitiveType.OBJECT, PrimitiveType.ARRAY,
|
||||||
|
PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||||
|
|
||||||
|
private static final ArgType NUMBERS_TYPE = ArgType.unknown(
|
||||||
|
PrimitiveType.INT, PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||||
|
|
||||||
|
private static ArgType narrowTypeByOp(IfOp op) {
|
||||||
|
if (op == IfOp.EQ || op == IfOp.NE) {
|
||||||
|
return WIDE_TYPE;
|
||||||
|
}
|
||||||
|
return NUMBERS_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
public IfOp getOp() {
|
public IfOp getOp() {
|
||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,11 @@ import jadx.core.dex.info.FieldInfo;
|
|||||||
import jadx.core.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
import jadx.core.dex.nodes.DexNode;
|
import jadx.core.dex.nodes.DexNode;
|
||||||
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.utils.InsnUtils;
|
import jadx.core.utils.InsnUtils;
|
||||||
@ -101,15 +103,15 @@ public class InsnDecoder {
|
|||||||
case Opcodes.CONST_4:
|
case Opcodes.CONST_4:
|
||||||
case Opcodes.CONST_16:
|
case Opcodes.CONST_16:
|
||||||
case Opcodes.CONST_HIGH16:
|
case Opcodes.CONST_HIGH16:
|
||||||
return insn(InsnType.CONST, InsnArg.reg(insn, 0, ArgType.NARROW),
|
LiteralArg narrowLitArg = InsnArg.lit(insn, ArgType.NARROW);
|
||||||
InsnArg.lit(insn, ArgType.NARROW));
|
return insn(InsnType.CONST, InsnArg.reg(insn, 0, narrowLitArg.getType()), narrowLitArg);
|
||||||
|
|
||||||
case Opcodes.CONST_WIDE:
|
case Opcodes.CONST_WIDE:
|
||||||
case Opcodes.CONST_WIDE_16:
|
case Opcodes.CONST_WIDE_16:
|
||||||
case Opcodes.CONST_WIDE_32:
|
case Opcodes.CONST_WIDE_32:
|
||||||
case Opcodes.CONST_WIDE_HIGH16:
|
case Opcodes.CONST_WIDE_HIGH16:
|
||||||
return insn(InsnType.CONST, InsnArg.reg(insn, 0, ArgType.WIDE),
|
LiteralArg wideLitArg = InsnArg.lit(insn, ArgType.WIDE);
|
||||||
InsnArg.lit(insn, ArgType.WIDE));
|
return insn(InsnType.CONST, InsnArg.reg(insn, 0, wideLitArg.getType()), wideLitArg);
|
||||||
|
|
||||||
case Opcodes.CONST_STRING:
|
case Opcodes.CONST_STRING:
|
||||||
case Opcodes.CONST_STRING_JUMBO:
|
case Opcodes.CONST_STRING_JUMBO:
|
||||||
@ -442,7 +444,7 @@ public class InsnDecoder {
|
|||||||
case Opcodes.IGET_OBJECT:
|
case Opcodes.IGET_OBJECT:
|
||||||
FieldInfo igetFld = FieldInfo.fromDex(dex, insn.getIndex());
|
FieldInfo igetFld = FieldInfo.fromDex(dex, insn.getIndex());
|
||||||
InsnNode igetInsn = new IndexInsnNode(InsnType.IGET, igetFld, 1);
|
InsnNode igetInsn = new IndexInsnNode(InsnType.IGET, igetFld, 1);
|
||||||
igetInsn.setResult(InsnArg.reg(insn, 0, igetFld.getType()));
|
igetInsn.setResult(InsnArg.reg(insn, 0, tryResolveFieldType(igetFld)));
|
||||||
igetInsn.addArg(InsnArg.reg(insn, 1, igetFld.getDeclClass().getType()));
|
igetInsn.addArg(InsnArg.reg(insn, 1, igetFld.getDeclClass().getType()));
|
||||||
return igetInsn;
|
return igetInsn;
|
||||||
|
|
||||||
@ -455,7 +457,7 @@ public class InsnDecoder {
|
|||||||
case Opcodes.IPUT_OBJECT:
|
case Opcodes.IPUT_OBJECT:
|
||||||
FieldInfo iputFld = FieldInfo.fromDex(dex, insn.getIndex());
|
FieldInfo iputFld = FieldInfo.fromDex(dex, insn.getIndex());
|
||||||
InsnNode iputInsn = new IndexInsnNode(InsnType.IPUT, iputFld, 2);
|
InsnNode iputInsn = new IndexInsnNode(InsnType.IPUT, iputFld, 2);
|
||||||
iputInsn.addArg(InsnArg.reg(insn, 0, iputFld.getType()));
|
iputInsn.addArg(InsnArg.reg(insn, 0, tryResolveFieldType(iputFld)));
|
||||||
iputInsn.addArg(InsnArg.reg(insn, 1, iputFld.getDeclClass().getType()));
|
iputInsn.addArg(InsnArg.reg(insn, 1, iputFld.getDeclClass().getType()));
|
||||||
return iputInsn;
|
return iputInsn;
|
||||||
|
|
||||||
@ -468,7 +470,7 @@ public class InsnDecoder {
|
|||||||
case Opcodes.SGET_OBJECT:
|
case Opcodes.SGET_OBJECT:
|
||||||
FieldInfo sgetFld = FieldInfo.fromDex(dex, insn.getIndex());
|
FieldInfo sgetFld = FieldInfo.fromDex(dex, insn.getIndex());
|
||||||
InsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, sgetFld, 0);
|
InsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, sgetFld, 0);
|
||||||
sgetInsn.setResult(InsnArg.reg(insn, 0, sgetFld.getType()));
|
sgetInsn.setResult(InsnArg.reg(insn, 0, tryResolveFieldType(sgetFld)));
|
||||||
return sgetInsn;
|
return sgetInsn;
|
||||||
|
|
||||||
case Opcodes.SPUT:
|
case Opcodes.SPUT:
|
||||||
@ -480,7 +482,7 @@ public class InsnDecoder {
|
|||||||
case Opcodes.SPUT_OBJECT:
|
case Opcodes.SPUT_OBJECT:
|
||||||
FieldInfo sputFld = FieldInfo.fromDex(dex, insn.getIndex());
|
FieldInfo sputFld = FieldInfo.fromDex(dex, insn.getIndex());
|
||||||
InsnNode sputInsn = new IndexInsnNode(InsnType.SPUT, sputFld, 1);
|
InsnNode sputInsn = new IndexInsnNode(InsnType.SPUT, sputFld, 1);
|
||||||
sputInsn.addArg(InsnArg.reg(insn, 0, sputFld.getType()));
|
sputInsn.addArg(InsnArg.reg(insn, 0, tryResolveFieldType(sputFld)));
|
||||||
return sputInsn;
|
return sputInsn;
|
||||||
|
|
||||||
case Opcodes.ARRAY_LENGTH:
|
case Opcodes.ARRAY_LENGTH:
|
||||||
@ -490,7 +492,7 @@ public class InsnDecoder {
|
|||||||
return arrLenInsn;
|
return arrLenInsn;
|
||||||
|
|
||||||
case Opcodes.AGET:
|
case Opcodes.AGET:
|
||||||
return arrayGet(insn, ArgType.NARROW);
|
return arrayGet(insn, ArgType.INT_FLOAT);
|
||||||
case Opcodes.AGET_BOOLEAN:
|
case Opcodes.AGET_BOOLEAN:
|
||||||
return arrayGet(insn, ArgType.BOOLEAN);
|
return arrayGet(insn, ArgType.BOOLEAN);
|
||||||
case Opcodes.AGET_BYTE:
|
case Opcodes.AGET_BYTE:
|
||||||
@ -505,7 +507,7 @@ public class InsnDecoder {
|
|||||||
return arrayGet(insn, ArgType.UNKNOWN_OBJECT);
|
return arrayGet(insn, ArgType.UNKNOWN_OBJECT);
|
||||||
|
|
||||||
case Opcodes.APUT:
|
case Opcodes.APUT:
|
||||||
return arrayPut(insn, ArgType.NARROW);
|
return arrayPut(insn, ArgType.INT_FLOAT);
|
||||||
case Opcodes.APUT_BOOLEAN:
|
case Opcodes.APUT_BOOLEAN:
|
||||||
return arrayPut(insn, ArgType.BOOLEAN);
|
return arrayPut(insn, ArgType.BOOLEAN);
|
||||||
case Opcodes.APUT_BYTE:
|
case Opcodes.APUT_BYTE:
|
||||||
@ -544,14 +546,16 @@ public class InsnDecoder {
|
|||||||
return invoke(insn, offset, InvokeType.VIRTUAL, true);
|
return invoke(insn, offset, InvokeType.VIRTUAL, true);
|
||||||
|
|
||||||
case Opcodes.NEW_INSTANCE:
|
case Opcodes.NEW_INSTANCE:
|
||||||
return insn(InsnType.NEW_INSTANCE,
|
ArgType clsType = dex.getType(insn.getIndex());
|
||||||
InsnArg.reg(insn, 0, dex.getType(insn.getIndex())));
|
IndexInsnNode newInstInsn = new IndexInsnNode(InsnType.NEW_INSTANCE, clsType, 0);
|
||||||
|
newInstInsn.setResult(InsnArg.reg(insn, 0, clsType));
|
||||||
|
return newInstInsn;
|
||||||
|
|
||||||
case Opcodes.NEW_ARRAY:
|
case Opcodes.NEW_ARRAY:
|
||||||
ArgType arrType = dex.getType(insn.getIndex());
|
ArgType arrType = dex.getType(insn.getIndex());
|
||||||
return new NewArrayNode(arrType,
|
return new NewArrayNode(arrType,
|
||||||
InsnArg.reg(insn, 0, arrType),
|
InsnArg.reg(insn, 0, arrType),
|
||||||
InsnArg.reg(insn, 1, ArgType.INT));
|
InsnArg.typeImmutableReg(insn, 1, ArgType.INT));
|
||||||
|
|
||||||
case Opcodes.FILL_ARRAY_DATA:
|
case Opcodes.FILL_ARRAY_DATA:
|
||||||
return fillArray(insn);
|
return fillArray(insn);
|
||||||
@ -582,6 +586,14 @@ public class InsnDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ArgType tryResolveFieldType(FieldInfo igetFld) {
|
||||||
|
FieldNode fieldNode = dex.resolveField(igetFld);
|
||||||
|
if (fieldNode != null) {
|
||||||
|
return fieldNode.getType();
|
||||||
|
}
|
||||||
|
return igetFld.getType();
|
||||||
|
}
|
||||||
|
|
||||||
private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) {
|
private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) {
|
||||||
int payloadOffset = insn.getTarget();
|
int payloadOffset = insn.getTarget();
|
||||||
DecodedInstruction payload = insnArr[payloadOffset];
|
DecodedInstruction payload = insnArr[payloadOffset];
|
||||||
@ -666,17 +678,17 @@ public class InsnDecoder {
|
|||||||
|
|
||||||
private InsnNode arrayGet(DecodedInstruction insn, ArgType argType) {
|
private InsnNode arrayGet(DecodedInstruction insn, ArgType argType) {
|
||||||
InsnNode inode = new InsnNode(InsnType.AGET, 2);
|
InsnNode inode = new InsnNode(InsnType.AGET, 2);
|
||||||
inode.setResult(InsnArg.reg(insn, 0, argType));
|
inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
|
||||||
inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
|
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType)));
|
||||||
inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
|
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
|
||||||
return inode;
|
return inode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private InsnNode arrayPut(DecodedInstruction insn, ArgType argType) {
|
private InsnNode arrayPut(DecodedInstruction insn, ArgType argType) {
|
||||||
InsnNode inode = new InsnNode(InsnType.APUT, 3);
|
InsnNode inode = new InsnNode(InsnType.APUT, 3);
|
||||||
inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
|
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType)));
|
||||||
inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
|
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
|
||||||
inode.addArg(InsnArg.reg(insn, 0, argType));
|
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
|
||||||
return inode;
|
return inode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
package jadx.core.dex.instructions.args;
|
package jadx.core.dex.instructions.args;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.dex.nodes.DexNode;
|
import jadx.core.dex.nodes.DexNode;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.dex.nodes.parser.SignatureParser;
|
import jadx.core.dex.nodes.parser.SignatureParser;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
public abstract class ArgType {
|
public abstract class ArgType {
|
||||||
|
|
||||||
public static final ArgType INT = primitive(PrimitiveType.INT);
|
public static final ArgType INT = primitive(PrimitiveType.INT);
|
||||||
public static final ArgType BOOLEAN = primitive(PrimitiveType.BOOLEAN);
|
public static final ArgType BOOLEAN = primitive(PrimitiveType.BOOLEAN);
|
||||||
public static final ArgType BYTE = primitive(PrimitiveType.BYTE);
|
public static final ArgType BYTE = primitive(PrimitiveType.BYTE);
|
||||||
@ -28,6 +26,7 @@ public abstract class ArgType {
|
|||||||
public static final ArgType STRING = object(Consts.CLASS_STRING);
|
public static final ArgType STRING = object(Consts.CLASS_STRING);
|
||||||
public static final ArgType ENUM = object(Consts.CLASS_ENUM);
|
public static final ArgType ENUM = object(Consts.CLASS_ENUM);
|
||||||
public static final ArgType THROWABLE = object(Consts.CLASS_THROWABLE);
|
public static final ArgType THROWABLE = object(Consts.CLASS_THROWABLE);
|
||||||
|
public static final ArgType OBJECT_ARRAY = array(OBJECT);
|
||||||
|
|
||||||
public static final ArgType UNKNOWN = unknown(PrimitiveType.values());
|
public static final ArgType UNKNOWN = unknown(PrimitiveType.values());
|
||||||
public static final ArgType UNKNOWN_OBJECT = unknown(PrimitiveType.OBJECT, PrimitiveType.ARRAY);
|
public static final ArgType UNKNOWN_OBJECT = unknown(PrimitiveType.OBJECT, PrimitiveType.ARRAY);
|
||||||
@ -38,11 +37,20 @@ public abstract class ArgType {
|
|||||||
PrimitiveType.OBJECT, PrimitiveType.ARRAY);
|
PrimitiveType.OBJECT, PrimitiveType.ARRAY);
|
||||||
|
|
||||||
public static final ArgType NARROW_NUMBERS = unknown(
|
public static final ArgType NARROW_NUMBERS = unknown(
|
||||||
|
PrimitiveType.BOOLEAN, PrimitiveType.INT, PrimitiveType.FLOAT,
|
||||||
|
PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
|
||||||
|
|
||||||
|
public static final ArgType NARROW_INTEGRAL = unknown(
|
||||||
|
PrimitiveType.INT, PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
|
||||||
|
|
||||||
|
public static final ArgType NARROW_NUMBERS_NO_BOOL = unknown(
|
||||||
PrimitiveType.INT, PrimitiveType.FLOAT,
|
PrimitiveType.INT, PrimitiveType.FLOAT,
|
||||||
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
|
PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
|
||||||
|
|
||||||
public static final ArgType WIDE = unknown(PrimitiveType.LONG, PrimitiveType.DOUBLE);
|
public static final ArgType WIDE = unknown(PrimitiveType.LONG, PrimitiveType.DOUBLE);
|
||||||
|
|
||||||
|
public static final ArgType INT_FLOAT = unknown(PrimitiveType.INT, PrimitiveType.FLOAT);
|
||||||
|
|
||||||
protected int hash;
|
protected int hash;
|
||||||
|
|
||||||
private static ArgType primitive(PrimitiveType stype) {
|
private static ArgType primitive(PrimitiveType stype) {
|
||||||
@ -174,6 +182,8 @@ public abstract class ArgType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final class GenericType extends ObjectType {
|
private static final class GenericType extends ObjectType {
|
||||||
|
private List<ArgType> extendTypes;
|
||||||
|
|
||||||
public GenericType(String obj) {
|
public GenericType(String obj) {
|
||||||
super(obj);
|
super(obj);
|
||||||
}
|
}
|
||||||
@ -182,6 +192,16 @@ public abstract class ArgType {
|
|||||||
public boolean isGenericType() {
|
public boolean isGenericType() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ArgType> getExtendTypes() {
|
||||||
|
return extendTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setExtendTypes(List<ArgType> extendTypes) {
|
||||||
|
this.extendTypes = extendTypes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class WildcardType extends ObjectType {
|
private static final class WildcardType extends ObjectType {
|
||||||
@ -275,7 +295,7 @@ public abstract class ArgType {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return super.toString() + "<" + Utils.arrayToString(generics) + ">";
|
return super.toString() + "<" + Utils.arrayToStr(generics) + ">";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,8 +349,9 @@ public abstract class ArgType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean internalEquals(Object obj) {
|
boolean internalEquals(Object other) {
|
||||||
return arrayElement.equals(((ArrayArg) obj).arrayElement);
|
ArrayArg otherArr = (ArrayArg) other;
|
||||||
|
return this.arrayElement.equals(otherArr.getArrayElement());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -369,14 +390,13 @@ public abstract class ArgType {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ArgType selectFirst() {
|
public ArgType selectFirst() {
|
||||||
PrimitiveType f = possibleTypes[0];
|
|
||||||
if (contains(PrimitiveType.OBJECT)) {
|
if (contains(PrimitiveType.OBJECT)) {
|
||||||
return OBJECT;
|
return OBJECT;
|
||||||
} else if (contains(PrimitiveType.ARRAY)) {
|
|
||||||
return array(OBJECT);
|
|
||||||
} else {
|
|
||||||
return primitive(f);
|
|
||||||
}
|
}
|
||||||
|
if (contains(PrimitiveType.ARRAY)) {
|
||||||
|
return array(OBJECT);
|
||||||
|
}
|
||||||
|
return primitive(possibleTypes[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -389,7 +409,7 @@ public abstract class ArgType {
|
|||||||
if (possibleTypes.length == PrimitiveType.values().length) {
|
if (possibleTypes.length == PrimitiveType.values().length) {
|
||||||
return "?";
|
return "?";
|
||||||
} else {
|
} else {
|
||||||
return "?" + Arrays.toString(possibleTypes);
|
return "?[" + Utils.arrayToStr(possibleTypes) + "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -426,6 +446,13 @@ public abstract class ArgType {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ArgType> getExtendTypes() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExtendTypes(List<ArgType> extendTypes) {
|
||||||
|
}
|
||||||
|
|
||||||
public ArgType getWildcardType() {
|
public ArgType getWildcardType() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -463,110 +490,6 @@ public abstract class ArgType {
|
|||||||
|
|
||||||
public abstract PrimitiveType[] getPossibleTypes();
|
public abstract PrimitiveType[] getPossibleTypes();
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static ArgType merge(@Nullable DexNode dex, ArgType a, ArgType b) {
|
|
||||||
if (a == null || b == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (a.equals(b)) {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
ArgType res = mergeInternal(dex, a, b);
|
|
||||||
if (res == null) {
|
|
||||||
res = mergeInternal(dex, b, a); // swap
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ArgType mergeInternal(@Nullable DexNode dex, ArgType a, ArgType b) {
|
|
||||||
if (a == UNKNOWN) {
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
if (a.isArray()) {
|
|
||||||
return mergeArrays(dex, (ArrayArg) a, b);
|
|
||||||
} else if (b.isArray()) {
|
|
||||||
return mergeArrays(dex, (ArrayArg) b, a);
|
|
||||||
}
|
|
||||||
if (!a.isTypeKnown()) {
|
|
||||||
if (b.isTypeKnown()) {
|
|
||||||
if (a.contains(b.getPrimitiveType())) {
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
// both types unknown
|
|
||||||
List<PrimitiveType> types = new ArrayList<>();
|
|
||||||
for (PrimitiveType type : a.getPossibleTypes()) {
|
|
||||||
if (b.contains(type)) {
|
|
||||||
types.add(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (types.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (types.size() == 1) {
|
|
||||||
PrimitiveType nt = types.get(0);
|
|
||||||
if (nt == PrimitiveType.OBJECT || nt == PrimitiveType.ARRAY) {
|
|
||||||
return unknown(nt);
|
|
||||||
} else {
|
|
||||||
return primitive(nt);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return unknown(types.toArray(new PrimitiveType[types.size()]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (a.isGenericType()) {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
if (b.isGenericType()) {
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.isObject() && b.isObject()) {
|
|
||||||
String aObj = a.getObject();
|
|
||||||
String bObj = b.getObject();
|
|
||||||
if (aObj.equals(bObj)) {
|
|
||||||
return a.getGenericTypes() != null ? a : b;
|
|
||||||
}
|
|
||||||
if (aObj.equals(Consts.CLASS_OBJECT)) {
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
if (bObj.equals(Consts.CLASS_OBJECT)) {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
if (dex == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String obj = dex.root().getClsp().getCommonAncestor(aObj, bObj);
|
|
||||||
return obj == null ? null : object(obj);
|
|
||||||
}
|
|
||||||
if (a.isPrimitive() && b.isPrimitive() && a.getRegCount() == b.getRegCount()) {
|
|
||||||
return primitive(PrimitiveType.getSmaller(a.getPrimitiveType(), b.getPrimitiveType()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ArgType mergeArrays(DexNode dex, ArrayArg array, ArgType b) {
|
|
||||||
if (b.isArray()) {
|
|
||||||
ArgType ea = array.getArrayElement();
|
|
||||||
ArgType eb = b.getArrayElement();
|
|
||||||
if (ea.isPrimitive() && eb.isPrimitive()) {
|
|
||||||
return OBJECT;
|
|
||||||
}
|
|
||||||
ArgType res = merge(dex, ea, eb);
|
|
||||||
return res == null ? null : array(res);
|
|
||||||
}
|
|
||||||
if (b.contains(PrimitiveType.ARRAY)) {
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
if (b.equals(OBJECT)) {
|
|
||||||
return OBJECT;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isCastNeeded(DexNode dex, ArgType from, ArgType to) {
|
public static boolean isCastNeeded(DexNode dex, ArgType from, ArgType to) {
|
||||||
if (from.equals(to)) {
|
if (from.equals(to)) {
|
||||||
return false;
|
return false;
|
||||||
@ -578,14 +501,49 @@ public abstract class ArgType {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isInstanceOf(DexNode dex, ArgType type, ArgType of) {
|
public static boolean isInstanceOf(RootNode root, ArgType type, ArgType of) {
|
||||||
if (type.equals(of)) {
|
if (type.equals(of)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!type.isObject() || !of.isObject()) {
|
if (!type.isObject() || !of.isObject()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return dex.root().getClsp().isImplements(type.getObject(), of.getObject());
|
return root.getClsp().isImplements(type.getObject(), of.getObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isClsKnown(RootNode root, ArgType cls) {
|
||||||
|
if (cls.isObject()) {
|
||||||
|
return root.getClsp().isClsKnown(cls.getObject());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArgType convertFromPrimitiveType(PrimitiveType primitiveType) {
|
||||||
|
switch (primitiveType) {
|
||||||
|
case BOOLEAN:
|
||||||
|
return BOOLEAN;
|
||||||
|
case CHAR:
|
||||||
|
return CHAR;
|
||||||
|
case BYTE:
|
||||||
|
return BYTE;
|
||||||
|
case SHORT:
|
||||||
|
return SHORT;
|
||||||
|
case INT:
|
||||||
|
return INT;
|
||||||
|
case FLOAT:
|
||||||
|
return FLOAT;
|
||||||
|
case LONG:
|
||||||
|
return LONG;
|
||||||
|
case DOUBLE:
|
||||||
|
return DOUBLE;
|
||||||
|
case OBJECT:
|
||||||
|
return OBJECT;
|
||||||
|
case ARRAY:
|
||||||
|
return OBJECT_ARRAY;
|
||||||
|
case VOID:
|
||||||
|
return ArgType.VOID;
|
||||||
|
}
|
||||||
|
return OBJECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArgType parse(String type) {
|
public static ArgType parse(String type) {
|
||||||
|
@ -31,10 +31,21 @@ public abstract class InsnArg extends Typed {
|
|||||||
return reg(InsnUtils.getArg(insn, argNum), type);
|
return reg(InsnUtils.getArg(insn, argNum), type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static RegisterArg typeImmutableIfKnownReg(DecodedInstruction insn, int argNum, ArgType type) {
|
||||||
|
if (type.isTypeKnown()) {
|
||||||
|
return typeImmutableReg(InsnUtils.getArg(insn, argNum), type);
|
||||||
|
}
|
||||||
|
return reg(InsnUtils.getArg(insn, argNum), type);
|
||||||
|
}
|
||||||
|
|
||||||
public static TypeImmutableArg typeImmutableReg(int regNum, ArgType type) {
|
public static TypeImmutableArg typeImmutableReg(int regNum, ArgType type) {
|
||||||
return new TypeImmutableArg(regNum, type);
|
return new TypeImmutableArg(regNum, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TypeImmutableArg typeImmutableReg(DecodedInstruction insn, int argNum, ArgType type) {
|
||||||
|
return typeImmutableReg(InsnUtils.getArg(insn, argNum), type);
|
||||||
|
}
|
||||||
|
|
||||||
public static RegisterArg reg(int regNum, ArgType type, boolean typeImmutable) {
|
public static RegisterArg reg(int regNum, ArgType type, boolean typeImmutable) {
|
||||||
return typeImmutable ? new TypeImmutableArg(regNum, type) : new RegisterArg(regNum, type);
|
return typeImmutable ? new TypeImmutableArg(regNum, type) : new RegisterArg(regNum, type);
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,10 @@ public final class LiteralArg extends InsnArg {
|
|||||||
} else if (!type.isTypeKnown()
|
} else if (!type.isTypeKnown()
|
||||||
&& !type.contains(PrimitiveType.LONG)
|
&& !type.contains(PrimitiveType.LONG)
|
||||||
&& !type.contains(PrimitiveType.DOUBLE)) {
|
&& !type.contains(PrimitiveType.DOUBLE)) {
|
||||||
ArgType m = ArgType.merge(null, type, ArgType.NARROW_NUMBERS);
|
if (value != 1) {
|
||||||
if (m != null) {
|
type = ArgType.NARROW_NUMBERS_NO_BOOL;
|
||||||
type = m;
|
} else {
|
||||||
|
type = ArgType.NARROW_NUMBERS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ import jadx.core.dex.nodes.InsnNode;
|
|||||||
import jadx.core.utils.InsnUtils;
|
import jadx.core.utils.InsnUtils;
|
||||||
|
|
||||||
public class RegisterArg extends InsnArg implements Named {
|
public class RegisterArg extends InsnArg implements Named {
|
||||||
|
|
||||||
public static final String THIS_ARG_NAME = "this";
|
public static final String THIS_ARG_NAME = "this";
|
||||||
|
|
||||||
protected final int regNum;
|
protected final int regNum;
|
||||||
@ -36,6 +35,38 @@ public class RegisterArg extends InsnArg implements Named {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setType(ArgType type) {
|
||||||
|
if (sVar != null) {
|
||||||
|
sVar.getTypeInfo().setType(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArgType getType() {
|
||||||
|
SSAVar ssaVar = this.sVar;
|
||||||
|
if (ssaVar != null) {
|
||||||
|
return ssaVar.getTypeInfo().getType();
|
||||||
|
}
|
||||||
|
return ArgType.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getInitType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTypeImmutable() {
|
||||||
|
if (sVar != null) {
|
||||||
|
RegisterArg assign = sVar.getAssign();
|
||||||
|
if (assign == this) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return assign.isTypeImmutable();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public SSAVar getSVar() {
|
public SSAVar getSVar() {
|
||||||
return sVar;
|
return sVar;
|
||||||
}
|
}
|
||||||
@ -83,24 +114,12 @@ public class RegisterArg extends InsnArg implements Named {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setType(ArgType type) {
|
|
||||||
if (sVar != null) {
|
|
||||||
sVar.setType(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void mergeDebugInfo(ArgType type, String name) {
|
|
||||||
setType(type);
|
|
||||||
setName(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RegisterArg duplicate() {
|
public RegisterArg duplicate() {
|
||||||
return duplicate(getRegNum(), sVar);
|
return duplicate(getRegNum(), sVar);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RegisterArg duplicate(int regNum, SSAVar sVar) {
|
public RegisterArg duplicate(int regNum, SSAVar sVar) {
|
||||||
RegisterArg dup = new RegisterArg(regNum, getType());
|
RegisterArg dup = new RegisterArg(regNum, getInitType());
|
||||||
if (sVar != null) {
|
if (sVar != null) {
|
||||||
dup.setSVar(sVar);
|
dup.setSVar(sVar);
|
||||||
}
|
}
|
||||||
@ -140,6 +159,10 @@ public class RegisterArg extends InsnArg implements Named {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean equalRegister(RegisterArg arg) {
|
||||||
|
return regNum == arg.regNum;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean equalRegisterAndType(RegisterArg arg) {
|
public boolean equalRegisterAndType(RegisterArg arg) {
|
||||||
return regNum == arg.regNum && type.equals(arg.type);
|
return regNum == arg.regNum && type.equals(arg.type);
|
||||||
}
|
}
|
||||||
@ -159,7 +182,7 @@ public class RegisterArg extends InsnArg implements Named {
|
|||||||
}
|
}
|
||||||
RegisterArg other = (RegisterArg) obj;
|
RegisterArg other = (RegisterArg) obj;
|
||||||
return regNum == other.regNum
|
return regNum == other.regNum
|
||||||
&& type.equals(other.type)
|
&& Objects.equals(getType(), other.getType())
|
||||||
&& Objects.equals(sVar, other.getSVar());
|
&& Objects.equals(sVar, other.getSVar());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,13 +192,17 @@ public class RegisterArg extends InsnArg implements Named {
|
|||||||
sb.append("(r");
|
sb.append("(r");
|
||||||
sb.append(regNum);
|
sb.append(regNum);
|
||||||
if (sVar != null) {
|
if (sVar != null) {
|
||||||
sb.append("_").append(sVar.getVersion());
|
sb.append(':').append(sVar.getVersion());
|
||||||
}
|
}
|
||||||
if (getName() != null) {
|
if (getName() != null) {
|
||||||
sb.append(" '").append(getName()).append("'");
|
sb.append(" '").append(getName()).append('\'');
|
||||||
|
}
|
||||||
|
ArgType type = getType();
|
||||||
|
sb.append(' ').append(type);
|
||||||
|
ArgType initType = getInitType();
|
||||||
|
if (!type.equals(initType) && !type.isTypeKnown()) {
|
||||||
|
sb.append(" I:").append(initType);
|
||||||
}
|
}
|
||||||
sb.append(" ");
|
|
||||||
sb.append(type);
|
|
||||||
if (!isAttrStorageEmpty()) {
|
if (!isAttrStorageEmpty()) {
|
||||||
sb.append(' ').append(getAttributesString());
|
sb.append(' ').append(getAttributesString());
|
||||||
}
|
}
|
||||||
|
@ -8,15 +8,11 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import jadx.core.dex.attributes.AttrNode;
|
import jadx.core.dex.attributes.AttrNode;
|
||||||
import jadx.core.dex.instructions.PhiInsn;
|
import jadx.core.dex.instructions.PhiInsn;
|
||||||
|
import jadx.core.dex.visitors.typeinference.TypeInfo;
|
||||||
|
|
||||||
public class SSAVar extends AttrNode {
|
public class SSAVar extends AttrNode {
|
||||||
|
|
||||||
private final int regNum;
|
private final int regNum;
|
||||||
private final int version;
|
private final int version;
|
||||||
private VarName varName;
|
|
||||||
|
|
||||||
private int startUseAddr;
|
|
||||||
private int endUseAddr;
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private RegisterArg assign;
|
private RegisterArg assign;
|
||||||
@ -24,8 +20,8 @@ public class SSAVar extends AttrNode {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private PhiInsn usedInPhi;
|
private PhiInsn usedInPhi;
|
||||||
|
|
||||||
private ArgType type;
|
private TypeInfo typeInfo = new TypeInfo();
|
||||||
private boolean typeImmutable;
|
private VarName varName;
|
||||||
|
|
||||||
public SSAVar(int regNum, int v, @NotNull RegisterArg assign) {
|
public SSAVar(int regNum, int v, @NotNull RegisterArg assign) {
|
||||||
this.regNum = regNum;
|
this.regNum = regNum;
|
||||||
@ -33,48 +29,6 @@ public class SSAVar extends AttrNode {
|
|||||||
this.assign = assign;
|
this.assign = assign;
|
||||||
|
|
||||||
assign.setSVar(this);
|
assign.setSVar(this);
|
||||||
startUseAddr = -1;
|
|
||||||
endUseAddr = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getStartAddr() {
|
|
||||||
if (startUseAddr == -1) {
|
|
||||||
calcUsageAddrRange();
|
|
||||||
}
|
|
||||||
return startUseAddr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getEndAddr() {
|
|
||||||
if (endUseAddr == -1) {
|
|
||||||
calcUsageAddrRange();
|
|
||||||
}
|
|
||||||
return endUseAddr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void calcUsageAddrRange() {
|
|
||||||
int start = Integer.MAX_VALUE;
|
|
||||||
int end = Integer.MIN_VALUE;
|
|
||||||
|
|
||||||
if (assign.getParentInsn() != null) {
|
|
||||||
int insnAddr = assign.getParentInsn().getOffset();
|
|
||||||
if (insnAddr >= 0) {
|
|
||||||
start = Math.min(insnAddr, start);
|
|
||||||
end = Math.max(insnAddr, end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (RegisterArg arg : useList) {
|
|
||||||
if (arg.getParentInsn() != null) {
|
|
||||||
int insnAddr = arg.getParentInsn().getOffset();
|
|
||||||
if (insnAddr >= 0) {
|
|
||||||
start = Math.min(insnAddr, start);
|
|
||||||
end = Math.max(insnAddr, end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) {
|
|
||||||
startUseAddr = start;
|
|
||||||
endUseAddr = end;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRegNum() {
|
public int getRegNum() {
|
||||||
@ -139,30 +93,6 @@ public class SSAVar extends AttrNode {
|
|||||||
return useList.size() + usedInPhi.getResult().getSVar().getUseCount();
|
return useList.size() + usedInPhi.getResult().getSVar().getUseCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setType(ArgType type) {
|
|
||||||
ArgType acceptedType;
|
|
||||||
if (typeImmutable) {
|
|
||||||
// don't change type, just update types in useList
|
|
||||||
acceptedType = this.type;
|
|
||||||
} else {
|
|
||||||
acceptedType = type;
|
|
||||||
this.type = acceptedType;
|
|
||||||
}
|
|
||||||
assign.type = acceptedType;
|
|
||||||
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) {
|
|
||||||
useList.get(i).type = acceptedType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTypeImmutable(ArgType type) {
|
|
||||||
setType(type);
|
|
||||||
this.typeImmutable = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isTypeImmutable() {
|
|
||||||
return typeImmutable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
public void setName(String name) {
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
if (varName == null) {
|
if (varName == null) {
|
||||||
@ -187,6 +117,14 @@ public class SSAVar extends AttrNode {
|
|||||||
this.varName = varName;
|
this.varName = varName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TypeInfo getTypeInfo() {
|
||||||
|
return typeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTypeInfo(TypeInfo typeInfo) {
|
||||||
|
this.typeInfo = typeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) {
|
if (this == o) {
|
||||||
@ -206,6 +144,6 @@ public class SSAVar extends AttrNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "r" + regNum + "_" + version;
|
return "r" + regNum + ":" + version + " " + typeInfo.getType();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package jadx.core.dex.instructions.args;
|
package jadx.core.dex.instructions.args;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
public class TypeImmutableArg extends RegisterArg {
|
public class TypeImmutableArg extends RegisterArg {
|
||||||
|
|
||||||
@ -15,12 +17,11 @@ public class TypeImmutableArg extends RegisterArg {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setType(ArgType type) {
|
public void setType(ArgType type) {
|
||||||
// not allowed
|
// allow set only initial type
|
||||||
}
|
if (Objects.equals(this.type, type)) {
|
||||||
|
super.setType(type);
|
||||||
@Override
|
} else {
|
||||||
void setSVar(@NotNull SSAVar sVar) {
|
throw new JadxRuntimeException("Can't change arg with immutable type");
|
||||||
sVar.setTypeImmutable(type);
|
}
|
||||||
super.setSVar(sVar);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package jadx.core.dex.instructions.args;
|
package jadx.core.dex.instructions.args;
|
||||||
|
|
||||||
import jadx.core.dex.attributes.AttrNode;
|
import jadx.core.dex.attributes.AttrNode;
|
||||||
import jadx.core.dex.nodes.DexNode;
|
|
||||||
|
|
||||||
public abstract class Typed extends AttrNode {
|
public abstract class Typed extends AttrNode {
|
||||||
|
|
||||||
@ -18,17 +17,4 @@ public abstract class Typed extends AttrNode {
|
|||||||
public boolean isTypeImmutable() {
|
public boolean isTypeImmutable() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean merge(DexNode dex, ArgType newType) {
|
|
||||||
ArgType m = ArgType.merge(dex, type, newType);
|
|
||||||
if (m != null && !m.equals(type)) {
|
|
||||||
setType(m);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean merge(DexNode dex, InsnArg arg) {
|
|
||||||
return merge(dex, arg.getType());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -45,11 +45,10 @@ public class ConstructorInsn extends InsnNode {
|
|||||||
instanceArg.getSVar().setAssign(instanceArg);
|
instanceArg.getSVar().setAssign(instanceArg);
|
||||||
}
|
}
|
||||||
instanceArg.getSVar().removeUse(instanceArg);
|
instanceArg.getSVar().removeUse(instanceArg);
|
||||||
for (int i = 1; i < invoke.getArgsCount(); i++) {
|
int argsCount = invoke.getArgsCount();
|
||||||
|
for (int i = 1; i < argsCount; i++) {
|
||||||
addArg(invoke.getArg(i));
|
addArg(invoke.getArg(i));
|
||||||
}
|
}
|
||||||
offset = invoke.getOffset();
|
|
||||||
setSourceLine(invoke.getSourceLine());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConstructorInsn(MethodInfo callMth, CallType callType, RegisterArg instanceArg) {
|
public ConstructorInsn(MethodInfo callMth, CallType callType, RegisterArg instanceArg) {
|
||||||
|
@ -15,10 +15,6 @@ public final class TernaryInsn extends InsnNode {
|
|||||||
|
|
||||||
private IfCondition condition;
|
private IfCondition condition;
|
||||||
|
|
||||||
public TernaryInsn(IfCondition condition, RegisterArg result) {
|
|
||||||
this(condition, result, LiteralArg.TRUE, LiteralArg.FALSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TernaryInsn(IfCondition condition, RegisterArg result, InsnArg th, InsnArg els) {
|
public TernaryInsn(IfCondition condition, RegisterArg result, InsnArg th, InsnArg els) {
|
||||||
super(InsnType.TERNARY, 2);
|
super(InsnType.TERNARY, 2);
|
||||||
setResult(result);
|
setResult(result);
|
||||||
|
@ -87,6 +87,10 @@ public class DexNode implements IDexNode {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public ClassNode resolveClass(ClassInfo clsInfo) {
|
public ClassNode resolveClass(ClassInfo clsInfo) {
|
||||||
|
ClassNode classNode = resolveClassLocal(clsInfo);
|
||||||
|
if (classNode != null) {
|
||||||
|
return classNode;
|
||||||
|
}
|
||||||
return root.resolveClass(clsInfo);
|
return root.resolveClass(clsInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +102,6 @@ public class DexNode implements IDexNode {
|
|||||||
return resolveClass(ClassInfo.fromType(root, type));
|
return resolveClass(ClassInfo.fromType(root, type));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public MethodNode resolveMethod(@NotNull MethodInfo mth) {
|
public MethodNode resolveMethod(@NotNull MethodInfo mth) {
|
||||||
ClassNode cls = resolveClass(mth.getDeclClass());
|
ClassNode cls = resolveClass(mth.getDeclClass());
|
||||||
@ -138,7 +141,6 @@ public class DexNode implements IDexNode {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public FieldNode resolveField(FieldInfo field) {
|
public FieldNode resolveField(FieldInfo field) {
|
||||||
ClassNode cls = resolveClass(field.getDeclClass());
|
ClassNode cls = resolveClass(field.getDeclClass());
|
||||||
@ -184,10 +186,16 @@ public class DexNode implements IDexNode {
|
|||||||
// DexBuffer wrappers
|
// DexBuffer wrappers
|
||||||
|
|
||||||
public String getString(int index) {
|
public String getString(int index) {
|
||||||
|
if (index == DexNode.NO_INDEX) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return dexBuf.strings().get(index);
|
return dexBuf.strings().get(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArgType getType(int index) {
|
public ArgType getType(int index) {
|
||||||
|
if (index == DexNode.NO_INDEX) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return ArgType.parse(getString(dexBuf.typeIds().get(index)));
|
return ArgType.parse(getString(dexBuf.typeIds().get(index)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ public class FieldNode extends LineAttrNode {
|
|||||||
private final FieldInfo fieldInfo;
|
private final FieldInfo fieldInfo;
|
||||||
private final AccessInfo accFlags;
|
private final AccessInfo accFlags;
|
||||||
|
|
||||||
private ArgType type; // store signature
|
private ArgType type;
|
||||||
|
|
||||||
public FieldNode(ClassNode cls, Field field) {
|
public FieldNode(ClassNode cls, Field field) {
|
||||||
this(cls, FieldInfo.fromDex(cls.dex(), field.getFieldIndex()),
|
this(cls, FieldInfo.fromDex(cls.dex(), field.getFieldIndex()),
|
||||||
|
@ -19,6 +19,7 @@ import jadx.core.dex.instructions.args.NamedArg;
|
|||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
import jadx.core.dex.instructions.args.SSAVar;
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
import jadx.core.utils.InsnUtils;
|
import jadx.core.utils.InsnUtils;
|
||||||
|
import jadx.core.utils.InstructionRemover;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
public class InsnNode extends LineAttrNode {
|
public class InsnNode extends LineAttrNode {
|
||||||
@ -110,6 +111,7 @@ public class InsnNode extends LineAttrNode {
|
|||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
InsnArg arg = arguments.get(i);
|
InsnArg arg = arguments.get(i);
|
||||||
if (arg == from) {
|
if (arg == from) {
|
||||||
|
InstructionRemover.unbindArgUsage(null, arg);
|
||||||
setArg(i, to);
|
setArg(i, to);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -125,10 +127,7 @@ public class InsnNode extends LineAttrNode {
|
|||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
if (arg == arguments.get(i)) {
|
if (arg == arguments.get(i)) {
|
||||||
arguments.remove(i);
|
arguments.remove(i);
|
||||||
if (arg instanceof RegisterArg) {
|
InstructionRemover.unbindArgUsage(null, arg);
|
||||||
RegisterArg reg = (RegisterArg) arg;
|
|
||||||
reg.getSVar().removeUse(reg);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,17 +54,18 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
|
|||||||
private final AccessInfo accFlags;
|
private final AccessInfo accFlags;
|
||||||
|
|
||||||
private final Method methodData;
|
private final Method methodData;
|
||||||
|
private final boolean methodIsVirtual;
|
||||||
|
|
||||||
|
private boolean noCode;
|
||||||
private int regsCount;
|
private int regsCount;
|
||||||
private InsnNode[] instructions;
|
private InsnNode[] instructions;
|
||||||
private int codeSize;
|
private int codeSize;
|
||||||
private int debugInfoOffset;
|
private int debugInfoOffset;
|
||||||
private boolean noCode;
|
|
||||||
private boolean methodIsVirtual;
|
|
||||||
|
|
||||||
private ArgType retType;
|
private ArgType retType;
|
||||||
private RegisterArg thisArg;
|
private RegisterArg thisArg;
|
||||||
private List<RegisterArg> argsList;
|
private List<RegisterArg> argsList;
|
||||||
private List<SSAVar> sVars = Collections.emptyList();
|
private List<SSAVar> sVars;
|
||||||
private Map<ArgType, List<ArgType>> genericMap;
|
private Map<ArgType, List<ArgType>> genericMap;
|
||||||
|
|
||||||
private List<BlockNode> blocks;
|
private List<BlockNode> blocks;
|
||||||
@ -72,8 +73,8 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
|
|||||||
private List<BlockNode> exitBlocks;
|
private List<BlockNode> exitBlocks;
|
||||||
|
|
||||||
private Region region;
|
private Region region;
|
||||||
private List<ExceptionHandler> exceptionHandlers = Collections.emptyList();
|
private List<ExceptionHandler> exceptionHandlers;
|
||||||
private List<LoopInfo> loops = Collections.emptyList();
|
private List<LoopInfo> loops;
|
||||||
|
|
||||||
public MethodNode(ClassNode classNode, Method mthData, boolean isVirtual) {
|
public MethodNode(ClassNode classNode, Method mthData, boolean isVirtual) {
|
||||||
this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex());
|
this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex());
|
||||||
@ -82,6 +83,26 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
|
|||||||
this.noCode = mthData.getCodeOffset() == 0;
|
this.noCode = mthData.getCodeOffset() == 0;
|
||||||
this.methodData = noCode ? null : mthData;
|
this.methodData = noCode ? null : mthData;
|
||||||
this.methodIsVirtual = isVirtual;
|
this.methodIsVirtual = isVirtual;
|
||||||
|
unload();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unload() {
|
||||||
|
if (noCode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
retType = null;
|
||||||
|
thisArg = null;
|
||||||
|
argsList = Collections.emptyList();
|
||||||
|
sVars = Collections.emptyList();
|
||||||
|
genericMap = null;
|
||||||
|
instructions = null;
|
||||||
|
blocks = null;
|
||||||
|
enterBlock = null;
|
||||||
|
exitBlocks = null;
|
||||||
|
region = null;
|
||||||
|
exceptionHandlers = Collections.emptyList();
|
||||||
|
loops = Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -148,21 +169,6 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unload() {
|
|
||||||
if (noCode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
instructions = null;
|
|
||||||
blocks = null;
|
|
||||||
enterBlock = null;
|
|
||||||
exitBlocks = null;
|
|
||||||
exceptionHandlers = Collections.emptyList();
|
|
||||||
sVars.clear();
|
|
||||||
region = null;
|
|
||||||
loops = Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean parseSignature() {
|
private boolean parseSignature() {
|
||||||
SignatureParser sp = SignatureParser.fromNode(this);
|
SignatureParser sp = SignatureParser.fromNode(this);
|
||||||
if (sp == null) {
|
if (sp == null) {
|
||||||
@ -535,7 +541,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
|
|||||||
&& !parentClass.getAccessFlags().isStatic()) {
|
&& !parentClass.getAccessFlags().isStatic()) {
|
||||||
ClassNode outerCls = parentClass.getParentClass();
|
ClassNode outerCls = parentClass.getParentClass();
|
||||||
if (argsList != null && !argsList.isEmpty()
|
if (argsList != null && !argsList.isEmpty()
|
||||||
&& argsList.get(0).getType().equals(outerCls.getClassInfo().getType())) {
|
&& argsList.get(0).getInitType().equals(outerCls.getClassInfo().getType())) {
|
||||||
defaultArgCount = 1;
|
defaultArgCount = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -611,8 +617,13 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
|
|||||||
return "method";
|
return "method";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addWarn(String errStr) {
|
public void addWarn(String warnStr) {
|
||||||
ErrorsCounter.methodWarn(this, errStr);
|
ErrorsCounter.methodWarn(this, warnStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addComment(String commentStr) {
|
||||||
|
addAttr(AType.COMMENTS, commentStr);
|
||||||
|
LOG.info("{} in {}", commentStr, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addError(String errStr, Exception e) {
|
public void addError(String errStr, Exception e) {
|
||||||
|
@ -18,6 +18,7 @@ import jadx.core.dex.info.ConstStorage;
|
|||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
import jadx.core.dex.info.InfoStorage;
|
import jadx.core.dex.info.InfoStorage;
|
||||||
import jadx.core.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.visitors.typeinference.TypeUpdate;
|
||||||
import jadx.core.utils.ErrorsCounter;
|
import jadx.core.utils.ErrorsCounter;
|
||||||
import jadx.core.utils.StringUtils;
|
import jadx.core.utils.StringUtils;
|
||||||
import jadx.core.utils.android.AndroidResourcesUtils;
|
import jadx.core.utils.android.AndroidResourcesUtils;
|
||||||
@ -36,6 +37,7 @@ public class RootNode {
|
|||||||
private final StringUtils stringUtils;
|
private final StringUtils stringUtils;
|
||||||
private final ConstStorage constValues;
|
private final ConstStorage constValues;
|
||||||
private final InfoStorage infoStorage = new InfoStorage();
|
private final InfoStorage infoStorage = new InfoStorage();
|
||||||
|
private final TypeUpdate typeUpdate;
|
||||||
|
|
||||||
private ClspGraph clsp;
|
private ClspGraph clsp;
|
||||||
private List<DexNode> dexNodes;
|
private List<DexNode> dexNodes;
|
||||||
@ -48,6 +50,7 @@ public class RootNode {
|
|||||||
this.args = args;
|
this.args = args;
|
||||||
this.stringUtils = new StringUtils(args);
|
this.stringUtils = new StringUtils(args);
|
||||||
this.constValues = new ConstStorage(args);
|
this.constValues = new ConstStorage(args);
|
||||||
|
this.typeUpdate = new TypeUpdate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void load(List<InputFile> inputFiles) {
|
public void load(List<InputFile> inputFiles) {
|
||||||
@ -224,4 +227,8 @@ public class RootNode {
|
|||||||
public JadxArgs getArgs() {
|
public JadxArgs getArgs() {
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TypeUpdate getTypeUpdate() {
|
||||||
|
return typeUpdate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,9 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
|
|||||||
public List<IContainer> getBranches() {
|
public List<IContainer> getBranches() {
|
||||||
List<IContainer> branches = new ArrayList<>(cases.size() + 1);
|
List<IContainer> branches = new ArrayList<>(cases.size() + 1);
|
||||||
branches.addAll(cases);
|
branches.addAll(cases);
|
||||||
branches.add(defCase);
|
if (defCase != null) {
|
||||||
|
branches.add(defCase);
|
||||||
|
}
|
||||||
return Collections.unmodifiableList(branches);
|
return Collections.unmodifiableList(branches);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
|
||||||
import jadx.core.dex.instructions.IndexInsnNode;
|
import jadx.core.dex.instructions.IndexInsnNode;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
import jadx.core.dex.instructions.InvokeNode;
|
import jadx.core.dex.instructions.InvokeNode;
|
||||||
@ -16,14 +15,20 @@ import jadx.core.dex.instructions.args.PrimitiveType;
|
|||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
import jadx.core.dex.instructions.args.SSAVar;
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.DexNode;
|
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.visitors.typeinference.PostTypeInference;
|
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||||
|
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||||
import jadx.core.utils.InstructionRemover;
|
import jadx.core.utils.InstructionRemover;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
|
@JadxVisitor(
|
||||||
|
name = "Constants Inline",
|
||||||
|
desc = "Inline constant registers into instructions",
|
||||||
|
runAfter = SSATransform.class,
|
||||||
|
runBefore = TypeInferenceVisitor.class
|
||||||
|
)
|
||||||
public class ConstInlineVisitor extends AbstractVisitor {
|
public class ConstInlineVisitor extends AbstractVisitor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -63,11 +68,6 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ArgType resType = insn.getResult().getType();
|
|
||||||
// make sure arg has correct type
|
|
||||||
if (!arg.getType().isTypeKnown()) {
|
|
||||||
arg.merge(mth.dex(), resType);
|
|
||||||
}
|
|
||||||
return replaceConst(mth, insn, lit);
|
return replaceConst(mth, insn, lit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +106,8 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
InsnNode useInsn = arg.getParentInsn();
|
InsnNode useInsn = arg.getParentInsn();
|
||||||
if (useInsn == null
|
if (useInsn == null
|
||||||
|| useInsn.getType() == InsnType.PHI
|
|| useInsn.getType() == InsnType.PHI
|
||||||
|| useInsn.getType() == InsnType.MERGE) {
|
|| useInsn.getType() == InsnType.MERGE
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
LiteralArg litArg;
|
LiteralArg litArg;
|
||||||
@ -127,7 +128,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
litArg = InsnArg.lit(literal, ArgType.UNKNOWN);
|
litArg = InsnArg.lit(literal, ArgType.UNKNOWN);
|
||||||
}
|
}
|
||||||
if (useInsn.replaceArg(arg, litArg)) {
|
if (useInsn.replaceArg(arg, litArg)) {
|
||||||
fixTypes(mth, useInsn, litArg);
|
litArg.setType(arg.getInitType());
|
||||||
replaceCount++;
|
replaceCount++;
|
||||||
if (useInsn.getType() == InsnType.RETURN) {
|
if (useInsn.getType() == InsnType.RETURN) {
|
||||||
useInsn.setSourceLine(constInsn.getSourceLine());
|
useInsn.setSourceLine(constInsn.getSourceLine());
|
||||||
@ -147,96 +148,4 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
return replaceCount == use.size();
|
return replaceCount == use.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This is method similar to PostTypeInference.process method,
|
|
||||||
* but contains some expensive operations needed only after constant inline
|
|
||||||
*/
|
|
||||||
private static void fixTypes(MethodNode mth, InsnNode insn, LiteralArg litArg) {
|
|
||||||
DexNode dex = mth.dex();
|
|
||||||
PostTypeInference.process(mth, insn);
|
|
||||||
switch (insn.getType()) {
|
|
||||||
case CONST:
|
|
||||||
insn.getArg(0).merge(dex, insn.getResult());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MOVE:
|
|
||||||
insn.getResult().merge(dex, insn.getArg(0));
|
|
||||||
insn.getArg(0).merge(dex, insn.getResult());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IPUT:
|
|
||||||
case SPUT:
|
|
||||||
IndexInsnNode node = (IndexInsnNode) insn;
|
|
||||||
insn.getArg(0).merge(dex, ((FieldInfo) node.getIndex()).getType());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IF:
|
|
||||||
InsnArg firstArg = insn.getArg(0);
|
|
||||||
InsnArg secondArg = insn.getArg(1);
|
|
||||||
if (firstArg == litArg) {
|
|
||||||
firstArg.merge(dex, secondArg);
|
|
||||||
} else {
|
|
||||||
secondArg.merge(dex, firstArg);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CMP_G:
|
|
||||||
case CMP_L:
|
|
||||||
InsnArg arg0 = insn.getArg(0);
|
|
||||||
InsnArg arg1 = insn.getArg(1);
|
|
||||||
if (arg0 == litArg) {
|
|
||||||
arg0.merge(dex, arg1);
|
|
||||||
} else {
|
|
||||||
arg1.merge(dex, arg0);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RETURN:
|
|
||||||
if (insn.getArgsCount() != 0) {
|
|
||||||
insn.getArg(0).merge(dex, mth.getReturnType());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case INVOKE:
|
|
||||||
InvokeNode inv = (InvokeNode) insn;
|
|
||||||
List<ArgType> types = inv.getCallMth().getArgumentsTypes();
|
|
||||||
int count = insn.getArgsCount();
|
|
||||||
int k = types.size() == count ? 0 : -1;
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
InsnArg arg = insn.getArg(i);
|
|
||||||
if (!arg.getType().isTypeKnown()) {
|
|
||||||
ArgType type;
|
|
||||||
if (k >= 0) {
|
|
||||||
type = types.get(k);
|
|
||||||
} else {
|
|
||||||
type = mth.getParentClass().getClassInfo().getType();
|
|
||||||
}
|
|
||||||
arg.merge(dex, type);
|
|
||||||
}
|
|
||||||
k++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ARITH:
|
|
||||||
litArg.merge(dex, insn.getResult());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case APUT:
|
|
||||||
case AGET:
|
|
||||||
if (litArg == insn.getArg(1)) {
|
|
||||||
litArg.merge(dex, ArgType.INT);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case NEW_ARRAY:
|
|
||||||
if (litArg == insn.getArg(0)) {
|
|
||||||
litArg.merge(dex, ArgType.INT);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,168 @@
|
|||||||
|
package jadx.core.dex.visitors;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import jadx.core.codegen.TypeGen;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.instructions.InsnType;
|
||||||
|
import jadx.core.dex.instructions.InvokeNode;
|
||||||
|
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.mods.ConstructorInsn;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||||
|
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||||
|
import jadx.core.utils.BlockUtils;
|
||||||
|
import jadx.core.utils.InstructionRemover;
|
||||||
|
|
||||||
|
@JadxVisitor(
|
||||||
|
name = "ConstructorVisitor",
|
||||||
|
desc = "Replace invoke with constructor call",
|
||||||
|
runAfter = SSATransform.class,
|
||||||
|
runBefore = TypeInferenceVisitor.class
|
||||||
|
)
|
||||||
|
public class ConstructorVisitor extends AbstractVisitor {
|
||||||
|
@Override
|
||||||
|
public void visit(MethodNode mth) {
|
||||||
|
if (mth.isNoCode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceInvoke(mth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void replaceInvoke(MethodNode mth) {
|
||||||
|
InstructionRemover remover = new InstructionRemover(mth);
|
||||||
|
for (BlockNode block : mth.getBasicBlocks()) {
|
||||||
|
remover.setBlock(block);
|
||||||
|
int size = block.getInstructions().size();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
InsnNode insn = block.getInstructions().get(i);
|
||||||
|
if (insn.getType() == InsnType.INVOKE) {
|
||||||
|
processInvoke(mth, block, i, remover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remover.perform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void processInvoke(MethodNode mth, BlockNode block, int indexInBlock, InstructionRemover remover) {
|
||||||
|
ClassNode parentClass = mth.getParentClass();
|
||||||
|
InsnNode insn = block.getInstructions().get(indexInBlock);
|
||||||
|
InvokeNode inv = (InvokeNode) insn;
|
||||||
|
MethodInfo callMth = inv.getCallMth();
|
||||||
|
if (!callMth.isConstructor()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InsnNode instArgAssignInsn = ((RegisterArg) inv.getArg(0)).getAssignInsn();
|
||||||
|
ConstructorInsn co = new ConstructorInsn(mth, inv);
|
||||||
|
boolean remove = false;
|
||||||
|
if (co.isSuper() && (co.getArgsCount() == 0 || parentClass.isEnum())) {
|
||||||
|
remove = true;
|
||||||
|
} else if (co.isThis() && co.getArgsCount() == 0) {
|
||||||
|
MethodNode defCo = parentClass.searchMethodByName(callMth.getShortId());
|
||||||
|
if (defCo == null || defCo.isNoCode()) {
|
||||||
|
// default constructor not implemented
|
||||||
|
remove = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// remove super() call in instance initializer
|
||||||
|
if (parentClass.isAnonymous() && mth.isDefaultConstructor() && co.isSuper()) {
|
||||||
|
remove = true;
|
||||||
|
}
|
||||||
|
if (remove) {
|
||||||
|
remover.add(insn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (co.isNewInstance()) {
|
||||||
|
InsnNode newInstInsn = removeAssignChain(mth, instArgAssignInsn, remover, InsnType.NEW_INSTANCE);
|
||||||
|
if (newInstInsn != null) {
|
||||||
|
RegisterArg instArg = newInstInsn.getResult();
|
||||||
|
RegisterArg resultArg = co.getResult();
|
||||||
|
if (!resultArg.equals(instArg)) {
|
||||||
|
// replace all usages of 'instArg' with result of this constructor instruction
|
||||||
|
for (RegisterArg useArg : new ArrayList<>(instArg.getSVar().getUseList())) {
|
||||||
|
RegisterArg dup = resultArg.duplicate();
|
||||||
|
InsnNode parentInsn = useArg.getParentInsn();
|
||||||
|
parentInsn.replaceArg(useArg, dup);
|
||||||
|
dup.setParentInsn(parentInsn);
|
||||||
|
resultArg.getSVar().use(dup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newInstInsn.setResult(null); // don't unbind result arg on remove
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConstructorInsn replace = processConstructor(mth, co);
|
||||||
|
if (replace != null) {
|
||||||
|
co = replace;
|
||||||
|
}
|
||||||
|
BlockUtils.replaceInsn(block, indexInBlock, co);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace call of synthetic constructor
|
||||||
|
*/
|
||||||
|
private static ConstructorInsn processConstructor(MethodNode mth, ConstructorInsn co) {
|
||||||
|
MethodNode callMth = mth.dex().resolveMethod(co.getCallMth());
|
||||||
|
if (callMth == null
|
||||||
|
|| !callMth.getAccessFlags().isSynthetic()
|
||||||
|
|| !allArgsNull(co)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ClassNode classNode = mth.dex().resolveClass(callMth.getParentClass().getClassInfo());
|
||||||
|
if (classNode == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
RegisterArg instanceArg = co.getInstanceArg();
|
||||||
|
boolean passThis = instanceArg.isThis();
|
||||||
|
String ctrId = "<init>(" + (passThis ? TypeGen.signature(instanceArg.getInitType()) : "") + ")V";
|
||||||
|
MethodNode defCtr = classNode.searchMethodByName(ctrId);
|
||||||
|
if (defCtr == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ConstructorInsn newInsn = new ConstructorInsn(defCtr.getMethodInfo(), co.getCallType(), instanceArg);
|
||||||
|
newInsn.setResult(co.getResult());
|
||||||
|
return newInsn;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean allArgsNull(ConstructorInsn insn) {
|
||||||
|
for (InsnArg insnArg : insn.getArguments()) {
|
||||||
|
if (insnArg.isLiteral()) {
|
||||||
|
LiteralArg lit = (LiteralArg) insnArg;
|
||||||
|
if (lit.getLiteral() != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove instructions on 'move' chain until instruction with type 'insnType'
|
||||||
|
*/
|
||||||
|
private static InsnNode removeAssignChain(MethodNode mth, InsnNode insn, InstructionRemover remover, InsnType insnType) {
|
||||||
|
if (insn == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (insn.isAttrStorageEmpty()) {
|
||||||
|
remover.add(insn);
|
||||||
|
} else {
|
||||||
|
BlockUtils.replaceInsn(mth, insn, new InsnNode(InsnType.NOP, 0));
|
||||||
|
}
|
||||||
|
InsnType type = insn.getType();
|
||||||
|
if (type == insnType) {
|
||||||
|
return insn;
|
||||||
|
}
|
||||||
|
if (type == InsnType.MOVE) {
|
||||||
|
RegisterArg arg = (RegisterArg) insn.getArg(0);
|
||||||
|
return removeAssignChain(mth, arg.getAssignInsn(), remover, insnType);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,80 +0,0 @@
|
|||||||
package jadx.core.dex.visitors;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
|
||||||
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.nodes.parser.DebugInfoParser;
|
|
||||||
import jadx.core.utils.BlockUtils;
|
|
||||||
import jadx.core.utils.ErrorsCounter;
|
|
||||||
import jadx.core.utils.exceptions.DecodeException;
|
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
|
||||||
|
|
||||||
public class DebugInfoVisitor extends AbstractVisitor {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DebugInfoVisitor.class);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(MethodNode mth) throws JadxException {
|
|
||||||
try {
|
|
||||||
int debugOffset = mth.getDebugInfoOffset();
|
|
||||||
if (debugOffset > 0 && mth.dex().checkOffset(debugOffset)) {
|
|
||||||
processDebugInfo(mth, debugOffset);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error("Error in debug info parser: {}", ErrorsCounter.formatMsg(mth, e.getMessage()), e);
|
|
||||||
} finally {
|
|
||||||
mth.unloadInsnArr();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processDebugInfo(MethodNode mth, int debugOffset) throws DecodeException {
|
|
||||||
InsnNode[] insnArr = mth.getInstructions();
|
|
||||||
DebugInfoParser debugInfoParser = new DebugInfoParser(mth, debugOffset, insnArr);
|
|
||||||
debugInfoParser.process();
|
|
||||||
|
|
||||||
if (insnArr.length != 0) {
|
|
||||||
setMethodSourceLine(mth, insnArr);
|
|
||||||
}
|
|
||||||
if (!mth.getReturnType().equals(ArgType.VOID)) {
|
|
||||||
setLineForReturn(mth, insnArr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fix debug info for splitter 'return' instructions
|
|
||||||
*/
|
|
||||||
private void setLineForReturn(MethodNode mth, InsnNode[] insnArr) {
|
|
||||||
for (BlockNode exit : mth.getExitBlocks()) {
|
|
||||||
InsnNode ret = BlockUtils.getLastInsn(exit);
|
|
||||||
if (ret != null) {
|
|
||||||
InsnNode oldRet = insnArr[ret.getOffset()];
|
|
||||||
if (oldRet != ret) {
|
|
||||||
RegisterArg oldArg = (RegisterArg) oldRet.getArg(0);
|
|
||||||
RegisterArg newArg = (RegisterArg) ret.getArg(0);
|
|
||||||
newArg.mergeDebugInfo(oldArg.getType(), oldArg.getName());
|
|
||||||
ret.setSourceLine(oldRet.getSourceLine());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set method source line from first instruction
|
|
||||||
*/
|
|
||||||
private void setMethodSourceLine(MethodNode mth, InsnNode[] insnArr) {
|
|
||||||
for (InsnNode insn : insnArr) {
|
|
||||||
if (insn != null) {
|
|
||||||
int line = insn.getSourceLine();
|
|
||||||
if (line != 0) {
|
|
||||||
mth.setSourceLine(line - 1);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,5 @@
|
|||||||
package jadx.core.dex.visitors;
|
package jadx.core.dex.visitors;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -9,8 +8,6 @@ import java.util.Map;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.core.codegen.TypeGen;
|
|
||||||
import jadx.core.deobf.NameMapper;
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||||
@ -24,7 +21,6 @@ import jadx.core.dex.instructions.FillArrayNode;
|
|||||||
import jadx.core.dex.instructions.FilledNewArrayNode;
|
import jadx.core.dex.instructions.FilledNewArrayNode;
|
||||||
import jadx.core.dex.instructions.IndexInsnNode;
|
import jadx.core.dex.instructions.IndexInsnNode;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
import jadx.core.dex.instructions.InvokeNode;
|
|
||||||
import jadx.core.dex.instructions.NewArrayNode;
|
import jadx.core.dex.instructions.NewArrayNode;
|
||||||
import jadx.core.dex.instructions.SwitchNode;
|
import jadx.core.dex.instructions.SwitchNode;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
@ -46,6 +42,8 @@ import jadx.core.utils.InsnUtils;
|
|||||||
import jadx.core.utils.InstructionRemover;
|
import jadx.core.utils.InstructionRemover;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
import static jadx.core.utils.BlockUtils.replaceInsn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Visitor for modify method instructions
|
* Visitor for modify method instructions
|
||||||
* (remove, replace, process exception handlers)
|
* (remove, replace, process exception handlers)
|
||||||
@ -67,8 +65,6 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
InstructionRemover remover = new InstructionRemover(mth);
|
InstructionRemover remover = new InstructionRemover(mth);
|
||||||
replaceStep(mth, remover);
|
replaceStep(mth, remover);
|
||||||
removeStep(mth, remover);
|
removeStep(mth, remover);
|
||||||
|
|
||||||
checkArgsNames(mth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void replaceStep(MethodNode mth, InstructionRemover remover) {
|
private static void replaceStep(MethodNode mth, InstructionRemover remover) {
|
||||||
@ -79,8 +75,8 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
InsnNode insn = block.getInstructions().get(i);
|
InsnNode insn = block.getInstructions().get(i);
|
||||||
switch (insn.getType()) {
|
switch (insn.getType()) {
|
||||||
case INVOKE:
|
case CONSTRUCTOR:
|
||||||
processInvoke(mth, block, i, remover);
|
processAnonymousConstructor(mth, ((ConstructorInsn) insn));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CONST:
|
case CONST:
|
||||||
@ -115,24 +111,20 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case NEW_ARRAY:
|
case NEW_ARRAY:
|
||||||
// create array in 'fill-array' instruction
|
// replace with filled array if 'fill-array' is next instruction
|
||||||
int next = i + 1;
|
int next = i + 1;
|
||||||
if (next < size) {
|
if (next < size) {
|
||||||
InsnNode ni = block.getInstructions().get(next);
|
InsnNode ni = block.getInstructions().get(next);
|
||||||
if (ni.getType() == InsnType.FILL_ARRAY) {
|
if (ni.getType() == InsnType.FILL_ARRAY) {
|
||||||
ni.getResult().merge(mth.dex(), insn.getResult());
|
InsnNode filledArr = makeFilledArrayInsn(mth, (NewArrayNode) insn, (FillArrayNode) ni);
|
||||||
ArgType arrType = ((NewArrayNode) insn).getArrayType();
|
if (filledArr != null) {
|
||||||
((FillArrayNode) ni).mergeElementType(mth.dex(), arrType.getArrayElement());
|
replaceInsn(block, i, filledArr);
|
||||||
remover.add(insn);
|
remover.add(ni);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FILL_ARRAY:
|
|
||||||
InsnNode filledArr = makeFilledArrayInsn(mth, (FillArrayNode) insn);
|
|
||||||
replaceInsn(block, i, filledArr);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MOVE_EXCEPTION:
|
case MOVE_EXCEPTION:
|
||||||
processMoveException(block, insn, remover);
|
processMoveException(block, insn, remover);
|
||||||
break;
|
break;
|
||||||
@ -151,6 +143,18 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CHECK_CAST:
|
||||||
|
InsnArg castArg = insn.getArg(0);
|
||||||
|
ArgType castType = (ArgType) ((IndexInsnNode) insn).getIndex();
|
||||||
|
if (!ArgType.isCastNeeded(mth.dex(), castArg.getType(), castType)
|
||||||
|
|| isCastDuplicate((IndexInsnNode) insn)) {
|
||||||
|
InsnNode insnNode = new InsnNode(InsnType.MOVE, 1);
|
||||||
|
insnNode.setResult(insn.getResult());
|
||||||
|
insnNode.addArg(castArg);
|
||||||
|
replaceInsn(block, i, insnNode);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -159,6 +163,21 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isCastDuplicate(IndexInsnNode castInsn) {
|
||||||
|
InsnArg arg = castInsn.getArg(0);
|
||||||
|
if (arg.isRegister()) {
|
||||||
|
SSAVar sVar = ((RegisterArg) arg).getSVar();
|
||||||
|
if (sVar != null && sVar.getUseCount() == 1 && !sVar.isUsedInPhi()) {
|
||||||
|
InsnNode assignInsn = sVar.getAssign().getParentInsn();
|
||||||
|
if (assignInsn != null && assignInsn.getType() == InsnType.CHECK_CAST) {
|
||||||
|
ArgType assignCastType = (ArgType) ((IndexInsnNode) assignInsn).getIndex();
|
||||||
|
return assignCastType.equals(castInsn.getIndex());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove unnecessary instructions
|
* Remove unnecessary instructions
|
||||||
*/
|
*/
|
||||||
@ -181,60 +200,6 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void processInvoke(MethodNode mth, BlockNode block, int insnNumber, InstructionRemover remover) {
|
|
||||||
ClassNode parentClass = mth.getParentClass();
|
|
||||||
InsnNode insn = block.getInstructions().get(insnNumber);
|
|
||||||
InvokeNode inv = (InvokeNode) insn;
|
|
||||||
MethodInfo callMth = inv.getCallMth();
|
|
||||||
if (!callMth.isConstructor()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
InsnNode instArgAssignInsn = ((RegisterArg) inv.getArg(0)).getAssignInsn();
|
|
||||||
ConstructorInsn co = new ConstructorInsn(mth, inv);
|
|
||||||
boolean remove = false;
|
|
||||||
if (co.isSuper() && (co.getArgsCount() == 0 || parentClass.isEnum())) {
|
|
||||||
remove = true;
|
|
||||||
} else if (co.isThis() && co.getArgsCount() == 0) {
|
|
||||||
MethodNode defCo = parentClass.searchMethodByName(callMth.getShortId());
|
|
||||||
if (defCo == null || defCo.isNoCode()) {
|
|
||||||
// default constructor not implemented
|
|
||||||
remove = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// remove super() call in instance initializer
|
|
||||||
if (parentClass.isAnonymous() && mth.isDefaultConstructor() && co.isSuper()) {
|
|
||||||
remove = true;
|
|
||||||
}
|
|
||||||
if (remove) {
|
|
||||||
remover.add(insn);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (co.isNewInstance()) {
|
|
||||||
InsnNode newInstInsn = removeAssignChain(instArgAssignInsn, remover, InsnType.NEW_INSTANCE);
|
|
||||||
if (newInstInsn != null) {
|
|
||||||
RegisterArg instArg = newInstInsn.getResult();
|
|
||||||
RegisterArg resultArg = co.getResult();
|
|
||||||
if (!resultArg.equals(instArg)) {
|
|
||||||
// replace all usages of 'instArg' with result of this constructor instruction
|
|
||||||
for (RegisterArg useArg : new ArrayList<>(instArg.getSVar().getUseList())) {
|
|
||||||
RegisterArg dup = resultArg.duplicate();
|
|
||||||
InsnNode parentInsn = useArg.getParentInsn();
|
|
||||||
parentInsn.replaceArg(useArg, dup);
|
|
||||||
dup.setParentInsn(parentInsn);
|
|
||||||
resultArg.getSVar().use(dup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ConstructorInsn replace = processConstructor(mth, co);
|
|
||||||
if (replace != null) {
|
|
||||||
co = replace;
|
|
||||||
}
|
|
||||||
replaceInsn(block, insnNumber, co);
|
|
||||||
|
|
||||||
processAnonymousConstructor(mth, co);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void processAnonymousConstructor(MethodNode mth, ConstructorInsn co) {
|
private static void processAnonymousConstructor(MethodNode mth, ConstructorInsn co) {
|
||||||
MethodInfo callMth = co.getCallMth();
|
MethodInfo callMth = co.getCallMth();
|
||||||
MethodNode callMthNode = mth.dex().resolveMethod(callMth);
|
MethodNode callMthNode = mth.dex().resolveMethod(callMth);
|
||||||
@ -331,33 +296,8 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
return parentInsn;
|
return parentInsn;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static InsnNode makeFilledArrayInsn(MethodNode mth, NewArrayNode newArrayNode, FillArrayNode insn) {
|
||||||
* Replace call of synthetic constructor
|
ArgType insnArrayType = newArrayNode.getArrayType();
|
||||||
*/
|
|
||||||
private static ConstructorInsn processConstructor(MethodNode mth, ConstructorInsn co) {
|
|
||||||
MethodNode callMth = mth.dex().resolveMethod(co.getCallMth());
|
|
||||||
if (callMth == null
|
|
||||||
|| !callMth.getAccessFlags().isSynthetic()
|
|
||||||
|| !allArgsNull(co)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
ClassNode classNode = mth.dex().resolveClass(callMth.getParentClass().getClassInfo());
|
|
||||||
if (classNode == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
boolean passThis = co.getArgsCount() >= 1 && co.getArg(0).isThis();
|
|
||||||
String ctrId = "<init>(" + (passThis ? TypeGen.signature(co.getArg(0).getType()) : "") + ")V";
|
|
||||||
MethodNode defCtr = classNode.searchMethodByName(ctrId);
|
|
||||||
if (defCtr == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
ConstructorInsn newInsn = new ConstructorInsn(defCtr.getMethodInfo(), co.getCallType(), co.getInstanceArg());
|
|
||||||
newInsn.setResult(co.getResult());
|
|
||||||
return newInsn;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static InsnNode makeFilledArrayInsn(MethodNode mth, FillArrayNode insn) {
|
|
||||||
ArgType insnArrayType = insn.getResult().getType();
|
|
||||||
ArgType insnElementType = insnArrayType.getArrayElement();
|
ArgType insnElementType = insnArrayType.getArrayElement();
|
||||||
ArgType elType = insn.getElementType();
|
ArgType elType = insn.getElementType();
|
||||||
if (!elType.isTypeKnown()
|
if (!elType.isTypeKnown()
|
||||||
@ -378,12 +318,10 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
throw new JadxRuntimeException("Null array element type");
|
throw new JadxRuntimeException("Null array element type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
insn.mergeElementType(mth.dex(), elType);
|
|
||||||
elType = insn.getElementType();
|
|
||||||
|
|
||||||
List<LiteralArg> list = insn.getLiteralArgs();
|
List<LiteralArg> list = insn.getLiteralArgs(elType);
|
||||||
InsnNode filledArr = new FilledNewArrayNode(elType, list.size());
|
InsnNode filledArr = new FilledNewArrayNode(elType, list.size());
|
||||||
filledArr.setResult(insn.getResult());
|
filledArr.setResult(newArrayNode.getResult());
|
||||||
for (LiteralArg arg : list) {
|
for (LiteralArg arg : list) {
|
||||||
FieldNode f = mth.getParentClass().getConstFieldByLiteralArg(arg);
|
FieldNode f = mth.getParentClass().getConstFieldByLiteralArg(arg);
|
||||||
if (f != null) {
|
if (f != null) {
|
||||||
@ -396,39 +334,6 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
return filledArr;
|
return filledArr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean allArgsNull(ConstructorInsn insn) {
|
|
||||||
for (InsnArg insnArg : insn.getArguments()) {
|
|
||||||
if (insnArg.isLiteral()) {
|
|
||||||
LiteralArg lit = (LiteralArg) insnArg;
|
|
||||||
if (lit.getLiteral() != 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove instructions on 'move' chain until instruction with type 'insnType'
|
|
||||||
*/
|
|
||||||
private static InsnNode removeAssignChain(InsnNode insn, InstructionRemover remover, InsnType insnType) {
|
|
||||||
if (insn == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
remover.add(insn);
|
|
||||||
InsnType type = insn.getType();
|
|
||||||
if (type == insnType) {
|
|
||||||
return insn;
|
|
||||||
}
|
|
||||||
if (type == InsnType.MOVE) {
|
|
||||||
RegisterArg arg = (RegisterArg) insn.getArg(0);
|
|
||||||
return removeAssignChain(arg.getAssignInsn(), remover, insnType);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void processMoveException(BlockNode block, InsnNode insn, InstructionRemover remover) {
|
private static void processMoveException(BlockNode block, InsnNode insn, InstructionRemover remover) {
|
||||||
ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER);
|
ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER);
|
||||||
if (excHandlerAttr == null) {
|
if (excHandlerAttr == null) {
|
||||||
@ -457,25 +362,4 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
replaceInsn(block, 0, moveInsn);
|
replaceInsn(block, 0, moveInsn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace insn by index i in block,
|
|
||||||
* for proper copy attributes, assume attributes are not overlap
|
|
||||||
*/
|
|
||||||
private static void replaceInsn(BlockNode block, int i, InsnNode insn) {
|
|
||||||
InsnNode prevInsn = block.getInstructions().get(i);
|
|
||||||
insn.copyAttributesFrom(prevInsn);
|
|
||||||
insn.setSourceLine(prevInsn.getSourceLine());
|
|
||||||
block.getInstructions().set(i, insn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkArgsNames(MethodNode mth) {
|
|
||||||
for (RegisterArg arg : mth.getArguments(false)) {
|
|
||||||
String name = arg.getName();
|
|
||||||
if (name != null && NameMapper.isReserved(name)) {
|
|
||||||
name = name + "_";
|
|
||||||
arg.getSVar().setName(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -176,7 +176,8 @@ public class SimplifyVisitor extends AbstractVisitor {
|
|||||||
if (constrIndex != -1) { // If we found a CONSTRUCTOR, is it a StringBuilder?
|
if (constrIndex != -1) { // If we found a CONSTRUCTOR, is it a StringBuilder?
|
||||||
ConstructorInsn constr = (ConstructorInsn) chain.get(constrIndex);
|
ConstructorInsn constr = (ConstructorInsn) chain.get(constrIndex);
|
||||||
if (constr.getClassType().getFullName().equals(Consts.CLASS_STRING_BUILDER)) {
|
if (constr.getClassType().getFullName().equals(Consts.CLASS_STRING_BUILDER)) {
|
||||||
int len = chain.size(), argInd = 1;
|
int len = chain.size();
|
||||||
|
int argInd = 1;
|
||||||
InsnNode concatInsn = new InsnNode(InsnType.STR_CONCAT, len - 1);
|
InsnNode concatInsn = new InsnNode(InsnType.STR_CONCAT, len - 1);
|
||||||
InsnNode argInsn;
|
InsnNode argInsn;
|
||||||
if (constrIndex > 0) { // There was an arg to the StringBuilder constr
|
if (constrIndex > 0) { // There was an arg to the StringBuilder constr
|
||||||
@ -282,14 +283,13 @@ public class SimplifyVisitor extends AbstractVisitor {
|
|||||||
if (wrapType == InsnType.ARITH) {
|
if (wrapType == InsnType.ARITH) {
|
||||||
ArithNode ar = (ArithNode) wrap;
|
ArithNode ar = (ArithNode) wrap;
|
||||||
return new ArithNode(ar.getOp(), fArg, ar.getArg(1));
|
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));
|
|
||||||
}
|
}
|
||||||
|
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 (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.debug("Can't convert field arith insn: {}, mth: {}", insn, mth, e);
|
LOG.debug("Can't convert field arith insn: {}, mth: {}", insn, mth, e);
|
||||||
}
|
}
|
||||||
|
@ -507,7 +507,7 @@ public class BlockFinallyExtract extends AbstractVisitor {
|
|||||||
RegisterArg mapReg = removeInfo.getRegMap().get(remArg);
|
RegisterArg mapReg = removeInfo.getRegMap().get(remArg);
|
||||||
if (mapReg == null) {
|
if (mapReg == null) {
|
||||||
removeInfo.getRegMap().put(remReg, fReg);
|
removeInfo.getRegMap().put(remReg, fReg);
|
||||||
} else if (!mapReg.equalRegisterAndType(fReg)) {
|
} else if (!mapReg.equalRegister(fReg)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package jadx.core.dex.visitors.blocksmaker;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -61,17 +62,27 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
markReturnBlocks(mth);
|
markReturnBlocks(mth);
|
||||||
|
|
||||||
if (i++ > 100) {
|
if (i++ > 100) {
|
||||||
throw new AssertionError("Can't fix method cfg: " + mth);
|
mth.addWarn("CFG modification limit reached, blocks count: " + mth.getBasicBlocks().size());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
checkForUnreachableBlocks(mth);
|
||||||
|
|
||||||
computeDominanceFrontier(mth);
|
computeDominanceFrontier(mth);
|
||||||
registerLoops(mth);
|
registerLoops(mth);
|
||||||
processNestedLoops(mth);
|
processNestedLoops(mth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void checkForUnreachableBlocks(MethodNode mth) {
|
||||||
|
mth.getBasicBlocks().forEach(block -> {
|
||||||
|
if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) {
|
||||||
|
throw new JadxRuntimeException("Unreachable block: " + block);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean canRemoveBlock(BlockNode block) {
|
private static boolean canRemoveBlock(BlockNode block) {
|
||||||
return block.getInstructions().isEmpty()
|
return block.getInstructions().isEmpty()
|
||||||
&& !block.isSynthetic()
|
|
||||||
&& block.isAttrStorageEmpty()
|
&& block.isAttrStorageEmpty()
|
||||||
&& block.getSuccessors().size() <= 1
|
&& block.getSuccessors().size() <= 1
|
||||||
&& !block.getPredecessors().isEmpty();
|
&& !block.getPredecessors().isEmpty();
|
||||||
@ -112,6 +123,7 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
if (lastInsn != null && lastInsn.getType() == InsnType.IF) {
|
if (lastInsn != null && lastInsn.getType() == InsnType.IF) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// TODO: implement insn extraction into separate block for partial predecessors
|
||||||
int sameInsnCount = getSameLastInsnCount(predecessors);
|
int sameInsnCount = getSameLastInsnCount(predecessors);
|
||||||
if (sameInsnCount > 0) {
|
if (sameInsnCount > 0) {
|
||||||
List<InsnNode> insns = getLastInsns(predecessors.get(0), sameInsnCount);
|
List<InsnNode> insns = getLastInsns(predecessors.get(0), sameInsnCount);
|
||||||
@ -187,10 +199,20 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BlockNode entryBlock = mth.getEnterBlock();
|
BlockNode entryBlock = mth.getEnterBlock();
|
||||||
|
calcDominators(basicBlocks, entryBlock);
|
||||||
|
markLoops(mth);
|
||||||
|
|
||||||
|
// clear self dominance
|
||||||
|
basicBlocks.forEach(block -> block.getDoms().clear(block.getId()));
|
||||||
|
|
||||||
|
calcImmediateDominators(basicBlocks, entryBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void calcDominators(List<BlockNode> basicBlocks, BlockNode entryBlock) {
|
||||||
entryBlock.getDoms().clear();
|
entryBlock.getDoms().clear();
|
||||||
entryBlock.getDoms().set(entryBlock.getId());
|
entryBlock.getDoms().set(entryBlock.getId());
|
||||||
|
|
||||||
BitSet dset = new BitSet(nBlocks);
|
BitSet domSet = new BitSet(basicBlocks.size());
|
||||||
boolean changed;
|
boolean changed;
|
||||||
do {
|
do {
|
||||||
changed = false;
|
changed = false;
|
||||||
@ -200,25 +222,21 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
BitSet d = block.getDoms();
|
BitSet d = block.getDoms();
|
||||||
if (!changed) {
|
if (!changed) {
|
||||||
dset.clear();
|
domSet.clear();
|
||||||
dset.or(d);
|
domSet.or(d);
|
||||||
}
|
}
|
||||||
for (BlockNode pred : block.getPredecessors()) {
|
for (BlockNode pred : block.getPredecessors()) {
|
||||||
d.and(pred.getDoms());
|
d.and(pred.getDoms());
|
||||||
}
|
}
|
||||||
d.set(block.getId());
|
d.set(block.getId());
|
||||||
if (!changed && !d.equals(dset)) {
|
if (!changed && !d.equals(domSet)) {
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (changed);
|
} while (changed);
|
||||||
|
}
|
||||||
|
|
||||||
markLoops(mth);
|
private static void calcImmediateDominators(List<BlockNode> basicBlocks, BlockNode entryBlock) {
|
||||||
|
|
||||||
// clear self dominance
|
|
||||||
basicBlocks.forEach(block -> block.getDoms().clear(block.getId()));
|
|
||||||
|
|
||||||
// calculate immediate dominators
|
|
||||||
for (BlockNode block : basicBlocks) {
|
for (BlockNode block : basicBlocks) {
|
||||||
if (block == entryBlock) {
|
if (block == entryBlock) {
|
||||||
continue;
|
continue;
|
||||||
@ -305,7 +323,7 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
private static void markLoops(MethodNode mth) {
|
private static void markLoops(MethodNode mth) {
|
||||||
mth.getBasicBlocks().forEach(block -> {
|
mth.getBasicBlocks().forEach(block -> {
|
||||||
// Every successor that dominates its predecessor is a header of a loop,
|
// Every successor that dominates its predecessor is a header of a loop,
|
||||||
// block -> succ is a back edge.
|
// block -> successor is a back edge.
|
||||||
block.getSuccessors().forEach(successor -> {
|
block.getSuccessors().forEach(successor -> {
|
||||||
if (block.getDoms().get(successor.getId())) {
|
if (block.getDoms().get(successor.getId())) {
|
||||||
successor.add(AFlag.LOOP_START);
|
successor.add(AFlag.LOOP_START);
|
||||||
@ -354,14 +372,7 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean modifyBlocksTree(MethodNode mth) {
|
private static boolean modifyBlocksTree(MethodNode mth) {
|
||||||
List<BlockNode> basicBlocks = mth.getBasicBlocks();
|
for (BlockNode block : mth.getBasicBlocks()) {
|
||||||
for (BlockNode block : basicBlocks) {
|
|
||||||
if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) {
|
|
||||||
throw new JadxRuntimeException("Unreachable block: " + block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (BlockNode block : basicBlocks) {
|
|
||||||
if (checkLoops(mth, block)) {
|
if (checkLoops(mth, block)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -389,59 +400,95 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean checkLoops(MethodNode mth, BlockNode block) {
|
private static boolean checkLoops(MethodNode mth, BlockNode block) {
|
||||||
// check loops
|
|
||||||
List<LoopInfo> loops = block.getAll(AType.LOOP);
|
List<LoopInfo> loops = block.getAll(AType.LOOP);
|
||||||
if (loops.size() > 1) {
|
int loopsCount = loops.size();
|
||||||
boolean oneHeader = true;
|
if (loopsCount == 0) {
|
||||||
for (LoopInfo loop : loops) {
|
return false;
|
||||||
if (loop.getStart() != block) {
|
}
|
||||||
oneHeader = false;
|
if (loopsCount > 1 && splitLoops(mth, block, loops)) {
|
||||||
break;
|
return true;
|
||||||
|
}
|
||||||
|
if (loopsCount == 1) {
|
||||||
|
LoopInfo loop = loops.get(0);
|
||||||
|
return insertBlocksForBreak(mth, loop)
|
||||||
|
|| insertBlocksForContinue(mth, loop)
|
||||||
|
|| insertBlockForProdecessors(mth, loop);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert additional blocks for possible 'break' insertion
|
||||||
|
*/
|
||||||
|
private static boolean insertBlocksForBreak(MethodNode mth, LoopInfo loop) {
|
||||||
|
boolean change = false;
|
||||||
|
List<Edge> edges = loop.getExitEdges();
|
||||||
|
if (!edges.isEmpty()) {
|
||||||
|
for (Edge edge : edges) {
|
||||||
|
BlockNode target = edge.getTarget();
|
||||||
|
BlockNode source = edge.getSource();
|
||||||
|
if (!target.contains(AFlag.SYNTHETIC) && !source.contains(AFlag.SYNTHETIC)) {
|
||||||
|
BlockSplitter.insertBlockBetween(mth, source, target);
|
||||||
|
change = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oneHeader) {
|
|
||||||
// several back edges connected to one loop header => make additional block
|
|
||||||
BlockNode newLoopEnd = BlockSplitter.startNewBlock(mth, block.getStartOffset());
|
|
||||||
newLoopEnd.add(AFlag.SYNTHETIC);
|
|
||||||
connect(newLoopEnd, block);
|
|
||||||
for (LoopInfo la : loops) {
|
|
||||||
BlockSplitter.replaceConnection(la.getEnd(), block, newLoopEnd);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (loops.size() == 1) {
|
return change;
|
||||||
LoopInfo loop = loops.get(0);
|
}
|
||||||
// insert additional blocks for possible 'break' insertion
|
|
||||||
List<Edge> edges = loop.getExitEdges();
|
/**
|
||||||
if (!edges.isEmpty()) {
|
* Insert additional blocks for possible 'continue' insertion
|
||||||
boolean change = false;
|
*/
|
||||||
for (Edge edge : edges) {
|
private static boolean insertBlocksForContinue(MethodNode mth, LoopInfo loop) {
|
||||||
BlockNode target = edge.getTarget();
|
BlockNode loopEnd = loop.getEnd();
|
||||||
BlockNode source = edge.getSource();
|
boolean change = false;
|
||||||
if (!target.contains(AFlag.SYNTHETIC) && !source.contains(AFlag.SYNTHETIC)) {
|
List<BlockNode> preds = loopEnd.getPredecessors();
|
||||||
BlockSplitter.insertBlockBetween(mth, source, target);
|
if (preds.size() > 1) {
|
||||||
change = true;
|
for (BlockNode pred : new ArrayList<>(preds)) {
|
||||||
}
|
if (!pred.contains(AFlag.SYNTHETIC)) {
|
||||||
}
|
BlockSplitter.insertBlockBetween(mth, pred, loopEnd);
|
||||||
if (change) {
|
change = true;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// insert additional blocks for possible 'continue' insertion
|
}
|
||||||
BlockNode loopEnd = loop.getEnd();
|
return change;
|
||||||
if (loopEnd.getPredecessors().size() > 1) {
|
}
|
||||||
boolean change = false;
|
|
||||||
List<BlockNode> nodes = new ArrayList<>(loopEnd.getPredecessors());
|
/**
|
||||||
for (BlockNode pred : nodes) {
|
* Insert additional block if loop header has several predecessors (exclude back edges)
|
||||||
if (!pred.contains(AFlag.SYNTHETIC)) {
|
*/
|
||||||
BlockSplitter.insertBlockBetween(mth, pred, loopEnd);
|
private static boolean insertBlockForProdecessors(MethodNode mth, LoopInfo loop) {
|
||||||
change = true;
|
BlockNode loopHeader = loop.getStart();
|
||||||
}
|
List<BlockNode> preds = loopHeader.getPredecessors();
|
||||||
}
|
if (preds.size() > 2) {
|
||||||
return change;
|
List<BlockNode> blocks = new LinkedList<>(preds);
|
||||||
|
blocks.removeIf(block -> block.contains(AFlag.LOOP_END));
|
||||||
|
BlockNode first = blocks.remove(0);
|
||||||
|
BlockNode preHeader = BlockSplitter.insertBlockBetween(mth, first, loopHeader);
|
||||||
|
blocks.forEach(block -> BlockSplitter.replaceConnection(block, loopHeader, preHeader));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean splitLoops(MethodNode mth, BlockNode block, List<LoopInfo> loops) {
|
||||||
|
boolean oneHeader = true;
|
||||||
|
for (LoopInfo loop : loops) {
|
||||||
|
if (loop.getStart() != block) {
|
||||||
|
oneHeader = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (oneHeader) {
|
||||||
|
// several back edges connected to one loop header => make additional block
|
||||||
|
BlockNode newLoopEnd = BlockSplitter.startNewBlock(mth, block.getStartOffset());
|
||||||
|
newLoopEnd.add(AFlag.SYNTHETIC);
|
||||||
|
connect(newLoopEnd, block);
|
||||||
|
for (LoopInfo la : loops) {
|
||||||
|
BlockSplitter.replaceConnection(la.getEnd(), block, newLoopEnd);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -518,7 +565,7 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
InsnNode insn = new InsnNode(returnInsn.getType(), returnInsn.getArgsCount());
|
InsnNode insn = new InsnNode(returnInsn.getType(), returnInsn.getArgsCount());
|
||||||
if (returnInsn.getArgsCount() == 1) {
|
if (returnInsn.getArgsCount() == 1) {
|
||||||
RegisterArg arg = (RegisterArg) returnInsn.getArg(0);
|
RegisterArg arg = (RegisterArg) returnInsn.getArg(0);
|
||||||
insn.addArg(InsnArg.reg(arg.getRegNum(), arg.getType()));
|
insn.addArg(InsnArg.reg(arg.getRegNum(), arg.getInitType()));
|
||||||
}
|
}
|
||||||
insn.copyAttributesFrom(returnInsn);
|
insn.copyAttributesFrom(returnInsn);
|
||||||
insn.setOffset(returnInsn.getOffset());
|
insn.setOffset(returnInsn.getOffset());
|
||||||
|
@ -47,6 +47,9 @@ public class BlockSplitter extends AbstractVisitor {
|
|||||||
removeInsns(mth);
|
removeInsns(mth);
|
||||||
removeEmptyDetachedBlocks(mth);
|
removeEmptyDetachedBlocks(mth);
|
||||||
initBlocksInTargetNodes(mth);
|
initBlocksInTargetNodes(mth);
|
||||||
|
|
||||||
|
removeJumpAttributes(mth.getInstructions());
|
||||||
|
mth.unloadInsnArr();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -304,4 +307,12 @@ public class BlockSplitter extends AbstractVisitor {
|
|||||||
&& block.getSuccessors().isEmpty()
|
&& block.getSuccessors().isEmpty()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeJumpAttributes(InsnNode[] insnArr) {
|
||||||
|
for (InsnNode insn : insnArr) {
|
||||||
|
if (insn != null && insn.contains(AType.JUMP)) {
|
||||||
|
insn.remove(AType.JUMP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,223 @@
|
|||||||
|
package jadx.core.dex.visitors.debuginfo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.core.deobf.NameMapper;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||||
|
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.Named;
|
||||||
|
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.dex.visitors.JadxVisitor;
|
||||||
|
import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
|
||||||
|
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||||
|
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||||
|
import jadx.core.dex.visitors.typeinference.TypeUpdateResult;
|
||||||
|
import jadx.core.utils.BlockUtils;
|
||||||
|
import jadx.core.utils.ErrorsCounter;
|
||||||
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
|
@JadxVisitor(
|
||||||
|
name = "Debug Info Parser",
|
||||||
|
desc = "Parse debug information (variable names and types, instruction lines)",
|
||||||
|
runAfter = {
|
||||||
|
SSATransform.class,
|
||||||
|
TypeInferenceVisitor.class,
|
||||||
|
EliminatePhiNodes.class
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public class DebugInfoApplyVisitor extends AbstractVisitor {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(DebugInfoApplyVisitor.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(MethodNode mth) throws JadxException {
|
||||||
|
try {
|
||||||
|
if (mth.contains(AType.LOCAL_VARS_DEBUG_INFO)) {
|
||||||
|
applyDebugInfo(mth);
|
||||||
|
mth.remove(AType.LOCAL_VARS_DEBUG_INFO);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Error to apply debug info: {}", ErrorsCounter.formatMsg(mth, e.getMessage()), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyDebugInfo(MethodNode mth) {
|
||||||
|
mth.getSVars().forEach(ssaVar -> collectVarDebugInfo(mth, ssaVar));
|
||||||
|
|
||||||
|
fixLinesForReturn(mth);
|
||||||
|
fixNamesForPhiInsns(mth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void collectVarDebugInfo(MethodNode mth, SSAVar ssaVar) {
|
||||||
|
Set<RegDebugInfoAttr> debugInfoSet = new HashSet<>(ssaVar.getUseCount() + 1);
|
||||||
|
addRegDbdInfo(debugInfoSet, ssaVar.getAssign());
|
||||||
|
ssaVar.getUseList().forEach(registerArg -> addRegDbdInfo(debugInfoSet, registerArg));
|
||||||
|
|
||||||
|
int dbgCount = debugInfoSet.size();
|
||||||
|
if (dbgCount == 0) {
|
||||||
|
searchDebugInfoByOffset(mth, ssaVar);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (dbgCount == 1) {
|
||||||
|
RegDebugInfoAttr debugInfo = debugInfoSet.iterator().next();
|
||||||
|
applyDebugInfo(mth, ssaVar, debugInfo.getRegType(), debugInfo.getName());
|
||||||
|
} else {
|
||||||
|
LOG.warn("Multiple debug info for {}: {}", ssaVar, debugInfoSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void searchDebugInfoByOffset(MethodNode mth, SSAVar ssaVar) {
|
||||||
|
LocalVarsDebugInfoAttr debugInfoAttr = mth.get(AType.LOCAL_VARS_DEBUG_INFO);
|
||||||
|
if (debugInfoAttr == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Optional<Integer> max = ssaVar.getUseList().stream()
|
||||||
|
.map(DebugInfoApplyVisitor::getInsnOffsetByArg)
|
||||||
|
.max(Integer::compareTo);
|
||||||
|
if (!max.isPresent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int startOffset = getInsnOffsetByArg(ssaVar.getAssign());
|
||||||
|
int endOffset = max.get();
|
||||||
|
int regNum = ssaVar.getRegNum();
|
||||||
|
for (LocalVar localVar : debugInfoAttr.getLocalVars()) {
|
||||||
|
if (localVar.getRegNum() == regNum) {
|
||||||
|
int startAddr = localVar.getStartAddr();
|
||||||
|
int endAddr = localVar.getEndAddr();
|
||||||
|
if (isInside(startOffset, startAddr, endAddr) || isInside(endOffset, startAddr, endAddr)) {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Apply debug info by offset for: {} to {}", ssaVar, localVar);
|
||||||
|
}
|
||||||
|
applyDebugInfo(mth, ssaVar, localVar.getType(), localVar.getName());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isInside(int var, int start, int end) {
|
||||||
|
return start <= var && var <= end;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getInsnOffsetByArg(InsnArg arg) {
|
||||||
|
if (arg != null) {
|
||||||
|
InsnNode insn = arg.getParentInsn();
|
||||||
|
if (insn != null) {
|
||||||
|
return insn.getOffset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void applyDebugInfo(MethodNode mth, SSAVar ssaVar, ArgType type, String varName) {
|
||||||
|
TypeUpdateResult result = mth.root().getTypeUpdate().apply(ssaVar, type);
|
||||||
|
if (result == TypeUpdateResult.REJECT) {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Reject debug info of type: {} and name: '{}' for {}, mth: {}", type, varName, ssaVar, mth);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (NameMapper.isValidIdentifier(varName)) {
|
||||||
|
ssaVar.setName(varName);
|
||||||
|
}
|
||||||
|
detachDebugInfo(ssaVar.getAssign());
|
||||||
|
ssaVar.getUseList().forEach(DebugInfoApplyVisitor::detachDebugInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void detachDebugInfo(RegisterArg reg) {
|
||||||
|
if (reg != null) {
|
||||||
|
reg.remove(AType.REG_DEBUG_INFO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addRegDbdInfo(Set<RegDebugInfoAttr> debugInfo, RegisterArg reg) {
|
||||||
|
RegDebugInfoAttr debugInfoAttr = reg.get(AType.REG_DEBUG_INFO);
|
||||||
|
if (debugInfoAttr != null) {
|
||||||
|
debugInfo.add(debugInfoAttr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix debug info for splitter 'return' instructions
|
||||||
|
*/
|
||||||
|
private static void fixLinesForReturn(MethodNode mth) {
|
||||||
|
if (mth.getReturnType().equals(ArgType.VOID)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InsnNode origReturn = null;
|
||||||
|
List<InsnNode> newReturns = new ArrayList<>(mth.getExitBlocks().size());
|
||||||
|
for (BlockNode exit : mth.getExitBlocks()) {
|
||||||
|
InsnNode ret = BlockUtils.getLastInsn(exit);
|
||||||
|
if (ret != null) {
|
||||||
|
if (ret.contains(AFlag.ORIG_RETURN)) {
|
||||||
|
origReturn = ret;
|
||||||
|
} else {
|
||||||
|
newReturns.add(ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (origReturn != null) {
|
||||||
|
for (InsnNode ret : newReturns) {
|
||||||
|
InsnArg oldArg = origReturn.getArg(0);
|
||||||
|
InsnArg newArg = ret.getArg(0);
|
||||||
|
if (oldArg.isRegister() && newArg.isRegister()) {
|
||||||
|
RegisterArg oldArgReg = (RegisterArg) oldArg;
|
||||||
|
RegisterArg newArgReg = (RegisterArg) newArg;
|
||||||
|
applyDebugInfo(mth, newArgReg.getSVar(), oldArgReg.getType(), oldArgReg.getName());
|
||||||
|
}
|
||||||
|
ret.setSourceLine(origReturn.getSourceLine());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void fixNamesForPhiInsns(MethodNode mth) {
|
||||||
|
mth.getSVars().forEach(ssaVar -> {
|
||||||
|
PhiInsn phiInsn = ssaVar.getUsedInPhi();
|
||||||
|
if (phiInsn != null) {
|
||||||
|
Set<String> names = new HashSet<>(1 + phiInsn.getArgsCount());
|
||||||
|
addArgName(phiInsn.getResult(), names);
|
||||||
|
phiInsn.getArguments().forEach(arg -> addArgName(arg, names));
|
||||||
|
if (names.size() == 1) {
|
||||||
|
setNameForInsn(phiInsn, names.iterator().next());
|
||||||
|
} else if (names.size() > 1) {
|
||||||
|
LOG.warn("Different names in phi insn: {}, use first", names);
|
||||||
|
setNameForInsn(phiInsn, names.iterator().next());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addArgName(InsnArg arg, Set<String> names) {
|
||||||
|
if (arg instanceof Named) {
|
||||||
|
String name = ((Named) arg).getName();
|
||||||
|
if (name != null) {
|
||||||
|
names.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setNameForInsn(PhiInsn phiInsn, String name) {
|
||||||
|
phiInsn.getResult().setName(name);
|
||||||
|
phiInsn.getArguments().forEach(arg -> {
|
||||||
|
if (arg instanceof Named) {
|
||||||
|
((Named) arg).setName(name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
package jadx.core.dex.visitors.debuginfo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.core.Consts;
|
||||||
|
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.visitors.AbstractVisitor;
|
||||||
|
import jadx.core.dex.visitors.JadxVisitor;
|
||||||
|
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
|
||||||
|
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||||
|
import jadx.core.utils.ErrorsCounter;
|
||||||
|
import jadx.core.utils.exceptions.DecodeException;
|
||||||
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
|
@JadxVisitor(
|
||||||
|
name = "Debug Info Parser",
|
||||||
|
desc = "Parse debug information (variable names and types, instruction lines)",
|
||||||
|
runBefore = {
|
||||||
|
BlockSplitter.class,
|
||||||
|
SSATransform.class
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public class DebugInfoParseVisitor extends AbstractVisitor {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(DebugInfoParseVisitor.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(MethodNode mth) throws JadxException {
|
||||||
|
try {
|
||||||
|
int debugOffset = mth.getDebugInfoOffset();
|
||||||
|
if (debugOffset > 0 && mth.dex().checkOffset(debugOffset)) {
|
||||||
|
processDebugInfo(mth, debugOffset);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Error to parse debug info: {}", ErrorsCounter.formatMsg(mth, e.getMessage()), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processDebugInfo(MethodNode mth, int debugOffset) throws DecodeException {
|
||||||
|
InsnNode[] insnArr = mth.getInstructions();
|
||||||
|
DebugInfoParser debugInfoParser = new DebugInfoParser(mth, debugOffset, insnArr);
|
||||||
|
List<LocalVar> localVars = debugInfoParser.process();
|
||||||
|
attachDebugInfo(mth, localVars, insnArr);
|
||||||
|
setMethodSourceLine(mth, insnArr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attachDebugInfo(MethodNode mth, List<LocalVar> localVars, InsnNode[] insnArr) {
|
||||||
|
if (localVars.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Consts.DEBUG && LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Parsed debug info for {}: ", mth);
|
||||||
|
localVars.forEach(v -> LOG.debug(" {}", v));
|
||||||
|
}
|
||||||
|
localVars.forEach(var -> {
|
||||||
|
int start = var.getStartAddr();
|
||||||
|
int end = var.getEndAddr();
|
||||||
|
RegDebugInfoAttr debugInfoAttr = new RegDebugInfoAttr(var);
|
||||||
|
if (start < 0) {
|
||||||
|
// attach to method arguments
|
||||||
|
for (RegisterArg arg : mth.getArguments(true)) {
|
||||||
|
attachDebugInfo(arg, var, debugInfoAttr);
|
||||||
|
}
|
||||||
|
start = 0;
|
||||||
|
}
|
||||||
|
for (int i = start; i <= end; i++) {
|
||||||
|
InsnNode insn = insnArr[i];
|
||||||
|
if (insn != null) {
|
||||||
|
attachDebugInfo(insn.getResult(), var, debugInfoAttr);
|
||||||
|
for (InsnArg arg : insn.getArguments()) {
|
||||||
|
attachDebugInfo(arg, var, debugInfoAttr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mth.addAttr(new LocalVarsDebugInfoAttr(localVars));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attachDebugInfo(InsnArg arg, LocalVar var, RegDebugInfoAttr debugInfoAttr) {
|
||||||
|
if (arg instanceof RegisterArg) {
|
||||||
|
RegisterArg reg = (RegisterArg) arg;
|
||||||
|
if (var.getRegNum() == reg.getRegNum()) {
|
||||||
|
reg.addAttr(debugInfoAttr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set method source line from first instruction
|
||||||
|
*/
|
||||||
|
private void setMethodSourceLine(MethodNode mth, InsnNode[] insnArr) {
|
||||||
|
for (InsnNode insn : insnArr) {
|
||||||
|
if (insn != null) {
|
||||||
|
int line = insn.getSourceLine();
|
||||||
|
if (line != 0) {
|
||||||
|
mth.setSourceLine(line - 1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,21 @@
|
|||||||
package jadx.core.dex.nodes.parser;
|
package jadx.core.dex.visitors.debuginfo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.android.dex.Dex.Section;
|
import com.android.dex.Dex.Section;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.core.deobf.NameMapper;
|
|
||||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
import jadx.core.dex.instructions.args.SSAVar;
|
|
||||||
import jadx.core.dex.nodes.DexNode;
|
import jadx.core.dex.nodes.DexNode;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.utils.exceptions.DecodeException;
|
import jadx.core.utils.exceptions.DecodeException;
|
||||||
|
|
||||||
public class DebugInfoParser {
|
public class DebugInfoParser {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(DebugInfoParser.class);
|
||||||
private static final int DBG_END_SEQUENCE = 0x00;
|
private static final int DBG_END_SEQUENCE = 0x00;
|
||||||
private static final int DBG_ADVANCE_PC = 0x01;
|
private static final int DBG_ADVANCE_PC = 0x01;
|
||||||
private static final int DBG_ADVANCE_LINE = 0x02;
|
private static final int DBG_ADVANCE_LINE = 0x02;
|
||||||
@ -39,9 +39,10 @@ public class DebugInfoParser {
|
|||||||
private final DexNode dex;
|
private final DexNode dex;
|
||||||
|
|
||||||
private final LocalVar[] locals;
|
private final LocalVar[] locals;
|
||||||
private final InsnArg[] activeRegisters;
|
|
||||||
private final InsnNode[] insnByOffset;
|
private final InsnNode[] insnByOffset;
|
||||||
|
|
||||||
|
private List<LocalVar> resultList;
|
||||||
|
|
||||||
public DebugInfoParser(MethodNode mth, int debugOffset, InsnNode[] insnByOffset) {
|
public DebugInfoParser(MethodNode mth, int debugOffset, InsnNode[] insnByOffset) {
|
||||||
this.mth = mth;
|
this.mth = mth;
|
||||||
this.dex = mth.dex();
|
this.dex = mth.dex();
|
||||||
@ -49,38 +50,37 @@ public class DebugInfoParser {
|
|||||||
|
|
||||||
int regsCount = mth.getRegsCount();
|
int regsCount = mth.getRegsCount();
|
||||||
this.locals = new LocalVar[regsCount];
|
this.locals = new LocalVar[regsCount];
|
||||||
this.activeRegisters = new InsnArg[regsCount];
|
|
||||||
this.insnByOffset = insnByOffset;
|
this.insnByOffset = insnByOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void process() throws DecodeException {
|
public List<LocalVar> process() throws DecodeException {
|
||||||
|
boolean varsInfoFound = false;
|
||||||
|
resultList = new ArrayList<>();
|
||||||
|
|
||||||
int addr = 0;
|
int addr = 0;
|
||||||
int line = section.readUleb128();
|
int line = section.readUleb128();
|
||||||
|
|
||||||
int paramsCount = section.readUleb128();
|
int paramsCount = section.readUleb128();
|
||||||
List<RegisterArg> mthArgs = mth.getArguments(false);
|
List<RegisterArg> mthArgs = mth.getArguments(false);
|
||||||
|
|
||||||
for (int i = 0; i < paramsCount; i++) {
|
for (int i = 0; i < paramsCount; i++) {
|
||||||
int id = section.readUleb128() - 1;
|
int nameId = section.readUleb128() - 1;
|
||||||
if (id != DexNode.NO_INDEX) {
|
if (nameId != DexNode.NO_INDEX) {
|
||||||
String name = dex.getString(id);
|
String name = dex.getString(nameId);
|
||||||
if (i < mthArgs.size()) {
|
if (i < mthArgs.size() && name != null) {
|
||||||
mthArgs.get(i).setName(name);
|
RegisterArg arg = mthArgs.get(i);
|
||||||
|
int regNum = arg.getRegNum();
|
||||||
|
LocalVar lVar = new LocalVar(regNum, name, arg.getInitType());
|
||||||
|
startVar(lVar, -1);
|
||||||
|
varsInfoFound = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (RegisterArg arg : mthArgs) {
|
|
||||||
int rn = arg.getRegNum();
|
|
||||||
locals[rn] = new LocalVar(arg);
|
|
||||||
activeRegisters[rn] = arg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// process '0' instruction
|
// process '0' instruction
|
||||||
addrChange(-1, 1, line);
|
addrChange(-1, 1, line);
|
||||||
setLine(addr, line);
|
setLine(addr, line);
|
||||||
|
|
||||||
boolean varsInfoFound = false;
|
|
||||||
|
|
||||||
int c = section.readByte() & 0xFF;
|
int c = section.readByte() & 0xFF;
|
||||||
while (c != DBG_END_SEQUENCE) {
|
while (c != DBG_END_SEQUENCE) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
@ -100,7 +100,7 @@ public class DebugInfoParser {
|
|||||||
int nameId = section.readUleb128() - 1;
|
int nameId = section.readUleb128() - 1;
|
||||||
int type = section.readUleb128() - 1;
|
int type = section.readUleb128() - 1;
|
||||||
LocalVar var = new LocalVar(dex, regNum, nameId, type, DexNode.NO_INDEX);
|
LocalVar var = new LocalVar(dex, regNum, nameId, type, DexNode.NO_INDEX);
|
||||||
startVar(var, addr, line);
|
startVar(var, addr);
|
||||||
varsInfoFound = true;
|
varsInfoFound = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -110,19 +110,13 @@ public class DebugInfoParser {
|
|||||||
int type = section.readUleb128() - 1;
|
int type = section.readUleb128() - 1;
|
||||||
int sign = section.readUleb128() - 1;
|
int sign = section.readUleb128() - 1;
|
||||||
LocalVar var = new LocalVar(dex, regNum, nameId, type, sign);
|
LocalVar var = new LocalVar(dex, regNum, nameId, type, sign);
|
||||||
startVar(var, addr, line);
|
startVar(var, addr);
|
||||||
varsInfoFound = true;
|
varsInfoFound = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DBG_RESTART_LOCAL: {
|
case DBG_RESTART_LOCAL: {
|
||||||
int regNum = section.readUleb128();
|
int regNum = section.readUleb128();
|
||||||
LocalVar var = locals[regNum];
|
restartVar(regNum, addr);
|
||||||
if (var != null) {
|
|
||||||
if (var.end(addr, line)) {
|
|
||||||
setVar(var);
|
|
||||||
}
|
|
||||||
var.start(addr, line);
|
|
||||||
}
|
|
||||||
varsInfoFound = true;
|
varsInfoFound = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -130,8 +124,7 @@ public class DebugInfoParser {
|
|||||||
int regNum = section.readUleb128();
|
int regNum = section.readUleb128();
|
||||||
LocalVar var = locals[regNum];
|
LocalVar var = locals[regNum];
|
||||||
if (var != null) {
|
if (var != null) {
|
||||||
var.end(addr, line);
|
endVar(var, addr);
|
||||||
setVar(var);
|
|
||||||
}
|
}
|
||||||
varsInfoFound = true;
|
varsInfoFound = true;
|
||||||
break;
|
break;
|
||||||
@ -153,10 +146,10 @@ public class DebugInfoParser {
|
|||||||
|
|
||||||
default: {
|
default: {
|
||||||
if (c >= DBG_FIRST_SPECIAL) {
|
if (c >= DBG_FIRST_SPECIAL) {
|
||||||
int adjustedOpcode = c - DBG_FIRST_SPECIAL;
|
int adjustedOpCode = c - DBG_FIRST_SPECIAL;
|
||||||
int addrInc = adjustedOpcode / DBG_LINE_RANGE;
|
int addrInc = adjustedOpCode / DBG_LINE_RANGE;
|
||||||
addr = addrChange(addr, addrInc, line);
|
addr = addrChange(addr, addrInc, line);
|
||||||
line += DBG_LINE_BASE + adjustedOpcode % DBG_LINE_RANGE;
|
line += DBG_LINE_BASE + adjustedOpCode % DBG_LINE_RANGE;
|
||||||
setLine(addr, line);
|
setLine(addr, line);
|
||||||
} else {
|
} else {
|
||||||
throw new DecodeException("Unknown debug insn code: " + c);
|
throw new DecodeException("Unknown debug insn code: " + c);
|
||||||
@ -170,33 +163,19 @@ public class DebugInfoParser {
|
|||||||
if (varsInfoFound) {
|
if (varsInfoFound) {
|
||||||
for (LocalVar var : locals) {
|
for (LocalVar var : locals) {
|
||||||
if (var != null && !var.isEnd()) {
|
if (var != null && !var.isEnd()) {
|
||||||
var.end(mth.getCodeSize() - 1, line);
|
endVar(var, mth.getCodeSize() - 1);
|
||||||
setVar(var);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setSourceLines(addr, insnByOffset.length, line);
|
setSourceLines(addr, insnByOffset.length, line);
|
||||||
|
|
||||||
|
return resultList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int addrChange(int addr, int addrInc, int line) {
|
private int addrChange(int addr, int addrInc, int line) {
|
||||||
int newAddr = addr + addrInc;
|
int newAddr = addr + addrInc;
|
||||||
int maxAddr = insnByOffset.length - 1;
|
int maxAddr = insnByOffset.length - 1;
|
||||||
newAddr = Math.min(newAddr, maxAddr);
|
newAddr = Math.min(newAddr, maxAddr);
|
||||||
for (int i = addr + 1; i <= newAddr; i++) {
|
|
||||||
InsnNode insn = insnByOffset[i];
|
|
||||||
if (insn == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (InsnArg arg : insn.getArguments()) {
|
|
||||||
if (arg.isRegister()) {
|
|
||||||
activeRegisters[((RegisterArg) arg).getRegNum()] = arg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RegisterArg res = insn.getResult();
|
|
||||||
if (res != null) {
|
|
||||||
activeRegisters[res.getRegNum()] = res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setSourceLines(addr, newAddr, line);
|
setSourceLines(addr, newAddr, line);
|
||||||
return newAddr;
|
return newAddr;
|
||||||
}
|
}
|
||||||
@ -214,81 +193,30 @@ public class DebugInfoParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startVar(LocalVar var, int addr, int line) {
|
private void restartVar(int regNum, int addr) {
|
||||||
int regNum = var.getRegNum();
|
|
||||||
LocalVar prev = locals[regNum];
|
LocalVar prev = locals[regNum];
|
||||||
if (prev != null && !prev.isEnd()) {
|
if (prev != null) {
|
||||||
prev.end(addr, line);
|
endVar(prev, addr);
|
||||||
setVar(prev);
|
LocalVar newVar = new LocalVar(regNum, prev.getName(), prev.getType());
|
||||||
}
|
startVar(newVar, addr);
|
||||||
InsnArg activeReg = activeRegisters[var.getRegNum()];
|
|
||||||
if (activeReg instanceof RegisterArg) {
|
|
||||||
SSAVar ssaVar = ((RegisterArg) activeReg).getSVar();
|
|
||||||
if (ssaVar != null && ssaVar.getStartAddr() != -1) {
|
|
||||||
InsnNode parentInsn = ssaVar.getAssign().getParentInsn();
|
|
||||||
if (parentInsn != null && parentInsn.getOffset() >= 0) {
|
|
||||||
addr = parentInsn.getOffset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var.start(addr, line);
|
|
||||||
locals[regNum] = var;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setVar(LocalVar var) {
|
|
||||||
int start = var.getStartAddr();
|
|
||||||
int end = var.getEndAddr();
|
|
||||||
|
|
||||||
for (int i = start; i <= end; i++) {
|
|
||||||
InsnNode insn = insnByOffset[i];
|
|
||||||
if (insn != null) {
|
|
||||||
fillLocals(insn, var);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
merge(activeRegisters[var.getRegNum()], var);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void fillLocals(InsnNode insn, LocalVar var) {
|
|
||||||
merge(insn.getResult(), var);
|
|
||||||
for (InsnArg arg : insn.getArguments()) {
|
|
||||||
merge(arg, var);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void merge(InsnArg arg, LocalVar var) {
|
|
||||||
if (arg == null || !arg.isRegister()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
RegisterArg reg = (RegisterArg) arg;
|
|
||||||
if (var.getRegNum() != reg.getRegNum()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
boolean mergeRequired = false;
|
|
||||||
|
|
||||||
SSAVar ssaVar = reg.getSVar();
|
|
||||||
if (ssaVar != null) {
|
|
||||||
int ssaEnd = ssaVar.getEndAddr();
|
|
||||||
int ssaStart = ssaVar.getStartAddr();
|
|
||||||
int localStart = var.getStartAddr();
|
|
||||||
int localEnd = var.getEndAddr();
|
|
||||||
|
|
||||||
boolean isIntersected = !(localEnd < ssaStart || ssaEnd < localStart);
|
|
||||||
if (isIntersected && ssaEnd <= localEnd) {
|
|
||||||
mergeRequired = true;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
mergeRequired = true;
|
mth.addComment("Debug info: failed to restart local var, previous not found, register: " + regNum);
|
||||||
}
|
|
||||||
|
|
||||||
if (mergeRequired) {
|
|
||||||
applyDebugInfo(reg, var);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void applyDebugInfo(RegisterArg reg, LocalVar var) {
|
private void startVar(LocalVar newVar, int addr) {
|
||||||
String varName = var.getName();
|
int regNum = newVar.getRegNum();
|
||||||
if (NameMapper.isValidIdentifier(varName)) {
|
LocalVar prev = locals[regNum];
|
||||||
reg.mergeDebugInfo(var.getType(), varName);
|
if (prev != null) {
|
||||||
|
endVar(prev, addr);
|
||||||
|
}
|
||||||
|
newVar.start(addr);
|
||||||
|
locals[regNum] = newVar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void endVar(LocalVar var, int addr) {
|
||||||
|
if (var.end(addr)) {
|
||||||
|
resultList.add(var);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,54 +1,48 @@
|
|||||||
package jadx.core.dex.nodes.parser;
|
package jadx.core.dex.visitors.debuginfo;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
|
||||||
import jadx.core.dex.nodes.DexNode;
|
import jadx.core.dex.nodes.DexNode;
|
||||||
import jadx.core.utils.InsnUtils;
|
import jadx.core.utils.InsnUtils;
|
||||||
|
|
||||||
final class LocalVar {
|
public final class LocalVar {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(LocalVar.class);
|
private static final Logger LOG = LoggerFactory.getLogger(LocalVar.class);
|
||||||
|
|
||||||
private final int regNum;
|
private final int regNum;
|
||||||
private String name;
|
private final String name;
|
||||||
private ArgType type;
|
private final ArgType type;
|
||||||
|
|
||||||
private boolean isEnd;
|
private boolean isEnd;
|
||||||
private int startAddr;
|
private int startAddr;
|
||||||
private int endAddr;
|
private int endAddr;
|
||||||
|
|
||||||
public LocalVar(DexNode dex, int rn, int nameId, int typeId, int signId) {
|
public LocalVar(DexNode dex, int rn, int nameId, int typeId, int signId) {
|
||||||
this.regNum = rn;
|
this(rn, dex.getString(nameId), dex.getType(typeId), dex.getString(signId));
|
||||||
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);
|
|
||||||
|
|
||||||
init(name, type, sign);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocalVar(RegisterArg arg) {
|
public LocalVar(int regNum, String name, ArgType type) {
|
||||||
this.regNum = arg.getRegNum();
|
this(regNum, name, type, null);
|
||||||
init(arg.getName(), arg.getType(), null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init(String name, ArgType type, String sign) {
|
public LocalVar(int regNum, String name, ArgType type, String sign) {
|
||||||
|
this.regNum = regNum;
|
||||||
|
this.name = name;
|
||||||
if (sign != null) {
|
if (sign != null) {
|
||||||
try {
|
try {
|
||||||
ArgType gType = ArgType.generic(sign);
|
ArgType gType = ArgType.generic(sign);
|
||||||
if (checkSignature(type, sign, gType)) {
|
if (checkSignature(type, gType)) {
|
||||||
type = gType;
|
type = gType;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Can't parse signature for local variable: {}", sign, e);
|
LOG.error("Can't parse signature for local variable: {}", sign, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.name = name;
|
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkSignature(ArgType type, String sign, ArgType gType) {
|
private boolean checkSignature(ArgType type, ArgType gType) {
|
||||||
boolean apply;
|
boolean apply;
|
||||||
ArgType el = gType.getArrayRootElement();
|
ArgType el = gType.getArrayRootElement();
|
||||||
if (el.isGeneric()) {
|
if (el.isGeneric()) {
|
||||||
@ -62,7 +56,7 @@ final class LocalVar {
|
|||||||
return apply;
|
return apply;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start(int addr, int line) {
|
public void start(int addr) {
|
||||||
this.isEnd = false;
|
this.isEnd = false;
|
||||||
this.startAddr = addr;
|
this.startAddr = addr;
|
||||||
}
|
}
|
||||||
@ -71,16 +65,15 @@ final class LocalVar {
|
|||||||
* Sets end address of local variable
|
* Sets end address of local variable
|
||||||
*
|
*
|
||||||
* @param addr address
|
* @param addr address
|
||||||
* @param line source line
|
|
||||||
* @return <b>true</b> if local variable was active, else <b>false</b>
|
* @return <b>true</b> if local variable was active, else <b>false</b>
|
||||||
*/
|
*/
|
||||||
public boolean end(int addr, int line) {
|
public boolean end(int addr) {
|
||||||
if (!isEnd) {
|
if (isEnd) {
|
||||||
this.isEnd = true;
|
return false;
|
||||||
this.endAddr = addr;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
this.isEnd = true;
|
||||||
|
this.endAddr = addr;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRegNum() {
|
public int getRegNum() {
|
||||||
@ -119,8 +112,8 @@ final class LocalVar {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return super.toString() + " " + (isEnd
|
return InsnUtils.formatOffset(startAddr)
|
||||||
? "end: " + InsnUtils.formatOffset(startAddr) + "-" + InsnUtils.formatOffset(endAddr)
|
+ "-" + (isEnd ? InsnUtils.formatOffset(endAddr) : " ")
|
||||||
: "active: " + InsnUtils.formatOffset(startAddr));
|
+ ": r" + regNum + " '" + name + "' " + type;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -60,16 +60,19 @@ public class DepthRegionTraversal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean traverseIterativeStepInternal(MethodNode mth, IRegionIterativeVisitor visitor,
|
private static boolean traverseIterativeStepInternal(MethodNode mth, IRegionIterativeVisitor visitor, IContainer container) {
|
||||||
IContainer container) {
|
|
||||||
if (container instanceof IRegion) {
|
if (container instanceof IRegion) {
|
||||||
IRegion region = (IRegion) container;
|
IRegion region = (IRegion) container;
|
||||||
if (visitor.visitRegion(mth, region)) {
|
if (visitor.visitRegion(mth, region)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
for (IContainer subCont : region.getSubBlocks()) {
|
for (IContainer subCont : region.getSubBlocks()) {
|
||||||
if (traverseIterativeStepInternal(mth, visitor, subCont)) {
|
try {
|
||||||
return true;
|
if (traverseIterativeStepInternal(mth, visitor, subCont)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (StackOverflowError overflow) {
|
||||||
|
throw new JadxOverflowException("Region traversal failed: Recursive call in traverseIterativeStepInternal method");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
|||||||
PhiInsn phiInsn = incrArg.getSVar().getUsedInPhi();
|
PhiInsn phiInsn = incrArg.getSVar().getUsedInPhi();
|
||||||
if (phiInsn == null
|
if (phiInsn == null
|
||||||
|| phiInsn.getArgsCount() != 2
|
|| phiInsn.getArgsCount() != 2
|
||||||
|| !phiInsn.getArg(1).equals(incrArg)
|
|| !phiInsn.containsArg(incrArg)
|
||||||
|| incrArg.getSVar().getUseCount() != 1) {
|
|| incrArg.getSVar().getUseCount() != 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -289,13 +289,13 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
|||||||
iterVar.setType(gType);
|
iterVar.setType(gType);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (ArgType.isInstanceOf(mth.dex(), gType, varType)) {
|
if (ArgType.isInstanceOf(mth.root(), gType, varType)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
ArgType wildcardType = gType.getWildcardType();
|
ArgType wildcardType = gType.getWildcardType();
|
||||||
if (wildcardType != null
|
if (wildcardType != null
|
||||||
&& gType.getWildcardBounds() == 1
|
&& gType.getWildcardBounds() == 1
|
||||||
&& ArgType.isInstanceOf(mth.dex(), wildcardType, varType)) {
|
&& ArgType.isInstanceOf(mth.root(), wildcardType, varType)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
LOG.warn("Generic type differs: '{}' and '{}' in {}", gType, varType, mth);
|
LOG.warn("Generic type differs: '{}' and '{}' in {}", gType, varType, mth);
|
||||||
|
@ -818,7 +818,10 @@ public class RegionMaker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!stack.containsExit(defCase)) {
|
if (!stack.containsExit(defCase)) {
|
||||||
sw.setDefaultCase(makeRegion(defCase, stack));
|
Region defRegion = makeRegion(defCase, stack);
|
||||||
|
if (RegionUtils.notEmpty(defRegion)) {
|
||||||
|
sw.setDefaultCase(defRegion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (Entry<BlockNode, List<Object>> entry : blocksMap.entrySet()) {
|
for (Entry<BlockNode, List<Object>> entry : blocksMap.entrySet()) {
|
||||||
BlockNode caseBlock = entry.getKey();
|
BlockNode caseBlock = entry.getKey();
|
||||||
|
@ -40,18 +40,18 @@ public class TernaryMod {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
BlockNode header = ifRegion.getHeader();
|
BlockNode header = ifRegion.getHeader();
|
||||||
InsnNode t = tb.getInstructions().get(0);
|
InsnNode thenInsn = tb.getInstructions().get(0);
|
||||||
InsnNode e = eb.getInstructions().get(0);
|
InsnNode elseInsn = eb.getInstructions().get(0);
|
||||||
|
|
||||||
if (t.getSourceLine() != e.getSourceLine()) {
|
if (thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
|
||||||
if (t.getSourceLine() != 0 && e.getSourceLine() != 0) {
|
if (thenInsn.getSourceLine() != 0 && elseInsn.getSourceLine() != 0) {
|
||||||
// sometimes source lines incorrect
|
// sometimes source lines incorrect
|
||||||
if (!checkLineStats(t, e)) {
|
if (!checkLineStats(thenInsn, elseInsn)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// no debug info
|
// no debug info
|
||||||
if (containsTernary(t) || containsTernary(e)) {
|
if (containsTernary(thenInsn) || containsTernary(elseInsn)) {
|
||||||
// don't make nested ternary by default
|
// don't make nested ternary by default
|
||||||
// TODO: add addition checks
|
// TODO: add addition checks
|
||||||
return false;
|
return false;
|
||||||
@ -59,27 +59,30 @@ public class TernaryMod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.getResult() != null && e.getResult() != null) {
|
RegisterArg thenResArg = thenInsn.getResult();
|
||||||
PhiInsn phi = t.getResult().getSVar().getUsedInPhi();
|
RegisterArg elseResArg = elseInsn.getResult();
|
||||||
if (phi == null || !t.getResult().equalRegisterAndType(e.getResult())) {
|
if (thenResArg != null && elseResArg != null) {
|
||||||
|
PhiInsn thenPhi = thenResArg.getSVar().getUsedInPhi();
|
||||||
|
PhiInsn elsePhi = elseResArg.getSVar().getUsedInPhi();
|
||||||
|
if (thenPhi == null || thenPhi != elsePhi) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ifRegion.getParent().replaceSubBlock(ifRegion, header)) {
|
if (!ifRegion.getParent().replaceSubBlock(ifRegion, header)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
InsnList.remove(tb, t);
|
InsnList.remove(tb, thenInsn);
|
||||||
InsnList.remove(eb, e);
|
InsnList.remove(eb, elseInsn);
|
||||||
|
|
||||||
RegisterArg resArg;
|
RegisterArg resArg;
|
||||||
if (phi.getArgsCount() == 2) {
|
if (thenPhi.getArgsCount() == 2) {
|
||||||
resArg = phi.getResult();
|
resArg = thenPhi.getResult();
|
||||||
} else {
|
} else {
|
||||||
resArg = t.getResult();
|
resArg = thenResArg;
|
||||||
phi.removeArg(e.getResult());
|
thenPhi.removeArg(elseResArg);
|
||||||
}
|
}
|
||||||
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(),
|
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(),
|
||||||
resArg, InsnArg.wrapArg(t), InsnArg.wrapArg(e));
|
resArg, InsnArg.wrapArg(thenInsn), InsnArg.wrapArg(elseInsn));
|
||||||
ternInsn.setSourceLine(t.getSourceLine());
|
ternInsn.setSourceLine(thenInsn.getSourceLine());
|
||||||
|
|
||||||
// remove 'if' instruction
|
// remove 'if' instruction
|
||||||
header.getInstructions().clear();
|
header.getInstructions().clear();
|
||||||
@ -91,18 +94,25 @@ public class TernaryMod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!mth.getReturnType().equals(ArgType.VOID)
|
if (!mth.getReturnType().equals(ArgType.VOID)
|
||||||
&& t.getType() == InsnType.RETURN && e.getType() == InsnType.RETURN) {
|
&& thenInsn.getType() == InsnType.RETURN
|
||||||
|
&& elseInsn.getType() == InsnType.RETURN) {
|
||||||
|
InsnArg thenArg = thenInsn.getArg(0);
|
||||||
|
InsnArg elseArg = elseInsn.getArg(0);
|
||||||
|
if (thenArg.isLiteral() != elseArg.isLiteral()) {
|
||||||
|
// one arg is literal
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!ifRegion.getParent().replaceSubBlock(ifRegion, header)) {
|
if (!ifRegion.getParent().replaceSubBlock(ifRegion, header)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
InsnList.remove(tb, t);
|
InsnList.remove(tb, thenInsn);
|
||||||
InsnList.remove(eb, e);
|
InsnList.remove(eb, elseInsn);
|
||||||
tb.remove(AFlag.RETURN);
|
tb.remove(AFlag.RETURN);
|
||||||
eb.remove(AFlag.RETURN);
|
eb.remove(AFlag.RETURN);
|
||||||
|
|
||||||
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(), null, t.getArg(0), e.getArg(0));
|
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(), null, thenArg, elseArg);
|
||||||
ternInsn.setSourceLine(t.getSourceLine());
|
ternInsn.setSourceLine(thenInsn.getSourceLine());
|
||||||
InsnNode retInsn = new InsnNode(InsnType.RETURN, 1);
|
InsnNode retInsn = new InsnNode(InsnType.RETURN, 1);
|
||||||
retInsn.addArg(InsnArg.wrapArg(ternInsn));
|
retInsn.addArg(InsnArg.wrapArg(ternInsn));
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package jadx.core.dex.visitors.ssa;
|
package jadx.core.dex.visitors.ssa;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -106,12 +107,12 @@ public class EliminatePhiNodes extends AbstractVisitor {
|
|||||||
RegisterArg newAssignArg = oldArg.duplicate(newRegNum, null);
|
RegisterArg newAssignArg = oldArg.duplicate(newRegNum, null);
|
||||||
SSAVar newSVar = mth.makeNewSVar(newRegNum, mth.getNextSVarVersion(newRegNum), newAssignArg);
|
SSAVar newSVar = mth.makeNewSVar(newRegNum, mth.getNextSVarVersion(newRegNum), newAssignArg);
|
||||||
newSVar.setName(oldSVar.getName());
|
newSVar.setName(oldSVar.getName());
|
||||||
newSVar.setType(assignArg.getType());
|
mth.root().getTypeUpdate().apply(newSVar, assignArg.getType());
|
||||||
|
|
||||||
if (assignParentInsn != null) {
|
if (assignParentInsn != null) {
|
||||||
assignParentInsn.setResult(newAssignArg);
|
assignParentInsn.setResult(newAssignArg);
|
||||||
}
|
}
|
||||||
for (RegisterArg useArg : oldSVar.getUseList()) {
|
for (RegisterArg useArg : new ArrayList<>(oldSVar.getUseList())) {
|
||||||
RegisterArg newUseArg = useArg.duplicate(newRegNum, newSVar);
|
RegisterArg newUseArg = useArg.duplicate(newRegNum, newSVar);
|
||||||
InsnNode parentInsn = useArg.getParentInsn();
|
InsnNode parentInsn = useArg.getParentInsn();
|
||||||
if (parentInsn != null) {
|
if (parentInsn != null) {
|
||||||
|
@ -220,7 +220,7 @@ public class SSATransform extends AbstractVisitor {
|
|||||||
if (parentInsn != null
|
if (parentInsn != null
|
||||||
&& parentInsn.getResult() != null
|
&& parentInsn.getResult() != null
|
||||||
&& parentInsn.contains(AFlag.TRY_LEAVE)
|
&& parentInsn.contains(AFlag.TRY_LEAVE)
|
||||||
&& phi.removeArg(arg)) {
|
&& phi.removeArg(arg) /* TODO: fix registers removing*/) {
|
||||||
argsCount--;
|
argsCount--;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
public enum BoundEnum {
|
||||||
|
ASSIGN,
|
||||||
|
USE
|
||||||
|
}
|
@ -1,28 +0,0 @@
|
|||||||
package jadx.core.dex.visitors.typeinference;
|
|
||||||
|
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
|
||||||
import jadx.core.utils.ErrorsCounter;
|
|
||||||
|
|
||||||
public class CheckTypeVisitor {
|
|
||||||
|
|
||||||
public static void visit(MethodNode mth, InsnNode insn) {
|
|
||||||
if (insn.getResult() != null
|
|
||||||
&& !insn.getResult().getType().isTypeKnown()) {
|
|
||||||
error("Wrong return type", mth, insn);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (InsnArg arg : insn.getArguments()) {
|
|
||||||
if (!arg.getType().isTypeKnown()) {
|
|
||||||
error("Wrong type", mth, insn);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void error(String msg, MethodNode mth, InsnNode insn) {
|
|
||||||
ErrorsCounter.methodWarn(mth, msg + ": " + insn);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
package jadx.core.dex.visitors.typeinference;
|
|
||||||
|
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
|
||||||
import jadx.core.dex.nodes.DexNode;
|
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
|
||||||
import jadx.core.dex.visitors.AbstractVisitor;
|
|
||||||
|
|
||||||
public class FinishTypeInference extends AbstractVisitor {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(MethodNode mth) {
|
|
||||||
if (mth.isNoCode()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean change;
|
|
||||||
int i = 0;
|
|
||||||
do {
|
|
||||||
change = false;
|
|
||||||
for (BlockNode block : mth.getBasicBlocks()) {
|
|
||||||
for (InsnNode insn : block.getInstructions()) {
|
|
||||||
if (PostTypeInference.process(mth, insn)) {
|
|
||||||
change = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
if (i > 1000) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (change);
|
|
||||||
|
|
||||||
// last chance to set correct value (just use first type from 'possible' list)
|
|
||||||
DexNode dex = mth.dex();
|
|
||||||
for (BlockNode block : mth.getBasicBlocks()) {
|
|
||||||
for (InsnNode insn : block.getInstructions()) {
|
|
||||||
SelectTypeVisitor.visit(dex, insn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check
|
|
||||||
for (BlockNode block : mth.getBasicBlocks()) {
|
|
||||||
for (InsnNode insn : block.getInstructions()) {
|
|
||||||
CheckTypeVisitor.visit(mth, insn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,9 @@
|
|||||||
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
|
||||||
|
public interface ITypeBound {
|
||||||
|
BoundEnum getBound();
|
||||||
|
|
||||||
|
ArgType getType();
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ITypeListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener function - triggered on type update
|
||||||
|
*
|
||||||
|
* @param updateInfo store all allowed type updates
|
||||||
|
* @param arg apply suggested type for this arg
|
||||||
|
* @param candidateType suggest new type
|
||||||
|
*/
|
||||||
|
TypeUpdateResult update(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType);
|
||||||
|
}
|
@ -1,152 +0,0 @@
|
|||||||
package jadx.core.dex.visitors.typeinference;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import jadx.core.dex.info.MethodInfo;
|
|
||||||
import jadx.core.dex.instructions.IndexInsnNode;
|
|
||||||
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.DexNode;
|
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
|
||||||
|
|
||||||
public class PostTypeInference {
|
|
||||||
|
|
||||||
private PostTypeInference() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean process(MethodNode mth, InsnNode insn) {
|
|
||||||
DexNode dex = mth.dex();
|
|
||||||
switch (insn.getType()) {
|
|
||||||
case CONST:
|
|
||||||
RegisterArg res = insn.getResult();
|
|
||||||
LiteralArg litArg = (LiteralArg) insn.getArg(0);
|
|
||||||
if (res.getType().isObject()) {
|
|
||||||
long lit = litArg.getLiteral();
|
|
||||||
if (lit != 0) {
|
|
||||||
// incorrect literal value for object
|
|
||||||
ArgType type = lit == 1 ? ArgType.BOOLEAN : ArgType.INT;
|
|
||||||
// can't merge with object -> force it
|
|
||||||
litArg.setType(type);
|
|
||||||
res.getSVar().setType(type);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return litArg.merge(dex, res);
|
|
||||||
|
|
||||||
case MOVE: {
|
|
||||||
boolean change = false;
|
|
||||||
if (insn.getResult().merge(dex, insn.getArg(0))) {
|
|
||||||
change = true;
|
|
||||||
}
|
|
||||||
if (insn.getArg(0).merge(dex, insn.getResult())) {
|
|
||||||
change = true;
|
|
||||||
}
|
|
||||||
return change;
|
|
||||||
}
|
|
||||||
|
|
||||||
case AGET:
|
|
||||||
return fixArrayTypes(dex, insn.getArg(0), insn.getResult());
|
|
||||||
|
|
||||||
case APUT:
|
|
||||||
return fixArrayTypes(dex, insn.getArg(0), insn.getArg(2));
|
|
||||||
|
|
||||||
case IF: {
|
|
||||||
boolean change = false;
|
|
||||||
if (insn.getArg(1).merge(dex, insn.getArg(0))) {
|
|
||||||
change = true;
|
|
||||||
}
|
|
||||||
if (insn.getArg(0).merge(dex, insn.getArg(1))) {
|
|
||||||
change = true;
|
|
||||||
}
|
|
||||||
return change;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check argument types for overloaded methods
|
|
||||||
case INVOKE: {
|
|
||||||
boolean change = false;
|
|
||||||
InvokeNode inv = (InvokeNode) insn;
|
|
||||||
MethodInfo callMth = inv.getCallMth();
|
|
||||||
MethodNode node = mth.dex().resolveMethod(callMth);
|
|
||||||
if (node != null && node.isArgsOverload()) {
|
|
||||||
List<ArgType> args = callMth.getArgumentsTypes();
|
|
||||||
int j = inv.getArgsCount() - 1;
|
|
||||||
for (int i = args.size() - 1; i >= 0; i--) {
|
|
||||||
ArgType argType = args.get(i);
|
|
||||||
InsnArg insnArg = inv.getArg(j--);
|
|
||||||
if (insnArg.isRegister() && !argType.equals(insnArg.getType())) {
|
|
||||||
insnArg.setType(argType);
|
|
||||||
change = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return change;
|
|
||||||
}
|
|
||||||
|
|
||||||
case CHECK_CAST: {
|
|
||||||
ArgType castType = (ArgType) ((IndexInsnNode) insn).getIndex();
|
|
||||||
RegisterArg result = insn.getResult();
|
|
||||||
ArgType resultType = result.getType();
|
|
||||||
// don't override generic types of same base class
|
|
||||||
boolean skip = castType.isObject() && resultType.isObject()
|
|
||||||
&& castType.getObject().equals(resultType.getObject());
|
|
||||||
if (!skip) {
|
|
||||||
// workaround for compiler bug (see TestDuplicateCast)
|
|
||||||
result.getSVar().setType(castType);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
case PHI:
|
|
||||||
case MERGE: {
|
|
||||||
ArgType type = insn.getResult().getType();
|
|
||||||
if (!type.isTypeKnown()) {
|
|
||||||
for (InsnArg arg : insn.getArguments()) {
|
|
||||||
if (arg.getType().isTypeKnown()) {
|
|
||||||
type = arg.getType();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
boolean changed = false;
|
|
||||||
if (updateType(insn.getResult(), type)) {
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < insn.getArgsCount(); i++) {
|
|
||||||
RegisterArg arg = (RegisterArg) insn.getArg(i);
|
|
||||||
if (updateType(arg, type)) {
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean updateType(RegisterArg arg, ArgType type) {
|
|
||||||
ArgType prevType = arg.getType();
|
|
||||||
if (prevType == null || !prevType.equals(type)) {
|
|
||||||
arg.setType(type);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean fixArrayTypes(DexNode dex, InsnArg array, InsnArg elem) {
|
|
||||||
boolean change = false;
|
|
||||||
if (!elem.getType().isTypeKnown() && elem.merge(dex, array.getType().getArrayElement())) {
|
|
||||||
change = true;
|
|
||||||
}
|
|
||||||
if (!array.getType().isTypeKnown() && array.merge(dex, ArgType.array(elem.getType()))) {
|
|
||||||
change = true;
|
|
||||||
}
|
|
||||||
return change;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package jadx.core.dex.visitors.typeinference;
|
|
||||||
|
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
|
||||||
import jadx.core.dex.nodes.DexNode;
|
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
|
||||||
|
|
||||||
public class SelectTypeVisitor {
|
|
||||||
|
|
||||||
private SelectTypeVisitor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void visit(DexNode dex, InsnNode insn) {
|
|
||||||
InsnArg res = insn.getResult();
|
|
||||||
if (res != null && !res.getType().isTypeKnown()) {
|
|
||||||
selectType(dex, res);
|
|
||||||
}
|
|
||||||
for (InsnArg arg : insn.getArguments()) {
|
|
||||||
if (!arg.getType().isTypeKnown()) {
|
|
||||||
selectType(dex, arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void selectType(DexNode dex, InsnArg arg) {
|
|
||||||
ArgType t = arg.getType();
|
|
||||||
ArgType newType = ArgType.merge(dex, t, t.selectFirst());
|
|
||||||
arg.setType(newType);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,48 @@
|
|||||||
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
|
||||||
|
public final class TypeBoundConst implements ITypeBound {
|
||||||
|
private final BoundEnum bound;
|
||||||
|
private final ArgType type;
|
||||||
|
|
||||||
|
public TypeBoundConst(BoundEnum bound, ArgType type) {
|
||||||
|
this.bound = bound;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BoundEnum getBound() {
|
||||||
|
return bound;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArgType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
TypeBoundConst that = (TypeBoundConst) o;
|
||||||
|
return bound == that.bound &&
|
||||||
|
Objects.equals(type, that.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(bound, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "{" + bound + ": " + type + '}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,221 @@
|
|||||||
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.CONFLICT;
|
||||||
|
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.NARROW;
|
||||||
|
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.NARROW_BY_GENERIC;
|
||||||
|
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.UNKNOWN;
|
||||||
|
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.WIDER;
|
||||||
|
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.WIDER_BY_GENERIC;
|
||||||
|
|
||||||
|
public class TypeCompare {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(TypeCompare.class);
|
||||||
|
|
||||||
|
private final RootNode root;
|
||||||
|
private final ArgTypeComparator comparator;
|
||||||
|
|
||||||
|
public TypeCompare(RootNode root) {
|
||||||
|
this.root = root;
|
||||||
|
this.comparator = new ArgTypeComparator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two type and return result for first argument (narrow, wider or conflict)
|
||||||
|
*/
|
||||||
|
public TypeCompareEnum compareTypes(ArgType first, ArgType second) {
|
||||||
|
if (first == second || Objects.equals(first, second)) {
|
||||||
|
return TypeCompareEnum.EQUAL;
|
||||||
|
}
|
||||||
|
boolean firstKnown = first.isTypeKnown();
|
||||||
|
boolean secondKnown = second.isTypeKnown();
|
||||||
|
if (firstKnown != secondKnown) {
|
||||||
|
if (firstKnown) {
|
||||||
|
return compareWithUnknown(first, second);
|
||||||
|
} else {
|
||||||
|
return compareWithUnknown(second, first).invert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean firstArray = first.isArray();
|
||||||
|
boolean secondArray = second.isArray();
|
||||||
|
if (firstArray != secondArray) {
|
||||||
|
if (firstArray) {
|
||||||
|
return compareArrayWithOtherType(first, second);
|
||||||
|
} else {
|
||||||
|
return compareArrayWithOtherType(second, first).invert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (firstArray /* && secondArray */) {
|
||||||
|
// both arrays
|
||||||
|
return compareTypes(first.getArrayElement(), second.getArrayElement());
|
||||||
|
}
|
||||||
|
if (!firstKnown /*&& !secondKnown*/) {
|
||||||
|
int variantLen = Integer.compare(first.getPossibleTypes().length, second.getPossibleTypes().length);
|
||||||
|
return variantLen > 0 ? WIDER : NARROW;
|
||||||
|
}
|
||||||
|
boolean firstPrimitive = first.isPrimitive();
|
||||||
|
boolean secondPrimitive = second.isPrimitive();
|
||||||
|
|
||||||
|
boolean firstObj = first.isObject();
|
||||||
|
boolean secondObj = second.isObject();
|
||||||
|
if (firstObj && secondObj) {
|
||||||
|
return compareObjects(first, second);
|
||||||
|
} else {
|
||||||
|
// primitive types conflicts with objects
|
||||||
|
if (firstObj && secondPrimitive) {
|
||||||
|
return CONFLICT;
|
||||||
|
}
|
||||||
|
if (firstPrimitive && secondObj) {
|
||||||
|
return CONFLICT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (firstPrimitive && secondPrimitive) {
|
||||||
|
int comparePrimitives = first.getPrimitiveType().compareTo(second.getPrimitiveType());
|
||||||
|
return comparePrimitives > 0 ? WIDER : NARROW;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.warn("Type compare function not complete, can't compare {} and {}", first, second);
|
||||||
|
return TypeCompareEnum.CONFLICT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeCompareEnum compareArrayWithOtherType(ArgType array, ArgType other) {
|
||||||
|
if (!other.isTypeKnown()) {
|
||||||
|
if (other.contains(PrimitiveType.ARRAY)) {
|
||||||
|
return NARROW;
|
||||||
|
}
|
||||||
|
return CONFLICT;
|
||||||
|
}
|
||||||
|
if (other.isObject()) {
|
||||||
|
if (other.equals(ArgType.OBJECT)) {
|
||||||
|
return NARROW;
|
||||||
|
}
|
||||||
|
return CONFLICT;
|
||||||
|
}
|
||||||
|
if (other.isPrimitive()) {
|
||||||
|
return CONFLICT;
|
||||||
|
}
|
||||||
|
throw new JadxRuntimeException("Unprocessed type: " + other + " in array compare");
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeCompareEnum compareWithUnknown(ArgType known, ArgType unknown) {
|
||||||
|
if (unknown == ArgType.UNKNOWN) {
|
||||||
|
return NARROW;
|
||||||
|
}
|
||||||
|
if (unknown == ArgType.UNKNOWN_OBJECT && (known.isObject() || known.isArray())) {
|
||||||
|
return NARROW;
|
||||||
|
}
|
||||||
|
PrimitiveType knownPrimitive;
|
||||||
|
if (known.isPrimitive()) {
|
||||||
|
knownPrimitive = known.getPrimitiveType();
|
||||||
|
} else if (known.isArray()) {
|
||||||
|
knownPrimitive = PrimitiveType.ARRAY;
|
||||||
|
} else {
|
||||||
|
knownPrimitive = PrimitiveType.OBJECT;
|
||||||
|
}
|
||||||
|
PrimitiveType[] possibleTypes = unknown.getPossibleTypes();
|
||||||
|
for (PrimitiveType possibleType : possibleTypes) {
|
||||||
|
if (possibleType == knownPrimitive) {
|
||||||
|
return NARROW;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CONFLICT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeCompareEnum compareObjects(ArgType first, ArgType second) {
|
||||||
|
boolean objectsEquals = first.getObject().equals(second.getObject());
|
||||||
|
boolean firstGenericType = first.isGenericType();
|
||||||
|
boolean secondGenericType = second.isGenericType();
|
||||||
|
if (firstGenericType && secondGenericType && !objectsEquals) {
|
||||||
|
return CONFLICT;
|
||||||
|
}
|
||||||
|
if (firstGenericType || secondGenericType) {
|
||||||
|
if (firstGenericType) {
|
||||||
|
return compareGenericTypeWithObject(first, second);
|
||||||
|
} else {
|
||||||
|
return compareGenericTypeWithObject(second, first).invert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean firstGeneric = first.isGeneric();
|
||||||
|
boolean secondGeneric = second.isGeneric();
|
||||||
|
if (firstGeneric != secondGeneric && objectsEquals) {
|
||||||
|
// don't check generics for now
|
||||||
|
return firstGeneric ? NARROW_BY_GENERIC : WIDER_BY_GENERIC;
|
||||||
|
}
|
||||||
|
boolean firstIsObjCls = first.equals(ArgType.OBJECT);
|
||||||
|
if (firstIsObjCls || second.equals(ArgType.OBJECT)) {
|
||||||
|
return firstIsObjCls ? WIDER : NARROW;
|
||||||
|
}
|
||||||
|
if (ArgType.isInstanceOf(root, first, second)) {
|
||||||
|
return NARROW;
|
||||||
|
}
|
||||||
|
if (ArgType.isInstanceOf(root, second, first)) {
|
||||||
|
return WIDER;
|
||||||
|
}
|
||||||
|
if (!ArgType.isClsKnown(root, first) || !ArgType.isClsKnown(root, second)) {
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
return TypeCompareEnum.CONFLICT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeCompareEnum compareGenericTypeWithObject(ArgType genericType, ArgType objType) {
|
||||||
|
List<ArgType> extendTypes = genericType.getExtendTypes();
|
||||||
|
if (extendTypes == null || extendTypes.isEmpty()) {
|
||||||
|
if (objType.equals(ArgType.OBJECT)) {
|
||||||
|
return NARROW_BY_GENERIC;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (extendTypes.contains(objType) || objType.equals(ArgType.OBJECT)) {
|
||||||
|
return NARROW_BY_GENERIC;
|
||||||
|
}
|
||||||
|
for (ArgType extendType : extendTypes) {
|
||||||
|
if (!ArgType.isInstanceOf(root, extendType, objType)) {
|
||||||
|
return CONFLICT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NARROW_BY_GENERIC;
|
||||||
|
}
|
||||||
|
// TODO: fill extendTypes
|
||||||
|
// return CONFLICT;
|
||||||
|
return NARROW_BY_GENERIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgTypeComparator getComparator() {
|
||||||
|
return comparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private final class ArgTypeComparator implements Comparator<ArgType> {
|
||||||
|
@Override
|
||||||
|
public int compare(ArgType a, ArgType b) {
|
||||||
|
TypeCompareEnum result = compareTypes(a, b);
|
||||||
|
switch (result) {
|
||||||
|
case CONFLICT:
|
||||||
|
return -2;
|
||||||
|
|
||||||
|
case WIDER:
|
||||||
|
case WIDER_BY_GENERIC:
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
case NARROW:
|
||||||
|
case NARROW_BY_GENERIC:
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
case EQUAL:
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
public enum TypeCompareEnum {
|
||||||
|
EQUAL,
|
||||||
|
NARROW,
|
||||||
|
NARROW_BY_GENERIC, // same basic type with generic
|
||||||
|
WIDER,
|
||||||
|
WIDER_BY_GENERIC, // same basic type without generic
|
||||||
|
CONFLICT,
|
||||||
|
UNKNOWN;
|
||||||
|
|
||||||
|
public TypeCompareEnum invert() {
|
||||||
|
switch (this) {
|
||||||
|
case NARROW:
|
||||||
|
return WIDER;
|
||||||
|
|
||||||
|
case NARROW_BY_GENERIC:
|
||||||
|
return WIDER_BY_GENERIC;
|
||||||
|
|
||||||
|
case WIDER:
|
||||||
|
return NARROW;
|
||||||
|
|
||||||
|
case WIDER_BY_GENERIC:
|
||||||
|
return NARROW_BY_GENERIC;
|
||||||
|
|
||||||
|
case CONFLICT:
|
||||||
|
case EQUAL:
|
||||||
|
case UNKNOWN:
|
||||||
|
default:
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,130 +0,0 @@
|
|||||||
package jadx.core.dex.visitors.typeinference;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import jadx.core.dex.attributes.AType;
|
|
||||||
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
|
||||||
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.instructions.args.VarName;
|
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
|
||||||
import jadx.core.dex.nodes.DexNode;
|
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
|
||||||
import jadx.core.dex.visitors.AbstractVisitor;
|
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
|
||||||
|
|
||||||
public class TypeInference extends AbstractVisitor {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(MethodNode mth) throws JadxException {
|
|
||||||
if (mth.isNoCode()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fixPhiVarNames(mth);
|
|
||||||
|
|
||||||
DexNode dex = mth.dex();
|
|
||||||
for (SSAVar var : mth.getSVars()) {
|
|
||||||
// inference variable type
|
|
||||||
ArgType type = processType(dex, var);
|
|
||||||
if (type == null) {
|
|
||||||
type = ArgType.UNKNOWN;
|
|
||||||
}
|
|
||||||
var.setType(type);
|
|
||||||
|
|
||||||
// search variable name
|
|
||||||
String name = processVarName(var);
|
|
||||||
var.setName(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// fix type for vars used only in Phi nodes
|
|
||||||
for (SSAVar sVar : mth.getSVars()) {
|
|
||||||
PhiInsn phi = sVar.getUsedInPhi();
|
|
||||||
if (phi != null) {
|
|
||||||
processPhiNode(phi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ArgType processType(DexNode dex, SSAVar var) {
|
|
||||||
RegisterArg assign = var.getAssign();
|
|
||||||
List<RegisterArg> useList = var.getUseList();
|
|
||||||
if (useList.isEmpty() || var.isTypeImmutable()) {
|
|
||||||
return assign.getType();
|
|
||||||
}
|
|
||||||
ArgType type = assign.getType();
|
|
||||||
for (RegisterArg arg : useList) {
|
|
||||||
ArgType useType = arg.getType();
|
|
||||||
if (!type.isTypeKnown() || !useType.isTypeKnown()) {
|
|
||||||
ArgType newType = ArgType.merge(dex, type, useType);
|
|
||||||
if (newType != null) {
|
|
||||||
type = newType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static 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);
|
|
||||||
SSAVar sVar = arg.getSVar();
|
|
||||||
if (sVar != null) {
|
|
||||||
sVar.setName(phi.getResult().getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void fixPhiVarNames(MethodNode mth) {
|
|
||||||
for (BlockNode block : mth.getBasicBlocks()) {
|
|
||||||
PhiListAttr phiList = block.get(AType.PHI_LIST);
|
|
||||||
if (phiList == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (PhiInsn phiInsn : phiList.getList()) {
|
|
||||||
RegisterArg resArg = phiInsn.getResult();
|
|
||||||
int argsCount = phiInsn.getArgsCount();
|
|
||||||
for (int i = 0; i < argsCount; i++) {
|
|
||||||
RegisterArg arg = phiInsn.getArg(i);
|
|
||||||
arg.mergeName(resArg);
|
|
||||||
}
|
|
||||||
VarName varName = resArg.getSVar().getVarName();
|
|
||||||
if (varName == null) {
|
|
||||||
varName = new VarName();
|
|
||||||
resArg.getSVar().setVarName(varName);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < argsCount; i++) {
|
|
||||||
RegisterArg arg = phiInsn.getArg(i);
|
|
||||||
arg.getSVar().setVarName(varName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String processVarName(SSAVar var) {
|
|
||||||
String name = var.getAssign().getName();
|
|
||||||
if (name != null) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
for (RegisterArg arg : var.getUseList()) {
|
|
||||||
String vName = arg.getName();
|
|
||||||
if (vName != null) {
|
|
||||||
return vName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,177 @@
|
|||||||
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.IndexInsnNode;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
|
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||||
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.dex.visitors.AbstractVisitor;
|
||||||
|
import jadx.core.dex.visitors.ConstInlineVisitor;
|
||||||
|
import jadx.core.dex.visitors.JadxVisitor;
|
||||||
|
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||||
|
|
||||||
|
@JadxVisitor(
|
||||||
|
name = "Type Inference",
|
||||||
|
desc = "Calculate best types for registers",
|
||||||
|
runAfter = {
|
||||||
|
SSATransform.class,
|
||||||
|
ConstInlineVisitor.class
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(TypeInferenceVisitor.class);
|
||||||
|
|
||||||
|
private TypeUpdate typeUpdate;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(RootNode root) {
|
||||||
|
typeUpdate = root.getTypeUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(MethodNode mth) {
|
||||||
|
if (mth.isNoCode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// collect initial types from assign and usages
|
||||||
|
mth.getSVars().forEach(this::attachBounds);
|
||||||
|
// start initial type changing
|
||||||
|
mth.getSVars().forEach(this::setBestType);
|
||||||
|
|
||||||
|
// try all possible types if var type is still unknown
|
||||||
|
mth.getSVars().forEach(var -> {
|
||||||
|
ArgType type = var.getTypeInfo().getType();
|
||||||
|
if (type != null && !type.isTypeKnown()) {
|
||||||
|
tryAllTypes(var, type);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBestType(SSAVar ssaVar) {
|
||||||
|
try {
|
||||||
|
RegisterArg assignArg = ssaVar.getAssign();
|
||||||
|
if (assignArg.isTypeImmutable()) {
|
||||||
|
ArgType initType = assignArg.getInitType();
|
||||||
|
TypeUpdateResult result = typeUpdate.apply(ssaVar, initType);
|
||||||
|
if (result == TypeUpdateResult.REJECT && LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Initial immutable type set rejected: {} -> {}", ssaVar, initType);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
calculateFromBounds(ssaVar);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Failed to calculate best type for var: {}", ssaVar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void calculateFromBounds(SSAVar ssaVar) {
|
||||||
|
TypeInfo typeInfo = ssaVar.getTypeInfo();
|
||||||
|
Set<ITypeBound> bounds = typeInfo.getBounds();
|
||||||
|
Optional<ArgType> bestTypeOpt = selectBestTypeFromBounds(bounds);
|
||||||
|
if (bestTypeOpt.isPresent()) {
|
||||||
|
ArgType candidateType = bestTypeOpt.get();
|
||||||
|
TypeUpdateResult result = typeUpdate.apply(ssaVar, candidateType);
|
||||||
|
if (result == TypeUpdateResult.REJECT && LOG.isDebugEnabled()) {
|
||||||
|
if (ssaVar.getTypeInfo().getType().equals(candidateType)) {
|
||||||
|
LOG.warn("Same type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds);
|
||||||
|
} else {
|
||||||
|
LOG.debug("Type set rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!bounds.isEmpty()) {
|
||||||
|
LOG.warn("Failed to select best type from bounds: ");
|
||||||
|
for (ITypeBound bound : bounds) {
|
||||||
|
LOG.warn(" {}", bound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ArgType> selectBestTypeFromBounds(Set<ITypeBound> bounds) {
|
||||||
|
return bounds.stream()
|
||||||
|
.map(ITypeBound::getType)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.max(typeUpdate.getArgTypeComparator());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attachBounds(SSAVar var) {
|
||||||
|
TypeInfo typeInfo = var.getTypeInfo();
|
||||||
|
RegisterArg assign = var.getAssign();
|
||||||
|
addBound(typeInfo, makeAssignBound(assign));
|
||||||
|
|
||||||
|
for (RegisterArg regArg : var.getUseList()) {
|
||||||
|
addBound(typeInfo, makeUseBound(regArg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addBound(TypeInfo typeInfo, ITypeBound bound) {
|
||||||
|
if (bound != null && bound.getType() != ArgType.UNKNOWN) {
|
||||||
|
typeInfo.getBounds().add(bound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ITypeBound makeAssignBound(RegisterArg assign) {
|
||||||
|
InsnNode insn = assign.getParentInsn();
|
||||||
|
if (insn == null || assign.isTypeImmutable()) {
|
||||||
|
return new TypeBoundConst(BoundEnum.ASSIGN, assign.getInitType());
|
||||||
|
}
|
||||||
|
switch (insn.getType()) {
|
||||||
|
case NEW_INSTANCE:
|
||||||
|
ArgType clsType = (ArgType) ((IndexInsnNode) insn).getIndex();
|
||||||
|
return new TypeBoundConst(BoundEnum.ASSIGN, clsType);
|
||||||
|
|
||||||
|
case CONST:
|
||||||
|
LiteralArg constLit = (LiteralArg) insn.getArg(0);
|
||||||
|
return new TypeBoundConst(BoundEnum.ASSIGN, constLit.getType());
|
||||||
|
|
||||||
|
default:
|
||||||
|
ArgType type = insn.getResult().getInitType();
|
||||||
|
return new TypeBoundConst(BoundEnum.ASSIGN, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private ITypeBound makeUseBound(RegisterArg regArg) {
|
||||||
|
InsnNode insn = regArg.getParentInsn();
|
||||||
|
if (insn == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new TypeBoundConst(BoundEnum.USE, regArg.getInitType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryAllTypes(SSAVar var, ArgType type) {
|
||||||
|
List<ArgType> types = makePossibleTypesList(type);
|
||||||
|
for (ArgType candidateType : types) {
|
||||||
|
TypeUpdateResult result = typeUpdate.apply(var, candidateType);
|
||||||
|
if (result == TypeUpdateResult.CHANGED) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ArgType> makePossibleTypesList(ArgType type) {
|
||||||
|
List<ArgType> list = new ArrayList<>();
|
||||||
|
if (type.isArray()) {
|
||||||
|
for (ArgType arrElemType : makePossibleTypesList(type.getArrayElement())) {
|
||||||
|
list.add(ArgType.array(arrElemType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (PrimitiveType possibleType : type.getPossibleTypes()) {
|
||||||
|
list.add(ArgType.convertFromPrimitiveType(possibleType));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
|
||||||
|
public class TypeInfo {
|
||||||
|
private ArgType type = ArgType.UNKNOWN;
|
||||||
|
|
||||||
|
private final Set<ITypeBound> bounds = new HashSet<>();
|
||||||
|
|
||||||
|
public ArgType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(ArgType type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<ITypeBound> getBounds() {
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TypeInfo{" +
|
||||||
|
"type=" + type +
|
||||||
|
", bounds=" + bounds +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,322 @@
|
|||||||
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
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.PrimitiveType;
|
||||||
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
|
import jadx.core.dex.instructions.args.Typed;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
import static jadx.core.dex.visitors.typeinference.TypeUpdateResult.CHANGED;
|
||||||
|
import static jadx.core.dex.visitors.typeinference.TypeUpdateResult.REJECT;
|
||||||
|
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 TypeCompare comparator;
|
||||||
|
|
||||||
|
public TypeUpdate(RootNode root) {
|
||||||
|
this.listenerRegistry = initListenerRegistry();
|
||||||
|
this.comparator = new TypeCompare(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TypeUpdateResult apply(SSAVar ssaVar, ArgType candidateType) {
|
||||||
|
if (candidateType == null) {
|
||||||
|
return REJECT;
|
||||||
|
}
|
||||||
|
if (!candidateType.isTypeKnown() && ssaVar.getTypeInfo().getType().isTypeKnown()) {
|
||||||
|
return REJECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeUpdateInfo updateInfo = new TypeUpdateInfo();
|
||||||
|
TypeUpdateResult result = updateTypeChecked(updateInfo, ssaVar.getAssign(), candidateType);
|
||||||
|
if (result == REJECT) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Map<InsnArg, ArgType> updates = updateInfo.getUpdates();
|
||||||
|
if (updates.isEmpty()) {
|
||||||
|
return SAME;
|
||||||
|
}
|
||||||
|
updates.forEach(Typed::setType);
|
||||||
|
return CHANGED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeUpdateResult updateTypeChecked(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) {
|
||||||
|
if (candidateType == null) {
|
||||||
|
LOG.warn("Reject null type update, arg: {}, info: {}", arg, updateInfo, new RuntimeException());
|
||||||
|
return REJECT;
|
||||||
|
}
|
||||||
|
ArgType currentType = arg.getType();
|
||||||
|
if (Objects.equals(currentType, candidateType)) {
|
||||||
|
return SAME;
|
||||||
|
}
|
||||||
|
if (arg.isTypeImmutable() && currentType != ArgType.UNKNOWN) {
|
||||||
|
return REJECT;
|
||||||
|
}
|
||||||
|
if (arg instanceof RegisterArg) {
|
||||||
|
RegisterArg reg = (RegisterArg) arg;
|
||||||
|
return updateTypeForSsaVar(updateInfo, reg.getSVar(), candidateType);
|
||||||
|
}
|
||||||
|
return requestUpdate(updateInfo, arg, candidateType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeUpdateResult updateTypeForSsaVar(TypeUpdateInfo updateInfo, SSAVar ssaVar, ArgType candidateType) {
|
||||||
|
TypeInfo typeInfo = ssaVar.getTypeInfo();
|
||||||
|
if (!inBounds(typeInfo.getBounds(), candidateType)) {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Reject type '{}' for {} by bounds: {}", candidateType, ssaVar, typeInfo.getBounds());
|
||||||
|
}
|
||||||
|
return REJECT;
|
||||||
|
}
|
||||||
|
return requestUpdateForSsaVar(updateInfo, ssaVar, candidateType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private TypeUpdateResult requestUpdateForSsaVar(TypeUpdateInfo updateInfo, SSAVar ssaVar, ArgType candidateType) {
|
||||||
|
boolean allSame = true;
|
||||||
|
TypeUpdateResult result = requestUpdate(updateInfo, ssaVar.getAssign(), candidateType);
|
||||||
|
if (result == REJECT) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
List<RegisterArg> useList = ssaVar.getUseList();
|
||||||
|
for (RegisterArg arg : useList) {
|
||||||
|
TypeUpdateResult useResult = requestUpdate(updateInfo, arg, candidateType);
|
||||||
|
if (useResult == REJECT) {
|
||||||
|
return REJECT;
|
||||||
|
}
|
||||||
|
if (useResult != SAME) {
|
||||||
|
allSame = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allSame ? SAME : CHANGED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeUpdateResult requestUpdate(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) {
|
||||||
|
if (updateInfo.isProcessed(arg)) {
|
||||||
|
return CHANGED;
|
||||||
|
}
|
||||||
|
updateInfo.requestUpdate(arg, candidateType);
|
||||||
|
try {
|
||||||
|
return runListeners(updateInfo, arg, candidateType);
|
||||||
|
} catch (StackOverflowError overflow) {
|
||||||
|
throw new JadxOverflowException("Type update terminated with stack overflow, arg: " + arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeUpdateResult runListeners(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) {
|
||||||
|
InsnNode insn = arg.getParentInsn();
|
||||||
|
if (insn == null) {
|
||||||
|
return SAME;
|
||||||
|
}
|
||||||
|
List<ITypeListener> listeners = listenerRegistry.getListenersForInsn(insn.getType());
|
||||||
|
if (listeners.isEmpty()) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean inBounds(Set<ITypeBound> bounds, ArgType candidateType) {
|
||||||
|
for (ITypeBound bound : bounds) {
|
||||||
|
ArgType boundType = bound.getType();
|
||||||
|
if (boundType != null && !checkBound(candidateType, bound, boundType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private boolean checkBound(ArgType candidateType, ITypeBound bound, ArgType boundType) {
|
||||||
|
TypeCompareEnum compareResult = comparator.compareTypes(candidateType, boundType);
|
||||||
|
switch (compareResult) {
|
||||||
|
case EQUAL:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case WIDER:
|
||||||
|
case WIDER_BY_GENERIC:
|
||||||
|
return bound.getBound() != BoundEnum.USE;
|
||||||
|
|
||||||
|
case NARROW:
|
||||||
|
if (bound.getBound() == BoundEnum.ASSIGN) {
|
||||||
|
if (boundType.isTypeKnown() || !checkAssignForUnknown(boundType, candidateType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case NARROW_BY_GENERIC:
|
||||||
|
// allow replace object to same object with known generic type
|
||||||
|
// due to incomplete information about external methods and fields
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case CONFLICT:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case UNKNOWN:
|
||||||
|
LOG.warn("Can't compare types, unknown hierarchy: {} and {}", candidateType, boundType);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Not processed type compare enum: " + compareResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkAssignForUnknown(ArgType boundType, ArgType candidateType) {
|
||||||
|
if (boundType == ArgType.UNKNOWN) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
boolean candidateArray = candidateType.isArray();
|
||||||
|
if (boundType.isArray() && candidateArray) {
|
||||||
|
return checkAssignForUnknown(boundType.getArrayElement(), candidateType.getArrayElement());
|
||||||
|
}
|
||||||
|
if (candidateArray && boundType.contains(PrimitiveType.ARRAY)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (candidateType.isObject() && boundType.contains(PrimitiveType.OBJECT)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (candidateType.isPrimitive() && boundType.contains(candidateType.getPrimitiveType())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
return registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeUpdateResult sameFirstArgListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
||||||
|
InsnArg changeArg = isAssign(insn, arg) ? insn.getArg(0) : insn.getResult();
|
||||||
|
return updateTypeChecked(updateInfo, changeArg, candidateType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All args must have same types
|
||||||
|
*/
|
||||||
|
private TypeUpdateResult allSameListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
||||||
|
if (!isAssign(insn, arg)) {
|
||||||
|
return updateTypeChecked(updateInfo, insn.getResult(), candidateType);
|
||||||
|
}
|
||||||
|
boolean allSame = true;
|
||||||
|
for (InsnArg insnArg : insn.getArguments()) {
|
||||||
|
if (insnArg != arg) {
|
||||||
|
TypeUpdateResult result = updateTypeChecked(updateInfo, insnArg, candidateType);
|
||||||
|
if (result == REJECT) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (result != SAME) {
|
||||||
|
allSame = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allSame ? SAME : CHANGED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to set candidate type to all args, don't fail on reject
|
||||||
|
*/
|
||||||
|
private TypeUpdateResult suggestAllSameListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
||||||
|
if (!isAssign(insn, arg)) {
|
||||||
|
updateTypeChecked(updateInfo, insn.getResult(), candidateType);
|
||||||
|
}
|
||||||
|
boolean allSame = true;
|
||||||
|
for (InsnArg insnArg : insn.getArguments()) {
|
||||||
|
if (insnArg != arg) {
|
||||||
|
TypeUpdateResult result = updateTypeChecked(updateInfo, insnArg, candidateType);
|
||||||
|
if (result == REJECT) {
|
||||||
|
// ignore
|
||||||
|
} else if (result != SAME) {
|
||||||
|
allSame = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allSame ? SAME : CHANGED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeUpdateResult arrayGetListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
||||||
|
if (isAssign(insn, arg)) {
|
||||||
|
return updateTypeChecked(updateInfo, insn.getArg(0), ArgType.array(candidateType));
|
||||||
|
}
|
||||||
|
InsnArg arrArg = insn.getArg(0);
|
||||||
|
if (arrArg == arg) {
|
||||||
|
ArgType arrayElement = candidateType.getArrayElement();
|
||||||
|
if (arrayElement == null) {
|
||||||
|
return REJECT;
|
||||||
|
}
|
||||||
|
return updateTypeChecked(updateInfo, insn.getResult(), arrayElement);
|
||||||
|
}
|
||||||
|
// index argument
|
||||||
|
return SAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeUpdateResult arrayPutListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
||||||
|
InsnArg arrArg = insn.getArg(0);
|
||||||
|
InsnArg putArg = insn.getArg(2);
|
||||||
|
if (arrArg == arg) {
|
||||||
|
ArgType arrayElement = candidateType.getArrayElement();
|
||||||
|
if (arrayElement == null) {
|
||||||
|
return REJECT;
|
||||||
|
}
|
||||||
|
return updateTypeChecked(updateInfo, putArg, arrayElement);
|
||||||
|
}
|
||||||
|
if (arrArg == putArg) {
|
||||||
|
return updateTypeChecked(updateInfo, arrArg, ArgType.array(candidateType));
|
||||||
|
}
|
||||||
|
// index
|
||||||
|
return SAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeUpdateResult ifListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
||||||
|
InsnArg firstArg = insn.getArg(0);
|
||||||
|
InsnArg secondArg = insn.getArg(1);
|
||||||
|
InsnArg updateArg = firstArg == arg ? secondArg : firstArg;
|
||||||
|
return updateTypeChecked(updateInfo, updateArg, candidateType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAssign(InsnNode insn, InsnArg arg) {
|
||||||
|
return insn.getResult() == arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Comparator<ArgType> getArgTypeComparator() {
|
||||||
|
return comparator.getComparator();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
|
||||||
|
public class TypeUpdateInfo {
|
||||||
|
|
||||||
|
private final Map<InsnArg, ArgType> updates = new IdentityHashMap<>();
|
||||||
|
|
||||||
|
public void requestUpdate(InsnArg arg, ArgType changeType) {
|
||||||
|
updates.put(arg, changeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isProcessed(InsnArg arg) {
|
||||||
|
return updates.containsKey(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<InsnArg, ArgType> getUpdates() {
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.InsnType;
|
||||||
|
|
||||||
|
public class TypeUpdateRegistry {
|
||||||
|
|
||||||
|
private final Map<InsnType, List<ITypeListener>> listenersMap = new EnumMap<>(InsnType.class);
|
||||||
|
|
||||||
|
public void add(InsnType insnType, ITypeListener listener) {
|
||||||
|
listenersMap.computeIfAbsent(insnType, k -> new ArrayList<>(3)).add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public List<ITypeListener> getListenersForInsn(InsnType insnType) {
|
||||||
|
List<ITypeListener> list = listenersMap.get(insnType);
|
||||||
|
if (list == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
public enum TypeUpdateResult {
|
||||||
|
REJECT,
|
||||||
|
SAME,
|
||||||
|
CHANGED
|
||||||
|
}
|
@ -556,4 +556,38 @@ public class BlockUtils {
|
|||||||
blocks.forEach(block -> insns.addAll(block.getInstructions()));
|
blocks.forEach(block -> insns.addAll(block.getInstructions()));
|
||||||
return insns;
|
return insns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace insn by index i in block,
|
||||||
|
* for proper copy attributes, assume attributes are not overlap
|
||||||
|
*/
|
||||||
|
public static void replaceInsn(BlockNode block, int i, InsnNode insn) {
|
||||||
|
InsnNode prevInsn = block.getInstructions().get(i);
|
||||||
|
insn.copyAttributesFrom(prevInsn);
|
||||||
|
insn.setSourceLine(prevInsn.getSourceLine());
|
||||||
|
insn.setOffset(prevInsn.getOffset());
|
||||||
|
block.getInstructions().set(i, insn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean replaceInsn(BlockNode block, InsnNode oldInsn, InsnNode newInsn) {
|
||||||
|
List<InsnNode> instructions = block.getInstructions();
|
||||||
|
int size = instructions.size();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
InsnNode instruction = instructions.get(i);
|
||||||
|
if (instruction == oldInsn) {
|
||||||
|
replaceInsn(block, i, newInsn);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean replaceInsn(MethodNode mth, InsnNode oldInsn, InsnNode newInsn) {
|
||||||
|
for (BlockNode block : mth.getBasicBlocks()) {
|
||||||
|
if (replaceInsn(block, oldInsn, newInsn)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,11 @@ public class DebugUtils {
|
|||||||
dump(mth, "");
|
dump(mth, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void dumpRaw(MethodNode mth, String desc) {
|
||||||
|
File out = new File("test-graph-" + desc + "-tmp");
|
||||||
|
DotGraphVisitor.dumpRaw().save(out, mth);
|
||||||
|
}
|
||||||
|
|
||||||
public static void dump(MethodNode mth, String desc) {
|
public static void dump(MethodNode mth, String desc) {
|
||||||
File out = new File("test-graph-" + desc + "-tmp");
|
File out = new File("test-graph-" + desc + "-tmp");
|
||||||
DotGraphVisitor.dump().save(out, mth);
|
DotGraphVisitor.dump().save(out, mth);
|
||||||
|
@ -89,7 +89,7 @@ public class InstructionRemover {
|
|||||||
|
|
||||||
public static void unbindResult(MethodNode mth, InsnNode insn) {
|
public static void unbindResult(MethodNode mth, InsnNode insn) {
|
||||||
RegisterArg r = insn.getResult();
|
RegisterArg r = insn.getResult();
|
||||||
if (r != null && r.getSVar() != null) {
|
if (r != null && r.getSVar() != null && mth != null) {
|
||||||
mth.removeSVar(r.getSVar());
|
mth.removeSVar(r.getSVar());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,16 +57,15 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String arrayToString(Object[] array) {
|
public static <T> String arrayToStr(T[] arr) {
|
||||||
if (array == null) {
|
int len = arr == null ? 0 : arr.length;
|
||||||
|
if (len == 0) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for (int i = 0; i < array.length; i++) {
|
sb.append(arr[0]);
|
||||||
if (i != 0) {
|
for (int i = 1; i < len; i++) {
|
||||||
sb.append(", ");
|
sb.append(", ").append(arr[i]);
|
||||||
}
|
|
||||||
sb.append(array[i]);
|
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||||
|
|
||||||
public class JadxInternalAccess {
|
public class JadxInternalAccess {
|
||||||
|
|
||||||
public static RootNode getRoot(JadxDecompiler d) {
|
public static RootNode getRoot(JadxDecompiler d) {
|
||||||
return d.getRoot();
|
return d.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<IDexTreeVisitor> getPassList(JadxDecompiler d) {
|
||||||
|
return d.getPasses();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,113 @@
|
|||||||
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
|
||||||
|
import static jadx.core.dex.instructions.args.ArgType.BOOLEAN;
|
||||||
|
import static jadx.core.dex.instructions.args.ArgType.CHAR;
|
||||||
|
import static jadx.core.dex.instructions.args.ArgType.INT;
|
||||||
|
import static jadx.core.dex.instructions.args.ArgType.NARROW;
|
||||||
|
import static jadx.core.dex.instructions.args.ArgType.NARROW_INTEGRAL;
|
||||||
|
import static jadx.core.dex.instructions.args.ArgType.OBJECT;
|
||||||
|
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN;
|
||||||
|
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN_OBJECT;
|
||||||
|
import static jadx.core.dex.instructions.args.ArgType.array;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
public class TypeCompareTest {
|
||||||
|
private TypeCompare compare;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
JadxArgs args = new JadxArgs();
|
||||||
|
RootNode root = new RootNode(args);
|
||||||
|
root.load(Collections.emptyList());
|
||||||
|
root.initClassPath();
|
||||||
|
compare = new TypeCompare(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compareTypes() {
|
||||||
|
firstIsNarrow(INT, UNKNOWN);
|
||||||
|
|
||||||
|
firstIsNarrow(BOOLEAN, INT);
|
||||||
|
|
||||||
|
firstIsNarrow(array(UNKNOWN), UNKNOWN);
|
||||||
|
firstIsNarrow(array(UNKNOWN), NARROW);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void comparePrimitives() {
|
||||||
|
check(INT, UNKNOWN_OBJECT, TypeCompareEnum.CONFLICT);
|
||||||
|
check(INT, OBJECT, TypeCompareEnum.CONFLICT);
|
||||||
|
check(INT, CHAR, TypeCompareEnum.WIDER);
|
||||||
|
|
||||||
|
firstIsNarrow(CHAR, NARROW_INTEGRAL);
|
||||||
|
firstIsNarrow(array(CHAR), UNKNOWN_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compareArrays() {
|
||||||
|
firstIsNarrow(array(CHAR), OBJECT);
|
||||||
|
firstIsNarrow(array(CHAR), array(UNKNOWN));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compareGenerics() {
|
||||||
|
ArgType mapCls = ArgType.object("java.util.Map");
|
||||||
|
ArgType setCls = ArgType.object("java.util.Set");
|
||||||
|
|
||||||
|
ArgType keyType = ArgType.genericType("K");
|
||||||
|
ArgType valueType = ArgType.genericType("V");
|
||||||
|
ArgType mapGeneric = ArgType.generic(mapCls.getObject(), new ArgType[]{keyType, valueType});
|
||||||
|
|
||||||
|
check(mapGeneric, mapCls, TypeCompareEnum.NARROW_BY_GENERIC);
|
||||||
|
check(mapCls, mapGeneric, TypeCompareEnum.WIDER_BY_GENERIC);
|
||||||
|
|
||||||
|
check(mapCls, setCls, TypeCompareEnum.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compareGenericTypes() {
|
||||||
|
ArgType vType = ArgType.genericType("V");
|
||||||
|
ArgType rType = ArgType.genericType("R");
|
||||||
|
|
||||||
|
check(vType, ArgType.OBJECT, TypeCompareEnum.NARROW_BY_GENERIC);
|
||||||
|
check(ArgType.OBJECT, vType, TypeCompareEnum.WIDER_BY_GENERIC);
|
||||||
|
|
||||||
|
check(vType, rType, TypeCompareEnum.CONFLICT);
|
||||||
|
check(vType, vType, TypeCompareEnum.EQUAL);
|
||||||
|
|
||||||
|
ArgType tType = ArgType.genericType("T");
|
||||||
|
tType.setExtendTypes(Collections.singletonList(ArgType.STRING));
|
||||||
|
|
||||||
|
check(tType, ArgType.STRING, TypeCompareEnum.NARROW_BY_GENERIC);
|
||||||
|
check(ArgType.STRING, tType, TypeCompareEnum.WIDER_BY_GENERIC);
|
||||||
|
|
||||||
|
check(tType, ArgType.OBJECT, TypeCompareEnum.NARROW_BY_GENERIC);
|
||||||
|
check(ArgType.OBJECT, tType, TypeCompareEnum.WIDER_BY_GENERIC);
|
||||||
|
|
||||||
|
// TODO: use extend types from generic declaration for more strict checks
|
||||||
|
// check(vType, ArgType.STRING, TypeCompareEnum.CONFLICT);
|
||||||
|
// check(ArgType.STRING, vType, TypeCompareEnum.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void firstIsNarrow(ArgType first, ArgType second) {
|
||||||
|
check(first, second, TypeCompareEnum.NARROW);
|
||||||
|
// reverse
|
||||||
|
check(second, first, TypeCompareEnum.WIDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void check(ArgType first, ArgType second, TypeCompareEnum expectedResult) {
|
||||||
|
TypeCompareEnum result = compare.compareTypes(first, second);
|
||||||
|
assertThat("Compare '" + first + "' vs '" + second + "'",
|
||||||
|
result, is(expectedResult));
|
||||||
|
}
|
||||||
|
}
|
@ -53,7 +53,7 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
/**
|
/**
|
||||||
* Run auto check method if defined:
|
* Run auto check method if defined:
|
||||||
* <pre>
|
* <pre>
|
||||||
* public static void check()
|
* public void check() {}
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public static final String CHECK_METHOD_NAME = "check";
|
public static final String CHECK_METHOD_NAME = "check";
|
||||||
|
@ -2,7 +2,6 @@ package jadx.tests.external;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -14,7 +13,6 @@ import jadx.api.JadxArgs;
|
|||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
import jadx.api.JadxInternalAccess;
|
import jadx.api.JadxInternalAccess;
|
||||||
import jadx.api.JavaClass;
|
import jadx.api.JavaClass;
|
||||||
import jadx.core.Jadx;
|
|
||||||
import jadx.core.codegen.CodeGen;
|
import jadx.core.codegen.CodeGen;
|
||||||
import jadx.core.codegen.CodeWriter;
|
import jadx.core.codegen.CodeWriter;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
@ -57,9 +55,7 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
|||||||
processAll(jadx);
|
processAll(jadx);
|
||||||
// jadx.saveSources();
|
// jadx.saveSources();
|
||||||
} else {
|
} else {
|
||||||
Pattern clsPtrn = Pattern.compile(clsPatternStr);
|
processByPatterns(jadx, clsPatternStr, mthPatternStr);
|
||||||
Pattern mthPtrn = mthPatternStr == null ? null : Pattern.compile(mthPatternStr);
|
|
||||||
processByPatterns(jadx, clsPtrn, mthPtrn);
|
|
||||||
}
|
}
|
||||||
printErrorReport(jadx);
|
printErrorReport(jadx);
|
||||||
}
|
}
|
||||||
@ -70,13 +66,13 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processByPatterns(JadxDecompiler jadx, Pattern clsPattern, @Nullable Pattern mthPattern) {
|
private void processByPatterns(JadxDecompiler jadx, String clsPattern, @Nullable String mthPattern) {
|
||||||
List<IDexTreeVisitor> passes = Jadx.getPassesList(jadx.getArgs());
|
List<IDexTreeVisitor> passes = JadxInternalAccess.getPassList(jadx);
|
||||||
RootNode root = JadxInternalAccess.getRoot(jadx);
|
RootNode root = JadxInternalAccess.getRoot(jadx);
|
||||||
int processed = 0;
|
int processed = 0;
|
||||||
for (ClassNode classNode : root.getClasses(true)) {
|
for (ClassNode classNode : root.getClasses(true)) {
|
||||||
String clsFullName = classNode.getClassInfo().getFullName();
|
String clsFullName = classNode.getClassInfo().getFullName();
|
||||||
if (clsPattern.matcher(clsFullName).matches()) {
|
if (isMatch(clsFullName, clsPattern)) {
|
||||||
if (processCls(mthPattern, passes, classNode)) {
|
if (processCls(mthPattern, passes, classNode)) {
|
||||||
processed++;
|
processed++;
|
||||||
}
|
}
|
||||||
@ -85,14 +81,14 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
|||||||
assertThat("No classes processed", processed, greaterThan(0));
|
assertThat("No classes processed", processed, greaterThan(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean processCls(@Nullable Pattern mthPattern, List<IDexTreeVisitor> passes, ClassNode classNode) {
|
private boolean processCls(@Nullable String mthPattern, List<IDexTreeVisitor> passes, ClassNode classNode) {
|
||||||
classNode.load();
|
classNode.load();
|
||||||
boolean decompile = false;
|
boolean decompile = false;
|
||||||
if (mthPattern == null) {
|
if (mthPattern == null) {
|
||||||
decompile = true;
|
decompile = true;
|
||||||
} else {
|
} else {
|
||||||
for (MethodNode mth : classNode.getMethods()) {
|
for (MethodNode mth : classNode.getMethods()) {
|
||||||
if (mthPattern.matcher(mth.getName()).matches()) {
|
if (isMthMatch(mth, mthPattern)) {
|
||||||
decompile = true;
|
decompile = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -119,14 +115,26 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void printMethods(ClassNode classNode, @NotNull Pattern mthPattern) {
|
private boolean isMthMatch(MethodNode mth, String mthPattern) {
|
||||||
|
String shortId = mth.getMethodInfo().getShortId();
|
||||||
|
return isMatch(shortId, mthPattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isMatch(String str, String pattern) {
|
||||||
|
if (str.equals(pattern)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return str.startsWith(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printMethods(ClassNode classNode, @NotNull String mthPattern) {
|
||||||
String code = classNode.getCode().getCodeStr();
|
String code = classNode.getCode().getCodeStr();
|
||||||
if (code == null) {
|
if (code == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String[] lines = code.split(CodeWriter.NL);
|
String[] lines = code.split(CodeWriter.NL);
|
||||||
for (MethodNode mth : classNode.getMethods()) {
|
for (MethodNode mth : classNode.getMethods()) {
|
||||||
if (mthPattern.matcher(mth.getName()).matches()) {
|
if (isMthMatch(mth, mthPattern)) {
|
||||||
int decompiledLine = mth.getDecompiledLine();
|
int decompiledLine = mth.getDecompiledLine();
|
||||||
StringBuilder mthCode = new StringBuilder();
|
StringBuilder mthCode = new StringBuilder();
|
||||||
int brackets = 0;
|
int brackets = 0;
|
||||||
@ -139,7 +147,7 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG.info("\n{}", mthCode);
|
LOG.info("{}\n{}", mth.getMethodInfo().getShortId(), mthCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,128 +0,0 @@
|
|||||||
package jadx.tests.functional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import jadx.core.clsp.ClspGraph;
|
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
|
||||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
|
||||||
import jadx.core.dex.nodes.DexNode;
|
|
||||||
import jadx.core.dex.nodes.RootNode;
|
|
||||||
import jadx.core.utils.exceptions.DecodeException;
|
|
||||||
|
|
||||||
import static jadx.core.dex.instructions.args.ArgType.BOOLEAN;
|
|
||||||
import static jadx.core.dex.instructions.args.ArgType.BYTE;
|
|
||||||
import static jadx.core.dex.instructions.args.ArgType.CHAR;
|
|
||||||
import static jadx.core.dex.instructions.args.ArgType.INT;
|
|
||||||
import static jadx.core.dex.instructions.args.ArgType.LONG;
|
|
||||||
import static jadx.core.dex.instructions.args.ArgType.NARROW;
|
|
||||||
import static jadx.core.dex.instructions.args.ArgType.OBJECT;
|
|
||||||
import static jadx.core.dex.instructions.args.ArgType.STRING;
|
|
||||||
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN;
|
|
||||||
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN_OBJECT;
|
|
||||||
import static jadx.core.dex.instructions.args.ArgType.array;
|
|
||||||
import static jadx.core.dex.instructions.args.ArgType.genericType;
|
|
||||||
import static jadx.core.dex.instructions.args.ArgType.object;
|
|
||||||
import static jadx.core.dex.instructions.args.ArgType.unknown;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
public class TypeMergeTest {
|
|
||||||
|
|
||||||
private DexNode dex;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void initClsp() throws IOException, DecodeException {
|
|
||||||
ClspGraph clsp = new ClspGraph();
|
|
||||||
clsp.load();
|
|
||||||
dex = mock(DexNode.class);
|
|
||||||
RootNode rootNode = mock(RootNode.class);
|
|
||||||
when(rootNode.getClsp()).thenReturn(clsp);
|
|
||||||
when(dex.root()).thenReturn(rootNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMerge() throws IOException, DecodeException {
|
|
||||||
first(INT, INT);
|
|
||||||
first(BOOLEAN, INT);
|
|
||||||
reject(INT, LONG);
|
|
||||||
first(INT, UNKNOWN);
|
|
||||||
reject(INT, UNKNOWN_OBJECT);
|
|
||||||
|
|
||||||
first(INT, NARROW);
|
|
||||||
first(CHAR, INT);
|
|
||||||
|
|
||||||
check(unknown(PrimitiveType.INT, PrimitiveType.BOOLEAN, PrimitiveType.FLOAT),
|
|
||||||
unknown(PrimitiveType.INT, PrimitiveType.BOOLEAN),
|
|
||||||
unknown(PrimitiveType.INT, PrimitiveType.BOOLEAN));
|
|
||||||
|
|
||||||
check(unknown(PrimitiveType.INT, PrimitiveType.FLOAT),
|
|
||||||
unknown(PrimitiveType.INT, PrimitiveType.BOOLEAN),
|
|
||||||
INT);
|
|
||||||
|
|
||||||
check(unknown(PrimitiveType.INT, PrimitiveType.OBJECT),
|
|
||||||
unknown(PrimitiveType.OBJECT, PrimitiveType.ARRAY),
|
|
||||||
unknown(PrimitiveType.OBJECT));
|
|
||||||
|
|
||||||
ArgType objExc = object("java.lang.Exception");
|
|
||||||
ArgType objThr = object("java.lang.Throwable");
|
|
||||||
ArgType objIO = object("java.io.IOException");
|
|
||||||
ArgType objArr = object("java.lang.ArrayIndexOutOfBoundsException");
|
|
||||||
ArgType objList = object("java.util.List");
|
|
||||||
|
|
||||||
first(objExc, objExc);
|
|
||||||
check(objExc, objList, OBJECT);
|
|
||||||
first(objExc, OBJECT);
|
|
||||||
|
|
||||||
check(objExc, objThr, objThr);
|
|
||||||
check(objIO, objArr, objExc);
|
|
||||||
|
|
||||||
ArgType generic = genericType("T");
|
|
||||||
first(generic, objExc);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testArrayMerge() {
|
|
||||||
check(array(INT), array(BYTE), ArgType.OBJECT);
|
|
||||||
first(array(INT), array(INT));
|
|
||||||
first(array(STRING), array(STRING));
|
|
||||||
|
|
||||||
first(OBJECT, array(INT));
|
|
||||||
first(OBJECT, array(STRING));
|
|
||||||
|
|
||||||
first(array(unknown(PrimitiveType.INT, PrimitiveType.FLOAT)), unknown(PrimitiveType.ARRAY));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void first(ArgType t1, ArgType t2) {
|
|
||||||
check(t1, t2, t1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reject(ArgType t1, ArgType t2) {
|
|
||||||
check(t1, t2, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void check(ArgType t1, ArgType t2, ArgType exp) {
|
|
||||||
merge(t1, t2, exp);
|
|
||||||
merge(t2, t1, exp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void merge(ArgType t1, ArgType t2, ArgType exp) {
|
|
||||||
ArgType res = ArgType.merge(dex, t1, t2);
|
|
||||||
String msg = format(t1, t2, exp, res);
|
|
||||||
if (exp == null) {
|
|
||||||
assertNull("Incorrect accept: " + msg, res);
|
|
||||||
} else {
|
|
||||||
assertNotNull("Incorrect reject: " + msg, res);
|
|
||||||
assertTrue("Incorrect result: " + msg, exp.equals(res));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String format(ArgType t1, ArgType t2, ArgType exp, ArgType res) {
|
|
||||||
return t1 + " <+> " + t2 + " = '" + res + "', expected: " + exp;
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,6 +7,7 @@ import jadx.tests.api.IntegrationTest;
|
|||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
import static org.hamcrest.CoreMatchers.not;
|
import static org.hamcrest.CoreMatchers.not;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
public class TestFloatValue extends IntegrationTest {
|
public class TestFloatValue extends IntegrationTest {
|
||||||
@ -17,6 +18,10 @@ public class TestFloatValue extends IntegrationTest {
|
|||||||
fa[0] /= 2;
|
fa[0] /= 2;
|
||||||
return fa;
|
return fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void check() {
|
||||||
|
assertEquals(0.275f, method()[0], 0.0001f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -17,7 +17,10 @@ public class TestRedundantBrackets extends IntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int method2(Object obj) {
|
public int method2(Object obj) {
|
||||||
return obj instanceof String ? ((String) obj).length() : 0;
|
if (obj instanceof String) {
|
||||||
|
return ((String) obj).length();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int method3(int a, int b) {
|
public int method3(int a, int b) {
|
||||||
@ -50,7 +53,8 @@ public class TestRedundantBrackets extends IntegrationTest {
|
|||||||
assertThat(code, not(containsString("(-1)")));
|
assertThat(code, not(containsString("(-1)")));
|
||||||
assertThat(code, not(containsString("return;")));
|
assertThat(code, not(containsString("return;")));
|
||||||
|
|
||||||
assertThat(code, containsString("return obj instanceof String ? ((String) obj).length() : 0;"));
|
assertThat(code, containsString("if (obj instanceof String) {"));
|
||||||
|
assertThat(code, containsString("return ((String) obj).length();"));
|
||||||
assertThat(code, containsString("a + b < 10"));
|
assertThat(code, containsString("a + b < 10"));
|
||||||
assertThat(code, containsString("(a & b) != 0"));
|
assertThat(code, containsString("(a & b) != 0"));
|
||||||
assertThat(code, containsString("if (num == 4 || num == 6 || num == 8 || num == 10)"));
|
assertThat(code, containsString("if (num == 4 || num == 6 || num == 8 || num == 10)"));
|
||||||
|
@ -0,0 +1,114 @@
|
|||||||
|
package jadx.tests.integration.annotations;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class TestAnnotationsMix extends IntegrationTest {
|
||||||
|
|
||||||
|
public static class TestCls {
|
||||||
|
|
||||||
|
public boolean test() throws Exception {
|
||||||
|
Class<?> cls = TestCls.class;
|
||||||
|
new Thread();
|
||||||
|
|
||||||
|
Method err = cls.getMethod("error");
|
||||||
|
assertTrue(err.getExceptionTypes().length > 0);
|
||||||
|
assertTrue(err.getExceptionTypes()[0] == Exception.class);
|
||||||
|
|
||||||
|
Method d = cls.getMethod("depr", String[].class);
|
||||||
|
assertTrue(d.getAnnotations().length > 0);
|
||||||
|
assertTrue(d.getAnnotations()[0].annotationType() == Deprecated.class);
|
||||||
|
|
||||||
|
Method ma = cls.getMethod("test", String[].class);
|
||||||
|
assertTrue(ma.getAnnotations().length > 0);
|
||||||
|
MyAnnotation a = (MyAnnotation) ma.getAnnotations()[0];
|
||||||
|
assertTrue(a.num() == 7);
|
||||||
|
assertTrue(a.state() == Thread.State.TERMINATED);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public int a;
|
||||||
|
|
||||||
|
public void error() throws Exception {
|
||||||
|
throw new Exception("error");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public static Object depr(String[] a) {
|
||||||
|
return Arrays.asList(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MyAnnotation(name = "b",
|
||||||
|
num = 7,
|
||||||
|
cls = Exception.class,
|
||||||
|
doubles = {0.0, 1.1},
|
||||||
|
value = 9.87f,
|
||||||
|
simple = @SimpleAnnotation(false))
|
||||||
|
public static Object test(String[] a) {
|
||||||
|
return Arrays.asList(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
public @interface MyAnnotation {
|
||||||
|
String name() default "a";
|
||||||
|
|
||||||
|
String str() default "str";
|
||||||
|
|
||||||
|
int num();
|
||||||
|
|
||||||
|
float value();
|
||||||
|
|
||||||
|
double[] doubles();
|
||||||
|
|
||||||
|
Class<?> cls();
|
||||||
|
|
||||||
|
SimpleAnnotation simple();
|
||||||
|
|
||||||
|
Thread.State state() default Thread.State.TERMINATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @interface SimpleAnnotation {
|
||||||
|
boolean value();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void check() throws Exception {
|
||||||
|
test();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
ClassNode cls = getClassNode(TestCls.class);
|
||||||
|
String code = cls.getCode().toString();
|
||||||
|
|
||||||
|
assertThat(code, not(containsString("int i = false;")));
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// assertThat(code, not(containsString("Thread thread = new Thread();")));
|
||||||
|
// assertThat(code, containsString("new Thread();"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoDebug() {
|
||||||
|
noDebugInfo();
|
||||||
|
getClassNode(TestCls.class);
|
||||||
|
}
|
||||||
|
}
|
@ -39,5 +39,15 @@ public class TestArith3 extends IntegrationTest {
|
|||||||
assertThat(code, containsOne("while (n + 4 < buffer.length) {"));
|
assertThat(code, containsOne("while (n + 4 < buffer.length) {"));
|
||||||
assertThat(code, containsOne("n += len + 5;"));
|
assertThat(code, containsOne("n += len + 5;"));
|
||||||
assertThat(code, not(containsString("; n += len + 5) {")));
|
assertThat(code, not(containsString("; n += len + 5) {")));
|
||||||
|
assertThat(code, not(containsString("default:")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoDebug() {
|
||||||
|
noDebugInfo();
|
||||||
|
ClassNode cls = getClassNode(TestCls.class);
|
||||||
|
String code = cls.getCode().toString();
|
||||||
|
|
||||||
|
assertThat(code, containsOne("while ("));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,35 @@
|
|||||||
package jadx.tests.integration.arrays;
|
package jadx.tests.integration.arrays;
|
||||||
|
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
|
||||||
import jadx.tests.api.SmaliTest;
|
|
||||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class TestArrays4 extends SmaliTest {
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
public static class TestCls {
|
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||||
char[] payload;
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
public TestCls(byte[] bytes) {
|
public class TestArrays4 extends IntegrationTest {
|
||||||
char[] a = toChars(bytes);
|
|
||||||
this.payload = new char[a.length];
|
|
||||||
System.arraycopy(a, 0, this.payload, 0, bytes.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static char[] toChars(byte[] bArr) {
|
public static class TestCls {
|
||||||
return new char[bArr.length];
|
char[] payload;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
public TestCls(byte[] bytes) {
|
||||||
public void testArrayTypeInference() {
|
char[] a = toChars(bytes);
|
||||||
noDebugInfo();
|
this.payload = new char[a.length];
|
||||||
ClassNode cls = getClassNode(TestCls.class);
|
System.arraycopy(a, 0, this.payload, 0, bytes.length);
|
||||||
String code = cls.getCode().toString();
|
}
|
||||||
|
|
||||||
assertThat(code, containsOne("char[] toChars = toChars(bArr);"));
|
private static char[] toChars(byte[] bArr) {
|
||||||
}
|
return new char[bArr.length];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testArrayTypeInference() {
|
||||||
|
noDebugInfo();
|
||||||
|
ClassNode cls = getClassNode(TestCls.class);
|
||||||
|
String code = cls.getCode().toString();
|
||||||
|
|
||||||
|
assertThat(code, containsOne("char[] chars = toChars(bArr);"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ public class TestTernary extends IntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int test3(int a) {
|
public int test3(int a) {
|
||||||
return a > 0 ? 1 : (a + 2) * 3;
|
return a > 0 ? a : (a + 2) * 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,6 +34,6 @@ public class TestTernary extends IntegrationTest {
|
|||||||
assertThat(code, not(containsString("else")));
|
assertThat(code, not(containsString("else")));
|
||||||
assertThat(code, containsString("return a != 2;"));
|
assertThat(code, containsString("return a != 2;"));
|
||||||
assertThat(code, containsString("assertTrue(a == 3)"));
|
assertThat(code, containsString("assertTrue(a == 3)"));
|
||||||
assertThat(code, containsString("return a > 0 ? 1 : (a + 2) * 3;"));
|
assertThat(code, containsString("return a > 0 ? a : (a + 2) * 3;"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,23 +25,23 @@ public class TestReturnSourceLine extends IntegrationTest {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int test2(int v) {
|
// public int test2(int v) {
|
||||||
if (v == 0) {
|
// if (v == 0) {
|
||||||
f();
|
// f();
|
||||||
return v - 1;
|
// return v - 1;
|
||||||
}
|
// }
|
||||||
f();
|
// f();
|
||||||
return v + 1;
|
// return v + 1;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
public int test3(int v) {
|
// public int test3(int v) {
|
||||||
if (v == 0) {
|
// if (v == 0) {
|
||||||
f();
|
// f();
|
||||||
return v;
|
// return v;
|
||||||
}
|
// }
|
||||||
f();
|
// f();
|
||||||
return v + 1;
|
// return v + 1;
|
||||||
}
|
// }
|
||||||
|
|
||||||
private void f() {
|
private void f() {
|
||||||
}
|
}
|
||||||
@ -57,8 +57,8 @@ public class TestReturnSourceLine extends IntegrationTest {
|
|||||||
MethodNode test1 = cls.searchMethodByName("test1(Z)I");
|
MethodNode test1 = cls.searchMethodByName("test1(Z)I");
|
||||||
checkLine(lines, codeWriter, test1, 3, "return 1;");
|
checkLine(lines, codeWriter, test1, 3, "return 1;");
|
||||||
|
|
||||||
MethodNode test2 = cls.searchMethodByName("test2(I)I");
|
// MethodNode test2 = cls.searchMethodByName("test2(I)I");
|
||||||
checkLine(lines, codeWriter, test2, 3, "return v - 1;");
|
// checkLine(lines, codeWriter, test2, 3, "return v - 1;");
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// MethodNode test3 = cls.searchMethodByName("test3(I)I");
|
// MethodNode test3 = cls.searchMethodByName("test3(I)I");
|
||||||
|
@ -3,10 +3,8 @@ package jadx.tests.integration.debuginfo;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.tests.api.IntegrationTest;
|
|
||||||
import jadx.tests.api.SmaliTest;
|
import jadx.tests.api.SmaliTest;
|
||||||
|
|
||||||
import static jadx.tests.api.utils.JadxMatchers.containsLines;
|
|
||||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
@ -29,7 +29,10 @@ public class TestGenerics2 extends IntegrationTest {
|
|||||||
|
|
||||||
public V get(Object id) {
|
public V get(Object id) {
|
||||||
WeakReference<V> ref = this.items.get(id);
|
WeakReference<V> ref = this.items.get(id);
|
||||||
return (ref != null) ? ref.get() : null;
|
if (ref != null) {
|
||||||
|
return ref.get();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,6 +45,6 @@ public class TestGenerics2 extends IntegrationTest {
|
|||||||
assertThat(code, containsString("public ItemReference(V item, Object id, ReferenceQueue<? super V> queue) {"));
|
assertThat(code, containsString("public ItemReference(V item, Object id, ReferenceQueue<? super V> queue) {"));
|
||||||
assertThat(code, containsString("public V get(Object id) {"));
|
assertThat(code, containsString("public V get(Object id) {"));
|
||||||
assertThat(code, containsString("WeakReference<V> ref = "));
|
assertThat(code, containsString("WeakReference<V> ref = "));
|
||||||
assertThat(code, containsString("return ref != null ? ref.get() : null;"));
|
assertThat(code, containsString("return ref.get();"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
package jadx.tests.integration.generics;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
public class TestGenericsInArgs extends IntegrationTest {
|
||||||
|
|
||||||
|
public static class TestCls {
|
||||||
|
public static <T> void test(List<? super T> genericList, Set<T> set) {
|
||||||
|
if (genericList == null) {
|
||||||
|
throw new RuntimeException("list is null");
|
||||||
|
}
|
||||||
|
if (set == null) {
|
||||||
|
throw new RuntimeException("set is null");
|
||||||
|
}
|
||||||
|
genericList.clear();
|
||||||
|
use(genericList);
|
||||||
|
set.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void use(List l) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
ClassNode cls = getClassNode(TestCls.class);
|
||||||
|
String code = cls.getCode().toString();
|
||||||
|
|
||||||
|
assertThat(code, containsString("public static <T> void test(List<? super T> genericList, Set<T> set) {"));
|
||||||
|
assertThat(code, containsString("if (genericList == null) {"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoDebug() {
|
||||||
|
noDebugInfo();
|
||||||
|
ClassNode cls = getClassNode(TestCls.class);
|
||||||
|
String code = cls.getCode().toString();
|
||||||
|
|
||||||
|
assertThat(code, containsString("public static <T> void test(List<? super T> list, Set<T> set) {"));
|
||||||
|
assertThat(code, containsString("if (list == null) {"));
|
||||||
|
}
|
||||||
|
}
|
@ -40,4 +40,10 @@ public class TestIterableForEach3 extends IntegrationTest {
|
|||||||
assertThat(code, containsOne("if (str.length() == 0) {"));
|
assertThat(code, containsOne("if (str.length() == 0) {"));
|
||||||
// TODO move return outside 'if'
|
// TODO move return outside 'if'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoDebug() {
|
||||||
|
noDebugInfo();
|
||||||
|
getClassNode(TestCls.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,4 +48,15 @@ public class TestSequentialLoops2 extends IntegrationTest {
|
|||||||
assertThat(code, containsOne("return c"));
|
assertThat(code, containsOne("return c"));
|
||||||
assertThat(code, countString(2, "<= 127"));
|
assertThat(code, countString(2, "<= 127"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoDebug() {
|
||||||
|
noDebugInfo();
|
||||||
|
ClassNode cls = getClassNode(TestCls.class);
|
||||||
|
String code = cls.getCode().toString();
|
||||||
|
|
||||||
|
assertThat(code, countString(2, "while ("));
|
||||||
|
assertThat(code, containsString("break;"));
|
||||||
|
assertThat(code, countString(2, "<= 127"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ import jadx.tests.api.IntegrationTest;
|
|||||||
|
|
||||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
@ -34,6 +36,10 @@ public class TestSwitchBreak extends IntegrationTest {
|
|||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void check() {
|
||||||
|
assertThat(test(9), is("1--4--1--4--1-"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -43,7 +49,8 @@ public class TestSwitchBreak extends IntegrationTest {
|
|||||||
|
|
||||||
assertThat(code, containsString("switch (a % 4) {"));
|
assertThat(code, containsString("switch (a % 4) {"));
|
||||||
assertEquals(4, count(code, "case "));
|
assertEquals(4, count(code, "case "));
|
||||||
assertEquals(3, count(code, "break;"));
|
assertEquals(2, count(code, "break;"));
|
||||||
|
assertThat(code, not(containsString("default:")));
|
||||||
|
|
||||||
// TODO finish break with label from switch
|
// TODO finish break with label from switch
|
||||||
assertThat(code, containsOne("return s + \"+\";"));
|
assertThat(code, containsOne("return s + \"+\";"));
|
||||||
|
@ -44,6 +44,14 @@ public class TestTryCatch6 extends IntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void test() {
|
||||||
|
ClassNode cls = getClassNode(TestCls.class);
|
||||||
|
String code = cls.getCode().toString();
|
||||||
|
|
||||||
|
assertThat(code, containsOne("try {"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoDebug() {
|
||||||
noDebugInfo();
|
noDebugInfo();
|
||||||
ClassNode cls = getClassNode(TestCls.class);
|
ClassNode cls = getClassNode(TestCls.class);
|
||||||
String code = cls.getCode().toString();
|
String code = cls.getCode().toString();
|
||||||
|
@ -29,8 +29,8 @@ public class TestTryCatch7 extends IntegrationTest {
|
|||||||
ClassNode cls = getClassNode(TestCls.class);
|
ClassNode cls = getClassNode(TestCls.class);
|
||||||
String code = cls.getCode().toString();
|
String code = cls.getCode().toString();
|
||||||
|
|
||||||
String excVarName = "e";
|
String excVarName = "exception";
|
||||||
String catchExcVarName = "e2";
|
String catchExcVarName = "e";
|
||||||
assertThat(code, containsOne("Exception " + excVarName + " = new Exception();"));
|
assertThat(code, containsOne("Exception " + excVarName + " = new Exception();"));
|
||||||
assertThat(code, containsOne("} catch (Exception " + catchExcVarName + ") {"));
|
assertThat(code, containsOne("} catch (Exception " + catchExcVarName + ") {"));
|
||||||
assertThat(code, containsOne(excVarName + " = " + catchExcVarName + ";"));
|
assertThat(code, containsOne(excVarName + " = " + catchExcVarName + ";"));
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
package jadx.tests.integration.types;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.tests.api.SmaliTest;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
|
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() {
|
||||||
|
disableCompilation();
|
||||||
|
|
||||||
|
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")));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package jadx.tests.integration.variables;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.tests.api.SmaliTest;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
public class TestVariablesGeneric extends SmaliTest {
|
||||||
|
/*
|
||||||
|
public static <T> j a(i<? super T> iVar, c<T> cVar) {
|
||||||
|
if (iVar == null) {
|
||||||
|
throw new IllegalArgumentException("subscriber can not be null");
|
||||||
|
}
|
||||||
|
if (cVar.a == null) {
|
||||||
|
throw new IllegalStateException("onSubscribe function can not be null.");
|
||||||
|
}
|
||||||
|
...
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
disableCompilation();
|
||||||
|
ClassNode cls = getClassNodeFromSmaliWithPath("variables", "TestVariablesGeneric");
|
||||||
|
String code = cls.getCode().toString();
|
||||||
|
|
||||||
|
assertThat(code, not(containsString("iVar2")));
|
||||||
|
assertThat(code, containsString("public static <T> j a(i<? super T> iVar, c<T> cVar) {"));
|
||||||
|
assertThat(code, containsString("if (iVar == null) {"));
|
||||||
|
}
|
||||||
|
}
|
176
jadx-core/src/test/smali/types/TestTypeResolver5.smali
Normal file
176
jadx-core/src/test/smali/types/TestTypeResolver5.smali
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
.class public LTestTypeResolver5;
|
||||||
|
.super Lcom/souq/app/activity/BaseContentActivity;
|
||||||
|
.source "SourceFile"
|
||||||
|
|
||||||
|
|
||||||
|
# static fields
|
||||||
|
.field public static final EXTERNAL_SOURCE:Ljava/lang/String; = "externalsource"
|
||||||
|
|
||||||
|
.field public static final IS_APPBOY_CAMPAIGN:Ljava/lang/String; = "appBoyCampaign"
|
||||||
|
|
||||||
|
.field public static final IS_NEWS_FEED:Ljava/lang/String; = "isNewsFeed"
|
||||||
|
|
||||||
|
|
||||||
|
# direct methods
|
||||||
|
.method public constructor <init>()V
|
||||||
|
.locals 0
|
||||||
|
|
||||||
|
.prologue
|
||||||
|
.line 35
|
||||||
|
invoke-direct {p0}, Lcom/souq/app/activity/BaseContentActivity;-><init>()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private openNextScreen(Landroid/os/Bundle;)V
|
||||||
|
.locals 3
|
||||||
|
|
||||||
|
.prologue
|
||||||
|
const/4 v1, 0x0
|
||||||
|
|
||||||
|
.line 56
|
||||||
|
if-eqz p1, :cond_2
|
||||||
|
|
||||||
|
const-string v0, "externalsource"
|
||||||
|
|
||||||
|
invoke-virtual {p1, v0}, Landroid/os/Bundle;->containsKey(Ljava/lang/String;)Z
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
if-eqz v0, :cond_2
|
||||||
|
|
||||||
|
const-string v0, "externalsource"
|
||||||
|
|
||||||
|
.line 57
|
||||||
|
invoke-virtual {p1, v0}, Landroid/os/Bundle;->getString(Ljava/lang/String;)Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
move-object v2, v0
|
||||||
|
|
||||||
|
.line 58
|
||||||
|
:goto_0
|
||||||
|
if-eqz p1, :cond_3
|
||||||
|
|
||||||
|
const-string v0, "isNewsFeed"
|
||||||
|
|
||||||
|
invoke-virtual {p1, v0}, Landroid/os/Bundle;->containsKey(Ljava/lang/String;)Z
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
if-eqz v0, :cond_3
|
||||||
|
|
||||||
|
const-string v0, "isNewsFeed"
|
||||||
|
|
||||||
|
.line 59
|
||||||
|
invoke-virtual {p1, v0}, Landroid/os/Bundle;->getBoolean(Ljava/lang/String;)Z
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
.line 61
|
||||||
|
:goto_1
|
||||||
|
invoke-static {v2}, Landroid/text/TextUtils;->isEmpty(Ljava/lang/CharSequence;)Z
|
||||||
|
|
||||||
|
move-result v2
|
||||||
|
|
||||||
|
if-nez v2, :cond_0
|
||||||
|
|
||||||
|
const/4 v1, 0x1
|
||||||
|
|
||||||
|
.line 64
|
||||||
|
:cond_0
|
||||||
|
if-eqz p1, :cond_1
|
||||||
|
|
||||||
|
.line 65
|
||||||
|
new-instance v2, Landroid/webkit/WebView;
|
||||||
|
|
||||||
|
invoke-direct {v2, p0}, Landroid/webkit/WebView;-><init>(Landroid/content/Context;)V
|
||||||
|
|
||||||
|
.line 66
|
||||||
|
invoke-direct {p0, v2, p1}, LTestTypeResolver5;->runJavaScriptForCampaign(Landroid/webkit/WebView;Landroid/os/Bundle;)V
|
||||||
|
|
||||||
|
.line 70
|
||||||
|
:cond_1
|
||||||
|
if-eqz v0, :cond_4
|
||||||
|
|
||||||
|
.line 72
|
||||||
|
invoke-direct {p0, p1}, LTestTypeResolver5;->startHomeActivity(Landroid/os/Bundle;)V
|
||||||
|
|
||||||
|
.line 73
|
||||||
|
invoke-virtual {p0}, LTestTypeResolver5;->finish()V
|
||||||
|
|
||||||
|
.line 80
|
||||||
|
:goto_2
|
||||||
|
return-void
|
||||||
|
|
||||||
|
.line 57
|
||||||
|
:cond_2
|
||||||
|
const-string v0, ""
|
||||||
|
|
||||||
|
move-object v2, v0
|
||||||
|
|
||||||
|
goto :goto_0
|
||||||
|
|
||||||
|
:cond_3
|
||||||
|
move v0, v1
|
||||||
|
|
||||||
|
.line 59
|
||||||
|
goto :goto_1
|
||||||
|
|
||||||
|
.line 74
|
||||||
|
:cond_4
|
||||||
|
invoke-virtual {p0}, LTestTypeResolver5;->isTaskRoot()Z
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
if-nez v0, :cond_5
|
||||||
|
|
||||||
|
if-eqz v1, :cond_6
|
||||||
|
|
||||||
|
.line 76
|
||||||
|
:cond_5
|
||||||
|
invoke-direct {p0, p1}, LTestTypeResolver5;->openSplash(Landroid/os/Bundle;)V
|
||||||
|
|
||||||
|
goto :goto_2
|
||||||
|
|
||||||
|
.line 78
|
||||||
|
:cond_6
|
||||||
|
invoke-virtual {p0}, LTestTypeResolver5;->finish()V
|
||||||
|
|
||||||
|
goto :goto_2
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private openSplash(Landroid/os/Bundle;)V
|
||||||
|
.locals 1
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private runJavaScriptForCampaign(Landroid/webkit/WebView;Landroid/os/Bundle;)V
|
||||||
|
.locals 1
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method private startHomeActivity(Landroid/os/Bundle;)V
|
||||||
|
.locals 1
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public onBackPressed()V
|
||||||
|
.locals 1
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public onCreate(Landroid/os/Bundle;)V
|
||||||
|
.locals 1
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method protected onPause()V
|
||||||
|
.locals 1
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method protected onStart()V
|
||||||
|
.locals 1
|
||||||
|
return-void
|
||||||
|
.end method
|
159
jadx-core/src/test/smali/variables/TestVariablesGeneric.smali
Normal file
159
jadx-core/src/test/smali/variables/TestVariablesGeneric.smali
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
.class public LTestVariablesGeneric;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
.source "SourceFile"
|
||||||
|
|
||||||
|
.method public static a(Lrx/i;Lrx/c;)Lrx/j;
|
||||||
|
.locals 3
|
||||||
|
.annotation system Ldalvik/annotation/Signature;
|
||||||
|
value = {
|
||||||
|
"<T:",
|
||||||
|
"Ljava/lang/Object;",
|
||||||
|
">(",
|
||||||
|
"Lrx/i<",
|
||||||
|
"-TT;>;",
|
||||||
|
"Lrx/c<",
|
||||||
|
"TT;>;)",
|
||||||
|
"Lrx/j;"
|
||||||
|
}
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
if-nez p0, :cond_0
|
||||||
|
|
||||||
|
.line 10325
|
||||||
|
new-instance p0, Ljava/lang/IllegalArgumentException;
|
||||||
|
|
||||||
|
const-string p1, "subscriber can not be null"
|
||||||
|
|
||||||
|
invoke-direct {p0, p1}, Ljava/lang/IllegalArgumentException;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
throw p0
|
||||||
|
|
||||||
|
.line 10327
|
||||||
|
:cond_0
|
||||||
|
iget-object v0, p1, Lrx/c;->a:Lrx/c$a;
|
||||||
|
|
||||||
|
if-nez v0, :cond_1
|
||||||
|
|
||||||
|
.line 10328
|
||||||
|
new-instance p0, Ljava/lang/IllegalStateException;
|
||||||
|
|
||||||
|
const-string p1, "onSubscribe function can not be null."
|
||||||
|
|
||||||
|
invoke-direct {p0, p1}, Ljava/lang/IllegalStateException;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
throw p0
|
||||||
|
|
||||||
|
.line 10336
|
||||||
|
:cond_1
|
||||||
|
invoke-virtual {p0}, Lrx/i;->onStart()V
|
||||||
|
|
||||||
|
.line 10343
|
||||||
|
instance-of v0, p0, Lrx/c/c;
|
||||||
|
|
||||||
|
if-nez v0, :cond_2
|
||||||
|
|
||||||
|
.line 10345
|
||||||
|
new-instance v0, Lrx/c/c;
|
||||||
|
|
||||||
|
invoke-direct {v0, p0}, Lrx/c/c;-><init>(Lrx/i;)V
|
||||||
|
|
||||||
|
move-object p0, v0
|
||||||
|
|
||||||
|
.line 10352
|
||||||
|
:cond_2
|
||||||
|
:try_start_0
|
||||||
|
iget-object v0, p1, Lrx/c;->a:Lrx/c$a;
|
||||||
|
|
||||||
|
invoke-static {p1, v0}, Lrx/d/c;->a(Lrx/c;Lrx/c$a;)Lrx/c$a;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
invoke-interface {p1, p0}, Lrx/c$a;->call(Ljava/lang/Object;)V
|
||||||
|
|
||||||
|
.line 10353
|
||||||
|
invoke-static {p0}, Lrx/d/c;->a(Lrx/j;)Lrx/j;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
:try_end_0
|
||||||
|
.catch Ljava/lang/Throwable; {:try_start_0 .. :try_end_0} :catch_0
|
||||||
|
|
||||||
|
return-object p1
|
||||||
|
|
||||||
|
:catch_0
|
||||||
|
move-exception p1
|
||||||
|
|
||||||
|
.line 10356
|
||||||
|
invoke-static {p1}, Lrx/exceptions/a;->b(Ljava/lang/Throwable;)V
|
||||||
|
|
||||||
|
.line 10358
|
||||||
|
invoke-virtual {p0}, Lrx/i;->isUnsubscribed()Z
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
if-eqz v0, :cond_3
|
||||||
|
|
||||||
|
.line 10359
|
||||||
|
invoke-static {p1}, Lrx/d/c;->b(Ljava/lang/Throwable;)Ljava/lang/Throwable;
|
||||||
|
|
||||||
|
move-result-object p0
|
||||||
|
|
||||||
|
invoke-static {p0}, Lrx/d/c;->a(Ljava/lang/Throwable;)V
|
||||||
|
|
||||||
|
goto :goto_0
|
||||||
|
|
||||||
|
.line 10363
|
||||||
|
:cond_3
|
||||||
|
:try_start_1
|
||||||
|
invoke-static {p1}, Lrx/d/c;->b(Ljava/lang/Throwable;)Ljava/lang/Throwable;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
invoke-virtual {p0, v0}, Lrx/i;->onError(Ljava/lang/Throwable;)V
|
||||||
|
:try_end_1
|
||||||
|
.catch Ljava/lang/Throwable; {:try_start_1 .. :try_end_1} :catch_1
|
||||||
|
|
||||||
|
.line 10375
|
||||||
|
:goto_0
|
||||||
|
invoke-static {}, Lrx/f/e;->b()Lrx/j;
|
||||||
|
|
||||||
|
move-result-object p0
|
||||||
|
|
||||||
|
return-object p0
|
||||||
|
|
||||||
|
:catch_1
|
||||||
|
move-exception p0
|
||||||
|
|
||||||
|
.line 10365
|
||||||
|
invoke-static {p0}, Lrx/exceptions/a;->b(Ljava/lang/Throwable;)V
|
||||||
|
|
||||||
|
.line 10368
|
||||||
|
new-instance v0, Lrx/exceptions/OnErrorFailedException;
|
||||||
|
|
||||||
|
new-instance v1, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v2, "Error occurred attempting to subscribe ["
|
||||||
|
|
||||||
|
invoke-direct {v1, v2}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Ljava/lang/Throwable;->getMessage()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
invoke-virtual {v1, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string p1, "] and then again while trying to pass to onError."
|
||||||
|
|
||||||
|
invoke-virtual {v1, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
invoke-direct {v0, p1, p0}, Lrx/exceptions/OnErrorFailedException;-><init>(Ljava/lang/String;Ljava/lang/Throwable;)V
|
||||||
|
|
||||||
|
.line 10370
|
||||||
|
invoke-static {v0}, Lrx/d/c;->b(Ljava/lang/Throwable;)Ljava/lang/Throwable;
|
||||||
|
|
||||||
|
.line 10372
|
||||||
|
throw v0
|
||||||
|
.end method
|
Loading…
Reference in New Issue
Block a user