fix: implement new type inference approach

This commit is contained in:
Skylot 2018-08-05 23:29:49 +03:00
parent 6d59f77165
commit 21e11c1d47
94 changed files with 3072 additions and 1551 deletions

1
.gitignore vendored
View File

@ -29,3 +29,4 @@ jadx-output/
*.dump
*.log
*.cfg
*.orig

View File

@ -296,6 +296,10 @@ public final class JadxDecompiler {
return root;
}
List<IDexTreeVisitor> getPasses() {
return passes;
}
synchronized BinaryXMLParser getXmlParser() {
if (xmlParser == null) {
xmlParser = new BinaryXMLParser(root);

View File

@ -13,7 +13,7 @@ import jadx.api.JadxArgs;
import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.CodeShrinker;
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.DotGraphVisitor;
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.BlockProcessor;
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.IfRegionVisitor;
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.ssa.EliminatePhiNodes;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
import jadx.core.dex.visitors.typeinference.TypeInference;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
public class Jadx {
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
@ -59,29 +60,27 @@ public class Jadx {
if (args.isFallbackMode()) {
passes.add(new FallbackModeVisitor());
} else {
passes.add(new DebugInfoParseVisitor());
passes.add(new BlockSplitter());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
passes.add(new BlockProcessor());
passes.add(new BlockExceptionHandler());
passes.add(new BlockFinallyExtract());
passes.add(new BlockFinish());
passes.add(new SSATransform());
passes.add(new DebugInfoVisitor());
passes.add(new TypeInference());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
passes.add(new ConstructorVisitor());
passes.add(new ConstInlineVisitor());
passes.add(new FinishTypeInference());
passes.add(new TypeInferenceVisitor());
passes.add(new EliminatePhiNodes());
passes.add(new DebugInfoApplyVisitor());
passes.add(new ModVisitor());
passes.add(new CodeShrinker());
passes.add(new ReSugarCode());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dump());
}

View File

@ -23,7 +23,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
public class ClspGraph {
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 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) {
String rawName = cls.getRawName();
NClass nClass = new NClass(rawName, -1);

View File

@ -1,7 +1,6 @@
package jadx.core.codegen;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
@ -484,19 +483,7 @@ public class InsnGen {
case FILL_ARRAY:
fallbackOnlyInsn(insn);
FillArrayNode arrayNode = (FillArrayNode) insn;
Object data = arrayNode.getData();
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 = "?";
}
String arrStr = arrayNode.dataToString();
code.add('{').add(arrStr.substring(1, arrStr.length() - 1)).add('}');
break;

View File

@ -124,6 +124,7 @@ public class MethodGen {
int i = 0;
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext(); ) {
RegisterArg arg = it.next();
ArgType argType = arg.getInitType();
// add argument annotation
if (paramsAnnotation != null) {
@ -135,17 +136,16 @@ public class MethodGen {
}
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
// change last array argument to varargs
ArgType type = arg.getType();
if (type.isArray()) {
ArgType elType = type.getArrayElement();
if (argType.isArray()) {
ArgType elType = argType.getArrayElement();
classGen.useType(argsCode, elType);
argsCode.add("...");
} else {
LOG.warn(ErrorsCounter.formatMsg(mth, "Last argument in varargs method not array"));
classGen.useType(argsCode, arg.getType());
classGen.useType(argsCode, argType);
}
} else {
classGen.useType(argsCode, arg.getType());
classGen.useType(argsCode, argType);
}
argsCode.add(' ');
argsCode.add(nameGen.assignArg(arg));

View File

@ -45,6 +45,7 @@ public class NameGen {
OBJ_ALIAS.put("java.lang.Float", "f");
OBJ_ALIAS.put("java.lang.Long", "l");
OBJ_ALIAS.put("java.lang.Double", "d");
OBJ_ALIAS.put("java.lang.StringBuilder", "sb");
}
public NameGen(MethodNode mth, boolean fallback) {
@ -108,13 +109,22 @@ public class NameGen {
String name = arg.getName();
String varName = name != null ? name : guessName(arg);
if (NameMapper.isReserved(varName)) {
return varName + "R";
varName = varName + "R";
}
if (!NameMapper.isValidIdentifier(varName)) {
varName = getFallbackName(arg);
}
return varName;
}
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) {
@ -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) {
@ -236,6 +250,9 @@ public class NameGen {
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
}
if (name.startsWith("to")) {
return fromName(name.substring(2));
}
return name;
}
}

View File

@ -47,9 +47,16 @@ public class TypeGen {
if (type == null || !type.isTypeKnown()) {
String n = Long.toString(lit);
if (Math.abs(lit) > 100) {
n += "; // 0x" + Long.toHexString(lit)
+ " float:" + Float.intBitsToFloat((int) lit)
+ " double:" + Double.longBitsToDouble(lit);
StringBuilder sb = new StringBuilder();
sb.append(n).append("(0x").append(Long.toHexString(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;
}

View File

@ -12,10 +12,12 @@ import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.JadxWarn;
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.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
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.nodes.parser.FieldInitAttr;
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<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() {
}
}

View File

@ -42,6 +42,12 @@ public abstract class AttrNode implements IAttributeNode {
return store;
}
private void unloadIfEmpty() {
if (storage.isEmpty() && storage != EMPTY_ATTR_STORAGE) {
storage = EMPTY_ATTR_STORAGE;
}
}
@Override
public boolean contains(AFlag flag) {
return storage.contains(flag);
@ -70,21 +76,25 @@ public abstract class AttrNode implements IAttributeNode {
@Override
public void remove(AFlag flag) {
storage.remove(flag);
unloadIfEmpty();
}
@Override
public <T extends IAttribute> void remove(AType<T> type) {
storage.remove(type);
unloadIfEmpty();
}
@Override
public void removeAttr(IAttribute attr) {
storage.remove(attr);
unloadIfEmpty();
}
@Override
public void clearAttributes() {
storage.clear();
unloadIfEmpty();
}
@Override

View File

@ -121,6 +121,6 @@ public class AttributeStorage {
if (list.isEmpty()) {
return "";
}
return "A:{" + Utils.listToString(list) + "}";
return "A[" + Utils.listToString(list) + "]";
}
}

View File

@ -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 ");
}
}

View File

@ -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 + ")";
}
}

View File

@ -1,6 +1,7 @@
package jadx.core.dex.instructions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
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.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
@ -20,7 +20,7 @@ public final class FillArrayNode extends InsnNode {
private ArgType elemType;
public FillArrayNode(int resReg, FillArrayDataPayloadDecodedInstruction payload) {
super(InsnType.FILL_ARRAY, 0);
super(InsnType.FILL_ARRAY, 1);
ArgType elType;
switch (payload.getElementWidthUnit()) {
case 1:
@ -39,7 +39,7 @@ public final class FillArrayNode extends InsnNode {
default:
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.size = payload.getSize();
@ -58,34 +58,27 @@ public final class FillArrayNode extends InsnNode {
return elemType;
}
public void mergeElementType(DexNode dex, ArgType foundElemType) {
ArgType r = ArgType.merge(dex, elemType, foundElemType);
if (r != null) {
elemType = r;
}
}
public List<LiteralArg> getLiteralArgs() {
public List<LiteralArg> getLiteralArgs(ArgType type) {
List<LiteralArg> list = new ArrayList<>(size);
Object array = data;
if (array instanceof int[]) {
for (int b : (int[]) array) {
list.add(InsnArg.lit(b, elemType));
list.add(InsnArg.lit(b, type));
}
} else if (array instanceof byte[]) {
for (byte b : (byte[]) array) {
list.add(InsnArg.lit(b, elemType));
list.add(InsnArg.lit(b, type));
}
} else if (array instanceof short[]) {
for (short b : (short[]) array) {
list.add(InsnArg.lit(b, elemType));
list.add(InsnArg.lit(b, type));
}
} else if (array instanceof long[]) {
for (long b : (long[]) array) {
list.add(InsnArg.lit(b, elemType));
list.add(InsnArg.lit(b, type));
}
} else {
throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + elemType);
throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + type);
}
return list;
}
@ -101,4 +94,25 @@ public final class FillArrayNode extends InsnNode {
FillArrayNode other = (FillArrayNode) obj;
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();
}
}

View File

@ -16,21 +16,21 @@ import static jadx.core.utils.BlockUtils.selectOther;
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;
private BlockNode thenBlock;
private BlockNode elseBlock;
public IfNode(DecodedInstruction insn, IfOp op) {
this(op, insn.getTarget(),
InsnArg.reg(insn, 0, ARG_TYPE),
insn.getRegisterCount() == 1 ? InsnArg.lit(0, ARG_TYPE) : InsnArg.reg(insn, 1, ARG_TYPE));
super(InsnType.IF, insn.getTarget(), 2);
this.op = op;
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) {
@ -40,6 +40,22 @@ public class IfNode extends GotoNode {
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() {
return op;
}

View File

@ -18,9 +18,11 @@ import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
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.PrimitiveType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InsnUtils;
@ -101,15 +103,15 @@ public class InsnDecoder {
case Opcodes.CONST_4:
case Opcodes.CONST_16:
case Opcodes.CONST_HIGH16:
return insn(InsnType.CONST, InsnArg.reg(insn, 0, ArgType.NARROW),
InsnArg.lit(insn, ArgType.NARROW));
LiteralArg narrowLitArg = InsnArg.lit(insn, ArgType.NARROW);
return insn(InsnType.CONST, InsnArg.reg(insn, 0, narrowLitArg.getType()), narrowLitArg);
case Opcodes.CONST_WIDE:
case Opcodes.CONST_WIDE_16:
case Opcodes.CONST_WIDE_32:
case Opcodes.CONST_WIDE_HIGH16:
return insn(InsnType.CONST, InsnArg.reg(insn, 0, ArgType.WIDE),
InsnArg.lit(insn, ArgType.WIDE));
LiteralArg wideLitArg = InsnArg.lit(insn, ArgType.WIDE);
return insn(InsnType.CONST, InsnArg.reg(insn, 0, wideLitArg.getType()), wideLitArg);
case Opcodes.CONST_STRING:
case Opcodes.CONST_STRING_JUMBO:
@ -442,7 +444,7 @@ public class InsnDecoder {
case Opcodes.IGET_OBJECT:
FieldInfo igetFld = FieldInfo.fromDex(dex, insn.getIndex());
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()));
return igetInsn;
@ -455,7 +457,7 @@ public class InsnDecoder {
case Opcodes.IPUT_OBJECT:
FieldInfo iputFld = FieldInfo.fromDex(dex, insn.getIndex());
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()));
return iputInsn;
@ -468,7 +470,7 @@ public class InsnDecoder {
case Opcodes.SGET_OBJECT:
FieldInfo sgetFld = FieldInfo.fromDex(dex, insn.getIndex());
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;
case Opcodes.SPUT:
@ -480,7 +482,7 @@ public class InsnDecoder {
case Opcodes.SPUT_OBJECT:
FieldInfo sputFld = FieldInfo.fromDex(dex, insn.getIndex());
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;
case Opcodes.ARRAY_LENGTH:
@ -490,7 +492,7 @@ public class InsnDecoder {
return arrLenInsn;
case Opcodes.AGET:
return arrayGet(insn, ArgType.NARROW);
return arrayGet(insn, ArgType.INT_FLOAT);
case Opcodes.AGET_BOOLEAN:
return arrayGet(insn, ArgType.BOOLEAN);
case Opcodes.AGET_BYTE:
@ -505,7 +507,7 @@ public class InsnDecoder {
return arrayGet(insn, ArgType.UNKNOWN_OBJECT);
case Opcodes.APUT:
return arrayPut(insn, ArgType.NARROW);
return arrayPut(insn, ArgType.INT_FLOAT);
case Opcodes.APUT_BOOLEAN:
return arrayPut(insn, ArgType.BOOLEAN);
case Opcodes.APUT_BYTE:
@ -544,14 +546,16 @@ public class InsnDecoder {
return invoke(insn, offset, InvokeType.VIRTUAL, true);
case Opcodes.NEW_INSTANCE:
return insn(InsnType.NEW_INSTANCE,
InsnArg.reg(insn, 0, dex.getType(insn.getIndex())));
ArgType clsType = 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:
ArgType arrType = dex.getType(insn.getIndex());
return new NewArrayNode(arrType,
InsnArg.reg(insn, 0, arrType),
InsnArg.reg(insn, 1, ArgType.INT));
InsnArg.typeImmutableReg(insn, 1, ArgType.INT));
case Opcodes.FILL_ARRAY_DATA:
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) {
int payloadOffset = insn.getTarget();
DecodedInstruction payload = insnArr[payloadOffset];
@ -666,17 +678,17 @@ public class InsnDecoder {
private InsnNode arrayGet(DecodedInstruction insn, ArgType argType) {
InsnNode inode = new InsnNode(InsnType.AGET, 2);
inode.setResult(InsnArg.reg(insn, 0, argType));
inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType)));
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
return inode;
}
private InsnNode arrayPut(DecodedInstruction insn, ArgType argType) {
InsnNode inode = new InsnNode(InsnType.APUT, 3);
inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
inode.addArg(InsnArg.reg(insn, 0, argType));
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType)));
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
return inode;
}

View File

@ -1,18 +1,16 @@
package jadx.core.dex.instructions.args;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.core.Consts;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.nodes.parser.SignatureParser;
import jadx.core.utils.Utils;
public abstract class ArgType {
public static final ArgType INT = primitive(PrimitiveType.INT);
public static final ArgType BOOLEAN = primitive(PrimitiveType.BOOLEAN);
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 ENUM = object(Consts.CLASS_ENUM);
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_OBJECT = unknown(PrimitiveType.OBJECT, PrimitiveType.ARRAY);
@ -38,11 +37,20 @@ public abstract class ArgType {
PrimitiveType.OBJECT, PrimitiveType.ARRAY);
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.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 INT_FLOAT = unknown(PrimitiveType.INT, PrimitiveType.FLOAT);
protected int hash;
private static ArgType primitive(PrimitiveType stype) {
@ -174,6 +182,8 @@ public abstract class ArgType {
}
private static final class GenericType extends ObjectType {
private List<ArgType> extendTypes;
public GenericType(String obj) {
super(obj);
}
@ -182,6 +192,16 @@ public abstract class ArgType {
public boolean isGenericType() {
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 {
@ -275,7 +295,7 @@ public abstract class ArgType {
@Override
public String toString() {
return super.toString() + "<" + Utils.arrayToString(generics) + ">";
return super.toString() + "<" + Utils.arrayToStr(generics) + ">";
}
}
@ -329,8 +349,9 @@ public abstract class ArgType {
}
@Override
boolean internalEquals(Object obj) {
return arrayElement.equals(((ArrayArg) obj).arrayElement);
boolean internalEquals(Object other) {
ArrayArg otherArr = (ArrayArg) other;
return this.arrayElement.equals(otherArr.getArrayElement());
}
@Override
@ -369,14 +390,13 @@ public abstract class ArgType {
@Override
public ArgType selectFirst() {
PrimitiveType f = possibleTypes[0];
if (contains(PrimitiveType.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
@ -389,7 +409,7 @@ public abstract class ArgType {
if (possibleTypes.length == PrimitiveType.values().length) {
return "?";
} else {
return "?" + Arrays.toString(possibleTypes);
return "?[" + Utils.arrayToStr(possibleTypes) + "]";
}
}
}
@ -426,6 +446,13 @@ public abstract class ArgType {
return null;
}
public List<ArgType> getExtendTypes() {
return Collections.emptyList();
}
public void setExtendTypes(List<ArgType> extendTypes) {
}
public ArgType getWildcardType() {
return null;
}
@ -463,110 +490,6 @@ public abstract class ArgType {
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) {
if (from.equals(to)) {
return false;
@ -578,14 +501,49 @@ public abstract class ArgType {
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)) {
return true;
}
if (!type.isObject() || !of.isObject()) {
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) {

View File

@ -31,10 +31,21 @@ public abstract class InsnArg extends Typed {
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) {
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) {
return typeImmutable ? new TypeImmutableArg(regNum, type) : new RegisterArg(regNum, type);
}

View File

@ -17,9 +17,10 @@ public final class LiteralArg extends InsnArg {
} else if (!type.isTypeKnown()
&& !type.contains(PrimitiveType.LONG)
&& !type.contains(PrimitiveType.DOUBLE)) {
ArgType m = ArgType.merge(null, type, ArgType.NARROW_NUMBERS);
if (m != null) {
type = m;
if (value != 1) {
type = ArgType.NARROW_NUMBERS_NO_BOOL;
} else {
type = ArgType.NARROW_NUMBERS;
}
}
}

View File

@ -11,7 +11,6 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
public class RegisterArg extends InsnArg implements Named {
public static final String THIS_ARG_NAME = "this";
protected final int regNum;
@ -36,6 +35,38 @@ public class RegisterArg extends InsnArg implements Named {
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() {
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() {
return duplicate(getRegNum(), sVar);
}
public RegisterArg duplicate(int regNum, SSAVar sVar) {
RegisterArg dup = new RegisterArg(regNum, getType());
RegisterArg dup = new RegisterArg(regNum, getInitType());
if (sVar != null) {
dup.setSVar(sVar);
}
@ -140,6 +159,10 @@ public class RegisterArg extends InsnArg implements Named {
return null;
}
public boolean equalRegister(RegisterArg arg) {
return regNum == arg.regNum;
}
public boolean equalRegisterAndType(RegisterArg arg) {
return regNum == arg.regNum && type.equals(arg.type);
}
@ -159,7 +182,7 @@ public class RegisterArg extends InsnArg implements Named {
}
RegisterArg other = (RegisterArg) obj;
return regNum == other.regNum
&& type.equals(other.type)
&& Objects.equals(getType(), other.getType())
&& Objects.equals(sVar, other.getSVar());
}
@ -169,13 +192,17 @@ public class RegisterArg extends InsnArg implements Named {
sb.append("(r");
sb.append(regNum);
if (sVar != null) {
sb.append("_").append(sVar.getVersion());
sb.append(':').append(sVar.getVersion());
}
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()) {
sb.append(' ').append(getAttributesString());
}

View File

@ -8,15 +8,11 @@ import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.visitors.typeinference.TypeInfo;
public class SSAVar extends AttrNode {
private final int regNum;
private final int version;
private VarName varName;
private int startUseAddr;
private int endUseAddr;
@NotNull
private RegisterArg assign;
@ -24,8 +20,8 @@ public class SSAVar extends AttrNode {
@Nullable
private PhiInsn usedInPhi;
private ArgType type;
private boolean typeImmutable;
private TypeInfo typeInfo = new TypeInfo();
private VarName varName;
public SSAVar(int regNum, int v, @NotNull RegisterArg assign) {
this.regNum = regNum;
@ -33,48 +29,6 @@ public class SSAVar extends AttrNode {
this.assign = assign;
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() {
@ -139,30 +93,6 @@ public class SSAVar extends AttrNode {
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) {
if (name != null) {
if (varName == null) {
@ -187,6 +117,14 @@ public class SSAVar extends AttrNode {
this.varName = varName;
}
public TypeInfo getTypeInfo() {
return typeInfo;
}
public void setTypeInfo(TypeInfo typeInfo) {
this.typeInfo = typeInfo;
}
@Override
public boolean equals(Object o) {
if (this == o) {
@ -206,6 +144,6 @@ public class SSAVar extends AttrNode {
@Override
public String toString() {
return "r" + regNum + "_" + version;
return "r" + regNum + ":" + version + " " + typeInfo.getType();
}
}

View File

@ -1,6 +1,8 @@
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 {
@ -15,12 +17,11 @@ public class TypeImmutableArg extends RegisterArg {
@Override
public void setType(ArgType type) {
// not allowed
// allow set only initial type
if (Objects.equals(this.type, type)) {
super.setType(type);
} else {
throw new JadxRuntimeException("Can't change arg with immutable type");
}
@Override
void setSVar(@NotNull SSAVar sVar) {
sVar.setTypeImmutable(type);
super.setSVar(sVar);
}
}

View File

@ -1,7 +1,6 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.nodes.DexNode;
public abstract class Typed extends AttrNode {
@ -18,17 +17,4 @@ public abstract class Typed extends AttrNode {
public boolean isTypeImmutable() {
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());
}
}

View File

@ -45,11 +45,10 @@ public class ConstructorInsn extends InsnNode {
instanceArg.getSVar().setAssign(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));
}
offset = invoke.getOffset();
setSourceLine(invoke.getSourceLine());
}
public ConstructorInsn(MethodInfo callMth, CallType callType, RegisterArg instanceArg) {

View File

@ -15,10 +15,6 @@ public final class TernaryInsn extends InsnNode {
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) {
super(InsnType.TERNARY, 2);
setResult(result);

View File

@ -87,6 +87,10 @@ public class DexNode implements IDexNode {
@Nullable
public ClassNode resolveClass(ClassInfo clsInfo) {
ClassNode classNode = resolveClassLocal(clsInfo);
if (classNode != null) {
return classNode;
}
return root.resolveClass(clsInfo);
}
@ -98,7 +102,6 @@ public class DexNode implements IDexNode {
return resolveClass(ClassInfo.fromType(root, type));
}
@Deprecated
@Nullable
public MethodNode resolveMethod(@NotNull MethodInfo mth) {
ClassNode cls = resolveClass(mth.getDeclClass());
@ -138,7 +141,6 @@ public class DexNode implements IDexNode {
return null;
}
@Deprecated
@Nullable
public FieldNode resolveField(FieldInfo field) {
ClassNode cls = resolveClass(field.getDeclClass());
@ -184,10 +186,16 @@ public class DexNode implements IDexNode {
// DexBuffer wrappers
public String getString(int index) {
if (index == DexNode.NO_INDEX) {
return null;
}
return dexBuf.strings().get(index);
}
public ArgType getType(int index) {
if (index == DexNode.NO_INDEX) {
return null;
}
return ArgType.parse(getString(dexBuf.typeIds().get(index)));
}

View File

@ -14,7 +14,7 @@ public class FieldNode extends LineAttrNode {
private final FieldInfo fieldInfo;
private final AccessInfo accFlags;
private ArgType type; // store signature
private ArgType type;
public FieldNode(ClassNode cls, Field field) {
this(cls, FieldInfo.fromDex(cls.dex(), field.getFieldIndex()),

View File

@ -19,6 +19,7 @@ import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.InstructionRemover;
import jadx.core.utils.Utils;
public class InsnNode extends LineAttrNode {
@ -110,6 +111,7 @@ public class InsnNode extends LineAttrNode {
for (int i = 0; i < count; i++) {
InsnArg arg = arguments.get(i);
if (arg == from) {
InstructionRemover.unbindArgUsage(null, arg);
setArg(i, to);
return true;
}
@ -125,10 +127,7 @@ public class InsnNode extends LineAttrNode {
for (int i = 0; i < count; i++) {
if (arg == arguments.get(i)) {
arguments.remove(i);
if (arg instanceof RegisterArg) {
RegisterArg reg = (RegisterArg) arg;
reg.getSVar().removeUse(reg);
}
InstructionRemover.unbindArgUsage(null, arg);
return true;
}
}

View File

@ -54,17 +54,18 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
private final AccessInfo accFlags;
private final Method methodData;
private final boolean methodIsVirtual;
private boolean noCode;
private int regsCount;
private InsnNode[] instructions;
private int codeSize;
private int debugInfoOffset;
private boolean noCode;
private boolean methodIsVirtual;
private ArgType retType;
private RegisterArg thisArg;
private List<RegisterArg> argsList;
private List<SSAVar> sVars = Collections.emptyList();
private List<SSAVar> sVars;
private Map<ArgType, List<ArgType>> genericMap;
private List<BlockNode> blocks;
@ -72,8 +73,8 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
private List<BlockNode> exitBlocks;
private Region region;
private List<ExceptionHandler> exceptionHandlers = Collections.emptyList();
private List<LoopInfo> loops = Collections.emptyList();
private List<ExceptionHandler> exceptionHandlers;
private List<LoopInfo> loops;
public MethodNode(ClassNode classNode, Method mthData, boolean isVirtual) {
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.methodData = noCode ? null : mthData;
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
@ -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() {
SignatureParser sp = SignatureParser.fromNode(this);
if (sp == null) {
@ -535,7 +541,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
&& !parentClass.getAccessFlags().isStatic()) {
ClassNode outerCls = parentClass.getParentClass();
if (argsList != null && !argsList.isEmpty()
&& argsList.get(0).getType().equals(outerCls.getClassInfo().getType())) {
&& argsList.get(0).getInitType().equals(outerCls.getClassInfo().getType())) {
defaultArgCount = 1;
}
}
@ -611,8 +617,13 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
return "method";
}
public void addWarn(String errStr) {
ErrorsCounter.methodWarn(this, errStr);
public void addWarn(String warnStr) {
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) {

View File

@ -18,6 +18,7 @@ import jadx.core.dex.info.ConstStorage;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.InfoStorage;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.visitors.typeinference.TypeUpdate;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.StringUtils;
import jadx.core.utils.android.AndroidResourcesUtils;
@ -36,6 +37,7 @@ public class RootNode {
private final StringUtils stringUtils;
private final ConstStorage constValues;
private final InfoStorage infoStorage = new InfoStorage();
private final TypeUpdate typeUpdate;
private ClspGraph clsp;
private List<DexNode> dexNodes;
@ -48,6 +50,7 @@ public class RootNode {
this.args = args;
this.stringUtils = new StringUtils(args);
this.constValues = new ConstStorage(args);
this.typeUpdate = new TypeUpdate(this);
}
public void load(List<InputFile> inputFiles) {
@ -224,4 +227,8 @@ public class RootNode {
public JadxArgs getArgs() {
return args;
}
public TypeUpdate getTypeUpdate() {
return typeUpdate;
}
}

View File

@ -64,7 +64,9 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
public List<IContainer> getBranches() {
List<IContainer> branches = new ArrayList<>(cases.size() + 1);
branches.addAll(cases);
if (defCase != null) {
branches.add(defCase);
}
return Collections.unmodifiableList(branches);
}

View File

@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.List;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
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.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.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.exceptions.JadxException;
@JadxVisitor(
name = "Constants Inline",
desc = "Inline constant registers into instructions",
runAfter = SSATransform.class,
runBefore = TypeInferenceVisitor.class
)
public class ConstInlineVisitor extends AbstractVisitor {
@Override
@ -63,11 +68,6 @@ public class ConstInlineVisitor extends AbstractVisitor {
}
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);
}
@ -106,7 +106,8 @@ public class ConstInlineVisitor extends AbstractVisitor {
InsnNode useInsn = arg.getParentInsn();
if (useInsn == null
|| useInsn.getType() == InsnType.PHI
|| useInsn.getType() == InsnType.MERGE) {
|| useInsn.getType() == InsnType.MERGE
) {
continue;
}
LiteralArg litArg;
@ -127,7 +128,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
litArg = InsnArg.lit(literal, ArgType.UNKNOWN);
}
if (useInsn.replaceArg(arg, litArg)) {
fixTypes(mth, useInsn, litArg);
litArg.setType(arg.getInitType());
replaceCount++;
if (useInsn.getType() == InsnType.RETURN) {
useInsn.setSourceLine(constInsn.getSourceLine());
@ -147,96 +148,4 @@ public class ConstInlineVisitor extends AbstractVisitor {
}
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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}
}

View File

@ -1,6 +1,5 @@
package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@ -9,8 +8,6 @@ import java.util.Map;
import org.slf4j.Logger;
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.AType;
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.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.NewArrayNode;
import jadx.core.dex.instructions.SwitchNode;
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.exceptions.JadxRuntimeException;
import static jadx.core.utils.BlockUtils.replaceInsn;
/**
* Visitor for modify method instructions
* (remove, replace, process exception handlers)
@ -67,8 +65,6 @@ public class ModVisitor extends AbstractVisitor {
InstructionRemover remover = new InstructionRemover(mth);
replaceStep(mth, remover);
removeStep(mth, remover);
checkArgsNames(mth);
}
private static void replaceStep(MethodNode mth, InstructionRemover remover) {
@ -79,8 +75,8 @@ public class ModVisitor extends AbstractVisitor {
for (int i = 0; i < size; i++) {
InsnNode insn = block.getInstructions().get(i);
switch (insn.getType()) {
case INVOKE:
processInvoke(mth, block, i, remover);
case CONSTRUCTOR:
processAnonymousConstructor(mth, ((ConstructorInsn) insn));
break;
case CONST:
@ -115,22 +111,18 @@ public class ModVisitor extends AbstractVisitor {
break;
case NEW_ARRAY:
// create array in 'fill-array' instruction
// replace with filled array if 'fill-array' is next instruction
int next = i + 1;
if (next < size) {
InsnNode ni = block.getInstructions().get(next);
if (ni.getType() == InsnType.FILL_ARRAY) {
ni.getResult().merge(mth.dex(), insn.getResult());
ArgType arrType = ((NewArrayNode) insn).getArrayType();
((FillArrayNode) ni).mergeElementType(mth.dex(), arrType.getArrayElement());
remover.add(insn);
}
}
break;
case FILL_ARRAY:
InsnNode filledArr = makeFilledArrayInsn(mth, (FillArrayNode) insn);
InsnNode filledArr = makeFilledArrayInsn(mth, (NewArrayNode) insn, (FillArrayNode) ni);
if (filledArr != null) {
replaceInsn(block, i, filledArr);
remover.add(ni);
}
}
}
break;
case MOVE_EXCEPTION:
@ -151,6 +143,18 @@ public class ModVisitor extends AbstractVisitor {
}
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:
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
*/
@ -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) {
MethodInfo callMth = co.getCallMth();
MethodNode callMthNode = mth.dex().resolveMethod(callMth);
@ -331,33 +296,8 @@ public class ModVisitor extends AbstractVisitor {
return parentInsn;
}
/**
* 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;
}
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();
private static InsnNode makeFilledArrayInsn(MethodNode mth, NewArrayNode newArrayNode, FillArrayNode insn) {
ArgType insnArrayType = newArrayNode.getArrayType();
ArgType insnElementType = insnArrayType.getArrayElement();
ArgType elType = insn.getElementType();
if (!elType.isTypeKnown()
@ -378,12 +318,10 @@ public class ModVisitor extends AbstractVisitor {
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());
filledArr.setResult(insn.getResult());
filledArr.setResult(newArrayNode.getResult());
for (LiteralArg arg : list) {
FieldNode f = mth.getParentClass().getConstFieldByLiteralArg(arg);
if (f != null) {
@ -396,39 +334,6 @@ public class ModVisitor extends AbstractVisitor {
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) {
ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER);
if (excHandlerAttr == null) {
@ -457,25 +362,4 @@ public class ModVisitor extends AbstractVisitor {
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);
}
}
}
}

View File

@ -176,7 +176,8 @@ public class SimplifyVisitor extends AbstractVisitor {
if (constrIndex != -1) { // If we found a CONSTRUCTOR, is it a StringBuilder?
ConstructorInsn constr = (ConstructorInsn) chain.get(constrIndex);
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 argInsn;
if (constrIndex > 0) { // There was an arg to the StringBuilder constr
@ -282,14 +283,13 @@ public class SimplifyVisitor extends AbstractVisitor {
if (wrapType == InsnType.ARITH) {
ArithNode ar = (ArithNode) wrap;
return new ArithNode(ar.getOp(), fArg, ar.getArg(1));
} else {
}
int argsCount = wrap.getArgsCount();
InsnNode concat = new InsnNode(InsnType.STR_CONCAT, argsCount - 1);
for (int i = 1; i < argsCount; i++) {
concat.addArg(wrap.getArg(i));
}
return new ArithNode(ArithOp.ADD, fArg, InsnArg.wrapArg(concat));
}
} catch (Exception e) {
LOG.debug("Can't convert field arith insn: {}, mth: {}", insn, mth, e);
}

View File

@ -507,7 +507,7 @@ public class BlockFinallyExtract extends AbstractVisitor {
RegisterArg mapReg = removeInfo.getRegMap().get(remArg);
if (mapReg == null) {
removeInfo.getRegMap().put(remReg, fReg);
} else if (!mapReg.equalRegisterAndType(fReg)) {
} else if (!mapReg.equalRegister(fReg)) {
return false;
}
}

View File

@ -3,6 +3,7 @@ package jadx.core.dex.visitors.blocksmaker;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.slf4j.Logger;
@ -61,17 +62,27 @@ public class BlockProcessor extends AbstractVisitor {
markReturnBlocks(mth);
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);
registerLoops(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) {
return block.getInstructions().isEmpty()
&& !block.isSynthetic()
&& block.isAttrStorageEmpty()
&& block.getSuccessors().size() <= 1
&& !block.getPredecessors().isEmpty();
@ -112,6 +123,7 @@ public class BlockProcessor extends AbstractVisitor {
if (lastInsn != null && lastInsn.getType() == InsnType.IF) {
return false;
}
// TODO: implement insn extraction into separate block for partial predecessors
int sameInsnCount = getSameLastInsnCount(predecessors);
if (sameInsnCount > 0) {
List<InsnNode> insns = getLastInsns(predecessors.get(0), sameInsnCount);
@ -187,10 +199,20 @@ public class BlockProcessor extends AbstractVisitor {
}
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().set(entryBlock.getId());
BitSet dset = new BitSet(nBlocks);
BitSet domSet = new BitSet(basicBlocks.size());
boolean changed;
do {
changed = false;
@ -200,25 +222,21 @@ public class BlockProcessor extends AbstractVisitor {
}
BitSet d = block.getDoms();
if (!changed) {
dset.clear();
dset.or(d);
domSet.clear();
domSet.or(d);
}
for (BlockNode pred : block.getPredecessors()) {
d.and(pred.getDoms());
}
d.set(block.getId());
if (!changed && !d.equals(dset)) {
if (!changed && !d.equals(domSet)) {
changed = true;
}
}
} while (changed);
}
markLoops(mth);
// clear self dominance
basicBlocks.forEach(block -> block.getDoms().clear(block.getId()));
// calculate immediate dominators
private static void calcImmediateDominators(List<BlockNode> basicBlocks, BlockNode entryBlock) {
for (BlockNode block : basicBlocks) {
if (block == entryBlock) {
continue;
@ -305,7 +323,7 @@ public class BlockProcessor extends AbstractVisitor {
private static void markLoops(MethodNode mth) {
mth.getBasicBlocks().forEach(block -> {
// 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 -> {
if (block.getDoms().get(successor.getId())) {
successor.add(AFlag.LOOP_START);
@ -354,14 +372,7 @@ public class BlockProcessor extends AbstractVisitor {
}
private static boolean modifyBlocksTree(MethodNode mth) {
List<BlockNode> basicBlocks = mth.getBasicBlocks();
for (BlockNode block : basicBlocks) {
if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) {
throw new JadxRuntimeException("Unreachable block: " + block);
}
}
for (BlockNode block : basicBlocks) {
for (BlockNode block : mth.getBasicBlocks()) {
if (checkLoops(mth, block)) {
return true;
}
@ -389,9 +400,78 @@ public class BlockProcessor extends AbstractVisitor {
}
private static boolean checkLoops(MethodNode mth, BlockNode block) {
// check loops
List<LoopInfo> loops = block.getAll(AType.LOOP);
if (loops.size() > 1) {
int loopsCount = loops.size();
if (loopsCount == 0) {
return false;
}
if (loopsCount > 1 && splitLoops(mth, block, loops)) {
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;
}
}
}
return change;
}
/**
* Insert additional blocks for possible 'continue' insertion
*/
private static boolean insertBlocksForContinue(MethodNode mth, LoopInfo loop) {
BlockNode loopEnd = loop.getEnd();
boolean change = false;
List<BlockNode> preds = loopEnd.getPredecessors();
if (preds.size() > 1) {
for (BlockNode pred : new ArrayList<>(preds)) {
if (!pred.contains(AFlag.SYNTHETIC)) {
BlockSplitter.insertBlockBetween(mth, pred, loopEnd);
change = true;
}
}
}
return change;
}
/**
* Insert additional block if loop header has several predecessors (exclude back edges)
*/
private static boolean insertBlockForProdecessors(MethodNode mth, LoopInfo loop) {
BlockNode loopHeader = loop.getStart();
List<BlockNode> preds = loopHeader.getPredecessors();
if (preds.size() > 2) {
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) {
@ -409,39 +489,6 @@ public class BlockProcessor extends AbstractVisitor {
}
return true;
}
}
if (loops.size() == 1) {
LoopInfo loop = loops.get(0);
// insert additional blocks for possible 'break' insertion
List<Edge> edges = loop.getExitEdges();
if (!edges.isEmpty()) {
boolean change = false;
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 (change) {
return true;
}
}
// insert additional blocks for possible 'continue' insertion
BlockNode loopEnd = loop.getEnd();
if (loopEnd.getPredecessors().size() > 1) {
boolean change = false;
List<BlockNode> nodes = new ArrayList<>(loopEnd.getPredecessors());
for (BlockNode pred : nodes) {
if (!pred.contains(AFlag.SYNTHETIC)) {
BlockSplitter.insertBlockBetween(mth, pred, loopEnd);
change = true;
}
}
return change;
}
}
return false;
}
@ -518,7 +565,7 @@ public class BlockProcessor extends AbstractVisitor {
InsnNode insn = new InsnNode(returnInsn.getType(), returnInsn.getArgsCount());
if (returnInsn.getArgsCount() == 1) {
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.setOffset(returnInsn.getOffset());

View File

@ -47,6 +47,9 @@ public class BlockSplitter extends AbstractVisitor {
removeInsns(mth);
removeEmptyDetachedBlocks(mth);
initBlocksInTargetNodes(mth);
removeJumpAttributes(mth.getInstructions());
mth.unloadInsnArr();
}
/**
@ -304,4 +307,12 @@ public class BlockSplitter extends AbstractVisitor {
&& block.getSuccessors().isEmpty()
);
}
private void removeJumpAttributes(InsnNode[] insnArr) {
for (InsnNode insn : insnArr) {
if (insn != null && insn.contains(AType.JUMP)) {
insn.remove(AType.JUMP);
}
}
}
}

View File

@ -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);
}
});
}
}

View File

@ -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;
}
}
}
}

View File

@ -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 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.instructions.args.InsnArg;
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.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.DecodeException;
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_ADVANCE_PC = 0x01;
private static final int DBG_ADVANCE_LINE = 0x02;
@ -39,9 +39,10 @@ public class DebugInfoParser {
private final DexNode dex;
private final LocalVar[] locals;
private final InsnArg[] activeRegisters;
private final InsnNode[] insnByOffset;
private List<LocalVar> resultList;
public DebugInfoParser(MethodNode mth, int debugOffset, InsnNode[] insnByOffset) {
this.mth = mth;
this.dex = mth.dex();
@ -49,38 +50,37 @@ public class DebugInfoParser {
int regsCount = mth.getRegsCount();
this.locals = new LocalVar[regsCount];
this.activeRegisters = new InsnArg[regsCount];
this.insnByOffset = insnByOffset;
}
public void process() throws DecodeException {
public List<LocalVar> process() throws DecodeException {
boolean varsInfoFound = false;
resultList = new ArrayList<>();
int addr = 0;
int line = section.readUleb128();
int paramsCount = section.readUleb128();
List<RegisterArg> mthArgs = mth.getArguments(false);
for (int i = 0; i < paramsCount; i++) {
int id = section.readUleb128() - 1;
if (id != DexNode.NO_INDEX) {
String name = dex.getString(id);
if (i < mthArgs.size()) {
mthArgs.get(i).setName(name);
}
}
}
for (RegisterArg arg : mthArgs) {
int rn = arg.getRegNum();
locals[rn] = new LocalVar(arg);
activeRegisters[rn] = arg;
for (int i = 0; i < paramsCount; i++) {
int nameId = section.readUleb128() - 1;
if (nameId != DexNode.NO_INDEX) {
String name = dex.getString(nameId);
if (i < mthArgs.size() && name != null) {
RegisterArg arg = mthArgs.get(i);
int regNum = arg.getRegNum();
LocalVar lVar = new LocalVar(regNum, name, arg.getInitType());
startVar(lVar, -1);
varsInfoFound = true;
}
}
}
// process '0' instruction
addrChange(-1, 1, line);
setLine(addr, line);
boolean varsInfoFound = false;
int c = section.readByte() & 0xFF;
while (c != DBG_END_SEQUENCE) {
switch (c) {
@ -100,7 +100,7 @@ public class DebugInfoParser {
int nameId = section.readUleb128() - 1;
int type = section.readUleb128() - 1;
LocalVar var = new LocalVar(dex, regNum, nameId, type, DexNode.NO_INDEX);
startVar(var, addr, line);
startVar(var, addr);
varsInfoFound = true;
break;
}
@ -110,19 +110,13 @@ public class DebugInfoParser {
int type = section.readUleb128() - 1;
int sign = section.readUleb128() - 1;
LocalVar var = new LocalVar(dex, regNum, nameId, type, sign);
startVar(var, addr, line);
startVar(var, addr);
varsInfoFound = true;
break;
}
case DBG_RESTART_LOCAL: {
int regNum = section.readUleb128();
LocalVar var = locals[regNum];
if (var != null) {
if (var.end(addr, line)) {
setVar(var);
}
var.start(addr, line);
}
restartVar(regNum, addr);
varsInfoFound = true;
break;
}
@ -130,8 +124,7 @@ public class DebugInfoParser {
int regNum = section.readUleb128();
LocalVar var = locals[regNum];
if (var != null) {
var.end(addr, line);
setVar(var);
endVar(var, addr);
}
varsInfoFound = true;
break;
@ -153,10 +146,10 @@ public class DebugInfoParser {
default: {
if (c >= DBG_FIRST_SPECIAL) {
int adjustedOpcode = c - DBG_FIRST_SPECIAL;
int addrInc = adjustedOpcode / DBG_LINE_RANGE;
int adjustedOpCode = c - DBG_FIRST_SPECIAL;
int addrInc = adjustedOpCode / DBG_LINE_RANGE;
addr = addrChange(addr, addrInc, line);
line += DBG_LINE_BASE + adjustedOpcode % DBG_LINE_RANGE;
line += DBG_LINE_BASE + adjustedOpCode % DBG_LINE_RANGE;
setLine(addr, line);
} else {
throw new DecodeException("Unknown debug insn code: " + c);
@ -170,33 +163,19 @@ public class DebugInfoParser {
if (varsInfoFound) {
for (LocalVar var : locals) {
if (var != null && !var.isEnd()) {
var.end(mth.getCodeSize() - 1, line);
setVar(var);
endVar(var, mth.getCodeSize() - 1);
}
}
}
setSourceLines(addr, insnByOffset.length, line);
return resultList;
}
private int addrChange(int addr, int addrInc, int line) {
int newAddr = addr + addrInc;
int maxAddr = insnByOffset.length - 1;
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);
return newAddr;
}
@ -214,81 +193,30 @@ public class DebugInfoParser {
}
}
private void startVar(LocalVar var, int addr, int line) {
int regNum = var.getRegNum();
private void restartVar(int regNum, int addr) {
LocalVar prev = locals[regNum];
if (prev != null && !prev.isEnd()) {
prev.end(addr, line);
setVar(prev);
}
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;
}
if (prev != null) {
endVar(prev, addr);
LocalVar newVar = new LocalVar(regNum, prev.getName(), prev.getType());
startVar(newVar, addr);
} else {
mergeRequired = true;
}
if (mergeRequired) {
applyDebugInfo(reg, var);
mth.addComment("Debug info: failed to restart local var, previous not found, register: " + regNum);
}
}
private static void applyDebugInfo(RegisterArg reg, LocalVar var) {
String varName = var.getName();
if (NameMapper.isValidIdentifier(varName)) {
reg.mergeDebugInfo(var.getType(), varName);
private void startVar(LocalVar newVar, int addr) {
int regNum = newVar.getRegNum();
LocalVar prev = locals[regNum];
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);
}
}
}

View File

@ -1,54 +1,48 @@
package jadx.core.dex.nodes.parser;
package jadx.core.dex.visitors.debuginfo;
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.DexNode;
import jadx.core.utils.InsnUtils;
final class LocalVar {
public final class LocalVar {
private static final Logger LOG = LoggerFactory.getLogger(LocalVar.class);
private final int regNum;
private String name;
private ArgType type;
private final String name;
private final ArgType type;
private boolean isEnd;
private int startAddr;
private int endAddr;
public LocalVar(DexNode dex, int rn, int nameId, int typeId, int signId) {
this.regNum = rn;
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);
this(rn, dex.getString(nameId), dex.getType(typeId), dex.getString(signId));
}
public LocalVar(RegisterArg arg) {
this.regNum = arg.getRegNum();
init(arg.getName(), arg.getType(), null);
public LocalVar(int regNum, String name, ArgType type) {
this(regNum, name, type, 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) {
try {
ArgType gType = ArgType.generic(sign);
if (checkSignature(type, sign, gType)) {
if (checkSignature(type, gType)) {
type = gType;
}
} catch (Exception e) {
LOG.error("Can't parse signature for local variable: {}", sign, e);
}
}
this.name = name;
this.type = type;
}
private boolean checkSignature(ArgType type, String sign, ArgType gType) {
private boolean checkSignature(ArgType type, ArgType gType) {
boolean apply;
ArgType el = gType.getArrayRootElement();
if (el.isGeneric()) {
@ -62,7 +56,7 @@ final class LocalVar {
return apply;
}
public void start(int addr, int line) {
public void start(int addr) {
this.isEnd = false;
this.startAddr = addr;
}
@ -71,17 +65,16 @@ final class LocalVar {
* Sets end address of local variable
*
* @param addr address
* @param line source line
* @return <b>true</b> if local variable was active, else <b>false</b>
*/
public boolean end(int addr, int line) {
if (!isEnd) {
public boolean end(int addr) {
if (isEnd) {
return false;
}
this.isEnd = true;
this.endAddr = addr;
return true;
}
return false;
}
public int getRegNum() {
return regNum;
@ -119,8 +112,8 @@ final class LocalVar {
@Override
public String toString() {
return super.toString() + " " + (isEnd
? "end: " + InsnUtils.formatOffset(startAddr) + "-" + InsnUtils.formatOffset(endAddr)
: "active: " + InsnUtils.formatOffset(startAddr));
return InsnUtils.formatOffset(startAddr)
+ "-" + (isEnd ? InsnUtils.formatOffset(endAddr) : " ")
+ ": r" + regNum + " '" + name + "' " + type;
}
}

View File

@ -60,17 +60,20 @@ public class DepthRegionTraversal {
}
}
private static boolean traverseIterativeStepInternal(MethodNode mth, IRegionIterativeVisitor visitor,
IContainer container) {
private static boolean traverseIterativeStepInternal(MethodNode mth, IRegionIterativeVisitor visitor, IContainer container) {
if (container instanceof IRegion) {
IRegion region = (IRegion) container;
if (visitor.visitRegion(mth, region)) {
return true;
}
for (IContainer subCont : region.getSubBlocks()) {
try {
if (traverseIterativeStepInternal(mth, visitor, subCont)) {
return true;
}
} catch (StackOverflowError overflow) {
throw new JadxOverflowException("Region traversal failed: Recursive call in traverseIterativeStepInternal method");
}
}
}
return false;

View File

@ -87,7 +87,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
PhiInsn phiInsn = incrArg.getSVar().getUsedInPhi();
if (phiInsn == null
|| phiInsn.getArgsCount() != 2
|| !phiInsn.getArg(1).equals(incrArg)
|| !phiInsn.containsArg(incrArg)
|| incrArg.getSVar().getUseCount() != 1) {
return false;
}
@ -289,13 +289,13 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
iterVar.setType(gType);
return true;
}
if (ArgType.isInstanceOf(mth.dex(), gType, varType)) {
if (ArgType.isInstanceOf(mth.root(), gType, varType)) {
return true;
}
ArgType wildcardType = gType.getWildcardType();
if (wildcardType != null
&& gType.getWildcardBounds() == 1
&& ArgType.isInstanceOf(mth.dex(), wildcardType, varType)) {
&& ArgType.isInstanceOf(mth.root(), wildcardType, varType)) {
return true;
}
LOG.warn("Generic type differs: '{}' and '{}' in {}", gType, varType, mth);

View File

@ -818,7 +818,10 @@ public class RegionMaker {
}
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()) {
BlockNode caseBlock = entry.getKey();

View File

@ -40,18 +40,18 @@ public class TernaryMod {
return false;
}
BlockNode header = ifRegion.getHeader();
InsnNode t = tb.getInstructions().get(0);
InsnNode e = eb.getInstructions().get(0);
InsnNode thenInsn = tb.getInstructions().get(0);
InsnNode elseInsn = eb.getInstructions().get(0);
if (t.getSourceLine() != e.getSourceLine()) {
if (t.getSourceLine() != 0 && e.getSourceLine() != 0) {
if (thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
if (thenInsn.getSourceLine() != 0 && elseInsn.getSourceLine() != 0) {
// sometimes source lines incorrect
if (!checkLineStats(t, e)) {
if (!checkLineStats(thenInsn, elseInsn)) {
return false;
}
} else {
// no debug info
if (containsTernary(t) || containsTernary(e)) {
if (containsTernary(thenInsn) || containsTernary(elseInsn)) {
// don't make nested ternary by default
// TODO: add addition checks
return false;
@ -59,27 +59,30 @@ public class TernaryMod {
}
}
if (t.getResult() != null && e.getResult() != null) {
PhiInsn phi = t.getResult().getSVar().getUsedInPhi();
if (phi == null || !t.getResult().equalRegisterAndType(e.getResult())) {
RegisterArg thenResArg = thenInsn.getResult();
RegisterArg elseResArg = elseInsn.getResult();
if (thenResArg != null && elseResArg != null) {
PhiInsn thenPhi = thenResArg.getSVar().getUsedInPhi();
PhiInsn elsePhi = elseResArg.getSVar().getUsedInPhi();
if (thenPhi == null || thenPhi != elsePhi) {
return false;
}
if (!ifRegion.getParent().replaceSubBlock(ifRegion, header)) {
return false;
}
InsnList.remove(tb, t);
InsnList.remove(eb, e);
InsnList.remove(tb, thenInsn);
InsnList.remove(eb, elseInsn);
RegisterArg resArg;
if (phi.getArgsCount() == 2) {
resArg = phi.getResult();
if (thenPhi.getArgsCount() == 2) {
resArg = thenPhi.getResult();
} else {
resArg = t.getResult();
phi.removeArg(e.getResult());
resArg = thenResArg;
thenPhi.removeArg(elseResArg);
}
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(),
resArg, InsnArg.wrapArg(t), InsnArg.wrapArg(e));
ternInsn.setSourceLine(t.getSourceLine());
resArg, InsnArg.wrapArg(thenInsn), InsnArg.wrapArg(elseInsn));
ternInsn.setSourceLine(thenInsn.getSourceLine());
// remove 'if' instruction
header.getInstructions().clear();
@ -91,18 +94,25 @@ public class TernaryMod {
}
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)) {
return false;
}
InsnList.remove(tb, t);
InsnList.remove(eb, e);
InsnList.remove(tb, thenInsn);
InsnList.remove(eb, elseInsn);
tb.remove(AFlag.RETURN);
eb.remove(AFlag.RETURN);
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(), null, t.getArg(0), e.getArg(0));
ternInsn.setSourceLine(t.getSourceLine());
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(), null, thenArg, elseArg);
ternInsn.setSourceLine(thenInsn.getSourceLine());
InsnNode retInsn = new InsnNode(InsnType.RETURN, 1);
retInsn.addArg(InsnArg.wrapArg(ternInsn));

View File

@ -1,5 +1,6 @@
package jadx.core.dex.visitors.ssa;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@ -106,12 +107,12 @@ public class EliminatePhiNodes extends AbstractVisitor {
RegisterArg newAssignArg = oldArg.duplicate(newRegNum, null);
SSAVar newSVar = mth.makeNewSVar(newRegNum, mth.getNextSVarVersion(newRegNum), newAssignArg);
newSVar.setName(oldSVar.getName());
newSVar.setType(assignArg.getType());
mth.root().getTypeUpdate().apply(newSVar, assignArg.getType());
if (assignParentInsn != null) {
assignParentInsn.setResult(newAssignArg);
}
for (RegisterArg useArg : oldSVar.getUseList()) {
for (RegisterArg useArg : new ArrayList<>(oldSVar.getUseList())) {
RegisterArg newUseArg = useArg.duplicate(newRegNum, newSVar);
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn != null) {

View File

@ -220,7 +220,7 @@ public class SSATransform extends AbstractVisitor {
if (parentInsn != null
&& parentInsn.getResult() != null
&& parentInsn.contains(AFlag.TRY_LEAVE)
&& phi.removeArg(arg)) {
&& phi.removeArg(arg) /* TODO: fix registers removing*/) {
argsCount--;
continue;
}

View File

@ -0,0 +1,6 @@
package jadx.core.dex.visitors.typeinference;
public enum BoundEnum {
ASSIGN,
USE
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}
}

View File

@ -0,0 +1,9 @@
package jadx.core.dex.visitors.typeinference;
import jadx.core.dex.instructions.args.ArgType;
public interface ITypeBound {
BoundEnum getBound();
ArgType getType();
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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 + '}';
}
}

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 +
'}';
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,7 @@
package jadx.core.dex.visitors.typeinference;
public enum TypeUpdateResult {
REJECT,
SAME,
CHANGED
}

View File

@ -556,4 +556,38 @@ public class BlockUtils {
blocks.forEach(block -> insns.addAll(block.getInstructions()));
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;
}
}

View File

@ -44,6 +44,11 @@ public class DebugUtils {
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) {
File out = new File("test-graph-" + desc + "-tmp");
DotGraphVisitor.dump().save(out, mth);

View File

@ -89,7 +89,7 @@ public class InstructionRemover {
public static void unbindResult(MethodNode mth, InsnNode insn) {
RegisterArg r = insn.getResult();
if (r != null && r.getSVar() != null) {
if (r != null && r.getSVar() != null && mth != null) {
mth.removeSVar(r.getSVar());
}
}

View File

@ -57,16 +57,15 @@ public class Utils {
}
}
public static String arrayToString(Object[] array) {
if (array == null) {
public static <T> String arrayToStr(T[] arr) {
int len = arr == null ? 0 : arr.length;
if (len == 0) {
return "";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < array.length; i++) {
if (i != 0) {
sb.append(", ");
}
sb.append(array[i]);
sb.append(arr[0]);
for (int i = 1; i < len; i++) {
sb.append(", ").append(arr[i]);
}
return sb.toString();
}

View File

@ -1,10 +1,17 @@
package jadx.api;
import java.util.List;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
public class JadxInternalAccess {
public static RootNode getRoot(JadxDecompiler d) {
return d.getRoot();
}
public static List<IDexTreeVisitor> getPassList(JadxDecompiler d) {
return d.getPasses();
}
}

View File

@ -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));
}
}

View File

@ -53,7 +53,7 @@ public abstract class IntegrationTest extends TestUtils {
/**
* Run auto check method if defined:
* <pre>
* public static void check()
* public void check() {}
* </pre>
*/
public static final String CHECK_METHOD_NAME = "check";

View File

@ -2,7 +2,6 @@ package jadx.tests.external;
import java.io.File;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@ -14,7 +13,6 @@ import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.JadxInternalAccess;
import jadx.api.JavaClass;
import jadx.core.Jadx;
import jadx.core.codegen.CodeGen;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.nodes.ClassNode;
@ -57,9 +55,7 @@ public abstract class BaseExternalTest extends IntegrationTest {
processAll(jadx);
// jadx.saveSources();
} else {
Pattern clsPtrn = Pattern.compile(clsPatternStr);
Pattern mthPtrn = mthPatternStr == null ? null : Pattern.compile(mthPatternStr);
processByPatterns(jadx, clsPtrn, mthPtrn);
processByPatterns(jadx, clsPatternStr, mthPatternStr);
}
printErrorReport(jadx);
}
@ -70,13 +66,13 @@ public abstract class BaseExternalTest extends IntegrationTest {
}
}
private void processByPatterns(JadxDecompiler jadx, Pattern clsPattern, @Nullable Pattern mthPattern) {
List<IDexTreeVisitor> passes = Jadx.getPassesList(jadx.getArgs());
private void processByPatterns(JadxDecompiler jadx, String clsPattern, @Nullable String mthPattern) {
List<IDexTreeVisitor> passes = JadxInternalAccess.getPassList(jadx);
RootNode root = JadxInternalAccess.getRoot(jadx);
int processed = 0;
for (ClassNode classNode : root.getClasses(true)) {
String clsFullName = classNode.getClassInfo().getFullName();
if (clsPattern.matcher(clsFullName).matches()) {
if (isMatch(clsFullName, clsPattern)) {
if (processCls(mthPattern, passes, classNode)) {
processed++;
}
@ -85,14 +81,14 @@ public abstract class BaseExternalTest extends IntegrationTest {
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();
boolean decompile = false;
if (mthPattern == null) {
decompile = true;
} else {
for (MethodNode mth : classNode.getMethods()) {
if (mthPattern.matcher(mth.getName()).matches()) {
if (isMthMatch(mth, mthPattern)) {
decompile = true;
break;
}
@ -119,14 +115,26 @@ public abstract class BaseExternalTest extends IntegrationTest {
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();
if (code == null) {
return;
}
String[] lines = code.split(CodeWriter.NL);
for (MethodNode mth : classNode.getMethods()) {
if (mthPattern.matcher(mth.getName()).matches()) {
if (isMthMatch(mth, mthPattern)) {
int decompiledLine = mth.getDecompiledLine();
StringBuilder mthCode = new StringBuilder();
int brackets = 0;
@ -139,7 +147,7 @@ public abstract class BaseExternalTest extends IntegrationTest {
break;
}
}
LOG.info("\n{}", mthCode);
LOG.info("{}\n{}", mth.getMethodInfo().getShortId(), mthCode);
}
}
}

View File

@ -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;
}
}

View File

@ -7,6 +7,7 @@ import jadx.tests.api.IntegrationTest;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
public class TestFloatValue extends IntegrationTest {
@ -17,6 +18,10 @@ public class TestFloatValue extends IntegrationTest {
fa[0] /= 2;
return fa;
}
public void check() {
assertEquals(0.275f, method()[0], 0.0001f);
}
}
@Test

View File

@ -17,7 +17,10 @@ public class TestRedundantBrackets extends IntegrationTest {
}
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) {
@ -50,7 +53,8 @@ public class TestRedundantBrackets extends IntegrationTest {
assertThat(code, not(containsString("(-1)")));
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) != 0"));
assertThat(code, containsString("if (num == 4 || num == 6 || num == 8 || num == 10)"));

View File

@ -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);
}
}

View File

@ -39,5 +39,15 @@ public class TestArith3 extends IntegrationTest {
assertThat(code, containsOne("while (n + 4 < buffer.length) {"));
assertThat(code, containsOne("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 ("));
}
}

View File

@ -1,12 +1,14 @@
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;
public class TestArrays4 extends SmaliTest {
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.junit.Assert.assertThat;
public class TestArrays4 extends IntegrationTest {
public static class TestCls {
char[] payload;
@ -28,7 +30,6 @@ public class TestArrays4 extends SmaliTest {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsOne("char[] toChars = toChars(bArr);"));
assertThat(code, containsOne("char[] chars = toChars(bArr);"));
}
}

View File

@ -22,7 +22,7 @@ public class TestTernary extends IntegrationTest {
}
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, containsString("return a != 2;"));
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;"));
}
}

View File

@ -25,23 +25,23 @@ public class TestReturnSourceLine extends IntegrationTest {
return 0;
}
public int test2(int v) {
if (v == 0) {
f();
return v - 1;
}
f();
return v + 1;
}
public int test3(int v) {
if (v == 0) {
f();
return v;
}
f();
return v + 1;
}
// public int test2(int v) {
// if (v == 0) {
// f();
// return v - 1;
// }
// f();
// return v + 1;
// }
//
// public int test3(int v) {
// if (v == 0) {
// f();
// return v;
// }
// f();
// return v + 1;
// }
private void f() {
}
@ -57,8 +57,8 @@ public class TestReturnSourceLine extends IntegrationTest {
MethodNode test1 = cls.searchMethodByName("test1(Z)I");
checkLine(lines, codeWriter, test1, 3, "return 1;");
MethodNode test2 = cls.searchMethodByName("test2(I)I");
checkLine(lines, codeWriter, test2, 3, "return v - 1;");
// MethodNode test2 = cls.searchMethodByName("test2(I)I");
// checkLine(lines, codeWriter, test2, 3, "return v - 1;");
// TODO:
// MethodNode test3 = cls.searchMethodByName("test3(I)I");

View File

@ -3,10 +3,8 @@ package jadx.tests.integration.debuginfo;
import org.junit.Test;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import jadx.tests.api.SmaliTest;
import static jadx.tests.api.utils.JadxMatchers.containsLines;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.junit.Assert.assertThat;

View File

@ -29,7 +29,10 @@ public class TestGenerics2 extends IntegrationTest {
public V get(Object 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 V get(Object id) {"));
assertThat(code, containsString("WeakReference<V> ref = "));
assertThat(code, containsString("return ref != null ? ref.get() : null;"));
assertThat(code, containsString("return ref.get();"));
}
}

View File

@ -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) {"));
}
}

View File

@ -40,4 +40,10 @@ public class TestIterableForEach3 extends IntegrationTest {
assertThat(code, containsOne("if (str.length() == 0) {"));
// TODO move return outside 'if'
}
@Test
public void testNoDebug() {
noDebugInfo();
getClassNode(TestCls.class);
}
}

View File

@ -48,4 +48,15 @@ public class TestSequentialLoops2 extends IntegrationTest {
assertThat(code, containsOne("return c"));
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"));
}
}

View File

@ -7,6 +7,8 @@ import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
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.assertThat;
@ -34,6 +36,10 @@ public class TestSwitchBreak extends IntegrationTest {
}
return s;
}
public void check() {
assertThat(test(9), is("1--4--1--4--1-"));
}
}
@Test
@ -43,7 +49,8 @@ public class TestSwitchBreak extends IntegrationTest {
assertThat(code, containsString("switch (a % 4) {"));
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
assertThat(code, containsOne("return s + \"+\";"));

View File

@ -44,6 +44,14 @@ public class TestTryCatch6 extends IntegrationTest {
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsOne("try {"));
}
@Test
public void testNoDebug() {
noDebugInfo();
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();

View File

@ -29,8 +29,8 @@ public class TestTryCatch7 extends IntegrationTest {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
String excVarName = "e";
String catchExcVarName = "e2";
String excVarName = "exception";
String catchExcVarName = "e";
assertThat(code, containsOne("Exception " + excVarName + " = new Exception();"));
assertThat(code, containsOne("} catch (Exception " + catchExcVarName + ") {"));
assertThat(code, containsOne(excVarName + " = " + catchExcVarName + ";"));

View File

@ -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")));
}
}

View File

@ -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) {"));
}
}

View 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

View 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