diff --git a/.gitignore b/.gitignore index d8906bd22..71fea2b0e 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ jadx-output/ *.dump *.log *.cfg +*.orig diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index db84e02e7..1b92e8a72 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -296,6 +296,10 @@ public final class JadxDecompiler { return root; } + List getPasses() { + return passes; + } + synchronized BinaryXMLParser getXmlParser() { if (xmlParser == null) { xmlParser = new BinaryXMLParser(root); diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 37d27f9fe..f77f7471a 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -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()); } diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java b/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java index 6143e23c1..e18785f42 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java @@ -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> ancestorCache = Collections.synchronizedMap(new WeakHashMap>()); + private final Map> ancestorCache = Collections.synchronizedMap(new WeakHashMap<>()); private Map nameMap; private final Set 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); diff --git a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java index fc941a2f9..8af4385f8 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -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; diff --git a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java index 3c189154f..61ab3b013 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -124,6 +124,7 @@ public class MethodGen { int i = 0; for (Iterator 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)); diff --git a/jadx-core/src/main/java/jadx/core/codegen/NameGen.java b/jadx-core/src/main/java/jadx/core/codegen/NameGen.java index 8ae4c46fb..b29c8176b 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/NameGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/NameGen.java @@ -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; } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/TypeGen.java b/jadx-core/src/main/java/jadx/core/codegen/TypeGen.java index dfb9441d3..7aa3b7707 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/TypeGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/TypeGen.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java index 1c518d772..f2ca9ba9f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java @@ -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 { public static final AType LOOP_LABEL = new AType<>(); public static final AType IGNORE_EDGE = new AType<>(); + // method + public static final AType LOCAL_VARS_DEBUG_INFO = new AType<>(); + + // registers + public static final AType REG_DEBUG_INFO = new AType<>(); + private AType() { } } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java index f0385b491..3d18035db 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java @@ -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 void remove(AType type) { storage.remove(type); + unloadIfEmpty(); } @Override public void removeAttr(IAttribute attr) { storage.remove(attr); + unloadIfEmpty(); } @Override public void clearAttributes() { storage.clear(); + unloadIfEmpty(); } @Override diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeStorage.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeStorage.java index 39d10db28..259cbf7fe 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeStorage.java @@ -121,6 +121,6 @@ public class AttributeStorage { if (list.isEmpty()) { return ""; } - return "A:{" + Utils.listToString(list) + "}"; + return "A[" + Utils.listToString(list) + "]"; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LocalVarsDebugInfoAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LocalVarsDebugInfoAttr.java new file mode 100644 index 000000000..c4efe9235 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LocalVarsDebugInfoAttr.java @@ -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 localVars; + + public LocalVarsDebugInfoAttr(List localVars) { + this.localVars = localVars; + } + + public List getLocalVars() { + return localVars; + } + + @Override + public AType getType() { + return AType.LOCAL_VARS_DEBUG_INFO; + } + + @Override + public String toString() { + return "Debug Info:\n " + Utils.listToString(localVars, "\n "); + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RegDebugInfoAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RegDebugInfoAttr.java new file mode 100644 index 000000000..a82f5bb8f --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RegDebugInfoAttr.java @@ -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 + ")"; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayNode.java index 4d30de8d1..f3bafb2a1 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayNode.java @@ -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 getLiteralArgs() { + public List getLiteralArgs(ArgType type) { List 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(); + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/IfNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/IfNode.java index be953902e..b72b25794 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/IfNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/IfNode.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java index 21ceffeeb..fcc0a3127 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java index 701c18444..5e3de592f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java @@ -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 extendTypes; + public GenericType(String obj) { super(obj); } @@ -182,6 +192,16 @@ public abstract class ArgType { public boolean isGenericType() { return true; } + + @Override + public List getExtendTypes() { + return extendTypes; + } + + @Override + public void setExtendTypes(List 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 getExtendTypes() { + return Collections.emptyList(); + } + + public void setExtendTypes(List 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 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) { diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java index f9ac7a500..01275d645 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java @@ -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); } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/LiteralArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/LiteralArg.java index 291129447..728372920 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/LiteralArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/LiteralArg.java @@ -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; } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java index cbc2da87c..636a6c409 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java @@ -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()); } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java index 66cb6eeca..68f4d5362 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java @@ -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(); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/TypeImmutableArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/TypeImmutableArg.java index a8f757db0..5833f0ab8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/TypeImmutableArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/TypeImmutableArg.java @@ -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 - } - - @Override - void setSVar(@NotNull SSAVar sVar) { - sVar.setTypeImmutable(type); - super.setSVar(sVar); + // 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"); + } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/Typed.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/Typed.java index 125d9f540..46b4c007d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/Typed.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/Typed.java @@ -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()); - } } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/mods/ConstructorInsn.java b/jadx-core/src/main/java/jadx/core/dex/instructions/mods/ConstructorInsn.java index b4451df7c..0b3a4e80c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/mods/ConstructorInsn.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/mods/ConstructorInsn.java @@ -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) { diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/mods/TernaryInsn.java b/jadx-core/src/main/java/jadx/core/dex/instructions/mods/TernaryInsn.java index bbc69443f..f1b98ecb8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/mods/TernaryInsn.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/mods/TernaryInsn.java @@ -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); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java index ce0d5ea34..c5edd6444 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java @@ -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))); } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java index 4d3a49bd3..a11f55f56 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java @@ -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()), diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java index 0d8348dce..3c415b47e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java @@ -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; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index 50affbac3..21baaf2ed 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -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 argsList; - private List sVars = Collections.emptyList(); + private List sVars; private Map> genericMap; private List blocks; @@ -72,8 +73,8 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode { private List exitBlocks; private Region region; - private List exceptionHandlers = Collections.emptyList(); - private List loops = Collections.emptyList(); + private List exceptionHandlers; + private List 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) { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java index 8598ac872..e5e6f1d7e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java @@ -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 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 inputFiles) { @@ -224,4 +227,8 @@ public class RootNode { public JadxArgs getArgs() { return args; } + + public TypeUpdate getTypeUpdate() { + return typeUpdate; + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/regions/SwitchRegion.java b/jadx-core/src/main/java/jadx/core/dex/regions/SwitchRegion.java index 4bfb532e6..2ac0a44df 100644 --- a/jadx-core/src/main/java/jadx/core/dex/regions/SwitchRegion.java +++ b/jadx-core/src/main/java/jadx/core/dex/regions/SwitchRegion.java @@ -64,7 +64,9 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion public List getBranches() { List branches = new ArrayList<>(cases.size() + 1); branches.addAll(cases); - branches.add(defCase); + if (defCase != null) { + branches.add(defCase); + } return Collections.unmodifiableList(branches); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java index a82ba3909..121c243f5 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java @@ -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 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; - } - } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java new file mode 100644 index 000000000..462bf3bd9 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java @@ -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 = "(" + (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; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DebugInfoVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DebugInfoVisitor.java deleted file mode 100644 index bb8cdfe45..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DebugInfoVisitor.java +++ /dev/null @@ -1,80 +0,0 @@ -package jadx.core.dex.visitors; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.instructions.args.RegisterArg; -import jadx.core.dex.nodes.BlockNode; -import jadx.core.dex.nodes.InsnNode; -import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.nodes.parser.DebugInfoParser; -import jadx.core.utils.BlockUtils; -import jadx.core.utils.ErrorsCounter; -import jadx.core.utils.exceptions.DecodeException; -import jadx.core.utils.exceptions.JadxException; - -public class DebugInfoVisitor extends AbstractVisitor { - - private static final Logger LOG = LoggerFactory.getLogger(DebugInfoVisitor.class); - - @Override - public void visit(MethodNode mth) throws JadxException { - try { - int debugOffset = mth.getDebugInfoOffset(); - if (debugOffset > 0 && mth.dex().checkOffset(debugOffset)) { - processDebugInfo(mth, debugOffset); - } - } catch (Exception e) { - LOG.error("Error in debug info parser: {}", ErrorsCounter.formatMsg(mth, e.getMessage()), e); - } finally { - mth.unloadInsnArr(); - } - } - - private void processDebugInfo(MethodNode mth, int debugOffset) throws DecodeException { - InsnNode[] insnArr = mth.getInstructions(); - DebugInfoParser debugInfoParser = new DebugInfoParser(mth, debugOffset, insnArr); - debugInfoParser.process(); - - if (insnArr.length != 0) { - setMethodSourceLine(mth, insnArr); - } - if (!mth.getReturnType().equals(ArgType.VOID)) { - setLineForReturn(mth, insnArr); - } - } - - /** - * Fix debug info for splitter 'return' instructions - */ - private void setLineForReturn(MethodNode mth, InsnNode[] insnArr) { - for (BlockNode exit : mth.getExitBlocks()) { - InsnNode ret = BlockUtils.getLastInsn(exit); - if (ret != null) { - InsnNode oldRet = insnArr[ret.getOffset()]; - if (oldRet != ret) { - RegisterArg oldArg = (RegisterArg) oldRet.getArg(0); - RegisterArg newArg = (RegisterArg) ret.getArg(0); - newArg.mergeDebugInfo(oldArg.getType(), oldArg.getName()); - ret.setSourceLine(oldRet.getSourceLine()); - } - } - } - } - - /** - * Set method source line from first instruction - */ - private void setMethodSourceLine(MethodNode mth, InsnNode[] insnArr) { - for (InsnNode insn : insnArr) { - if (insn != null) { - int line = insn.getSourceLine(); - if (line != 0) { - mth.setSourceLine(line - 1); - } - return; - } - } - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java index 740ab5092..5198b8271 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java @@ -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,24 +111,20 @@ 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); + InsnNode filledArr = makeFilledArrayInsn(mth, (NewArrayNode) insn, (FillArrayNode) ni); + if (filledArr != null) { + replaceInsn(block, i, filledArr); + remover.add(ni); + } } } break; - case FILL_ARRAY: - InsnNode filledArr = makeFilledArrayInsn(mth, (FillArrayNode) insn); - replaceInsn(block, i, filledArr); - break; - case MOVE_EXCEPTION: processMoveException(block, insn, remover); break; @@ -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 = "(" + (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 list = insn.getLiteralArgs(); + List 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); - } - } - } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java index 49ab39228..fcab32313 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java @@ -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)); } + 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); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockFinallyExtract.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockFinallyExtract.java index 0bf879e04..9ed0cf3b7 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockFinallyExtract.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockFinallyExtract.java @@ -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; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java index da40d6792..97466c1da 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java @@ -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 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 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 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 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,59 +400,95 @@ public class BlockProcessor extends AbstractVisitor { } private static boolean checkLoops(MethodNode mth, BlockNode block) { - // check loops List loops = block.getAll(AType.LOOP); - if (loops.size() > 1) { - boolean oneHeader = true; - for (LoopInfo loop : loops) { - if (loop.getStart() != block) { - oneHeader = false; - break; + 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 edges = loop.getExitEdges(); + if (!edges.isEmpty()) { + for (Edge edge : edges) { + BlockNode target = edge.getTarget(); + BlockNode source = edge.getSource(); + if (!target.contains(AFlag.SYNTHETIC) && !source.contains(AFlag.SYNTHETIC)) { + BlockSplitter.insertBlockBetween(mth, source, target); + change = true; } } - if (oneHeader) { - // several back edges connected to one loop header => make additional block - BlockNode newLoopEnd = BlockSplitter.startNewBlock(mth, block.getStartOffset()); - newLoopEnd.add(AFlag.SYNTHETIC); - connect(newLoopEnd, block); - for (LoopInfo la : loops) { - BlockSplitter.replaceConnection(la.getEnd(), block, newLoopEnd); - } - return true; - } } - if (loops.size() == 1) { - LoopInfo loop = loops.get(0); - // insert additional blocks for possible 'break' insertion - List 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; + 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 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; } } - // insert additional blocks for possible 'continue' insertion - BlockNode loopEnd = loop.getEnd(); - if (loopEnd.getPredecessors().size() > 1) { - boolean change = false; - List nodes = new ArrayList<>(loopEnd.getPredecessors()); - for (BlockNode pred : nodes) { - if (!pred.contains(AFlag.SYNTHETIC)) { - BlockSplitter.insertBlockBetween(mth, pred, loopEnd); - change = true; - } - } - return change; + } + 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 preds = loopHeader.getPredecessors(); + if (preds.size() > 2) { + List 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 loops) { + boolean oneHeader = true; + for (LoopInfo loop : loops) { + if (loop.getStart() != block) { + oneHeader = false; + break; } } + if (oneHeader) { + // several back edges connected to one loop header => make additional block + BlockNode newLoopEnd = BlockSplitter.startNewBlock(mth, block.getStartOffset()); + newLoopEnd.add(AFlag.SYNTHETIC); + connect(newLoopEnd, block); + for (LoopInfo la : loops) { + BlockSplitter.replaceConnection(la.getEnd(), block, newLoopEnd); + } + return true; + } return false; } @@ -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()); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java index d800a0f47..4a7750c72 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java @@ -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); + } + } + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java new file mode 100644 index 000000000..806a449ee --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java @@ -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 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 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 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 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 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 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); + } + }); + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java new file mode 100644 index 000000000..0ae84ebde --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java @@ -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 localVars = debugInfoParser.process(); + attachDebugInfo(mth, localVars, insnArr); + setMethodSourceLine(mth, insnArr); + } + + private void attachDebugInfo(MethodNode mth, List 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; + } + } + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/DebugInfoParser.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParser.java similarity index 57% rename from jadx-core/src/main/java/jadx/core/dex/nodes/parser/DebugInfoParser.java rename to jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParser.java index 932e96e77..0b79fe710 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/DebugInfoParser.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParser.java @@ -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 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 process() throws DecodeException { + boolean varsInfoFound = false; + resultList = new ArrayList<>(); + int addr = 0; int line = section.readUleb128(); int paramsCount = section.readUleb128(); List 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); + 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; } } } - for (RegisterArg arg : mthArgs) { - int rn = arg.getRegNum(); - locals[rn] = new LocalVar(arg); - activeRegisters[rn] = arg; - } - // 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); } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/LocalVar.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/LocalVar.java similarity index 60% rename from jadx-core/src/main/java/jadx/core/dex/nodes/parser/LocalVar.java rename to jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/LocalVar.java index fe066e50b..5d5612ef9 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/LocalVar.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/LocalVar.java @@ -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,16 +65,15 @@ final class LocalVar { * Sets end address of local variable * * @param addr address - * @param line source line * @return true if local variable was active, else false */ - public boolean end(int addr, int line) { - if (!isEnd) { - this.isEnd = true; - this.endAddr = addr; - return true; + public boolean end(int addr) { + if (isEnd) { + return false; } - return false; + this.isEnd = true; + this.endAddr = addr; + return true; } public int getRegNum() { @@ -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; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/DepthRegionTraversal.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/DepthRegionTraversal.java index 351f741f9..e25174a4c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/DepthRegionTraversal.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/DepthRegionTraversal.java @@ -60,16 +60,19 @@ 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()) { - if (traverseIterativeStepInternal(mth, visitor, subCont)) { - return true; + try { + if (traverseIterativeStepInternal(mth, visitor, subCont)) { + return true; + } + } catch (StackOverflowError overflow) { + throw new JadxOverflowException("Region traversal failed: Recursive call in traverseIterativeStepInternal method"); } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java index 0f258e4c6..413ac5890 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java @@ -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); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java index 80860bf5a..603bfc5c7 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java @@ -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> entry : blocksMap.entrySet()) { BlockNode caseBlock = entry.getKey(); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java index cd176a877..a31005566 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java @@ -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)); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ssa/EliminatePhiNodes.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ssa/EliminatePhiNodes.java index 3145e6d0b..8014a1278 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ssa/EliminatePhiNodes.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ssa/EliminatePhiNodes.java @@ -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) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ssa/SSATransform.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ssa/SSATransform.java index a1de3bd21..f05086da9 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ssa/SSATransform.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ssa/SSATransform.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/BoundEnum.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/BoundEnum.java new file mode 100644 index 000000000..b66d46013 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/BoundEnum.java @@ -0,0 +1,6 @@ +package jadx.core.dex.visitors.typeinference; + +public enum BoundEnum { + ASSIGN, + USE +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/CheckTypeVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/CheckTypeVisitor.java deleted file mode 100644 index 5e6c96667..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/CheckTypeVisitor.java +++ /dev/null @@ -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); - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/FinishTypeInference.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/FinishTypeInference.java deleted file mode 100644 index 981ad8d20..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/FinishTypeInference.java +++ /dev/null @@ -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); - } - } - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ITypeBound.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ITypeBound.java new file mode 100644 index 000000000..099ff773c --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ITypeBound.java @@ -0,0 +1,9 @@ +package jadx.core.dex.visitors.typeinference; + +import jadx.core.dex.instructions.args.ArgType; + +public interface ITypeBound { + BoundEnum getBound(); + + ArgType getType(); +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ITypeListener.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ITypeListener.java new file mode 100644 index 000000000..e4fa16e29 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ITypeListener.java @@ -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); +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/PostTypeInference.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/PostTypeInference.java deleted file mode 100644 index 5b81ce8a3..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/PostTypeInference.java +++ /dev/null @@ -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 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; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/SelectTypeVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/SelectTypeVisitor.java deleted file mode 100644 index 0553059e2..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/SelectTypeVisitor.java +++ /dev/null @@ -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); - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundConst.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundConst.java new file mode 100644 index 000000000..344374378 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundConst.java @@ -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 + '}'; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompare.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompare.java new file mode 100644 index 000000000..5f650bd8b --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompare.java @@ -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 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 { + @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; + } + } + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompareEnum.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompareEnum.java new file mode 100644 index 000000000..b205f3e3e --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompareEnum.java @@ -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; + } + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInference.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInference.java deleted file mode 100644 index e8eb941c6..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInference.java +++ /dev/null @@ -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 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; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java new file mode 100644 index 000000000..9de242932 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java @@ -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 bounds = typeInfo.getBounds(); + Optional 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 selectBestTypeFromBounds(Set 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 types = makePossibleTypesList(type); + for (ArgType candidateType : types) { + TypeUpdateResult result = typeUpdate.apply(var, candidateType); + if (result == TypeUpdateResult.CHANGED) { + break; + } + } + } + + private List makePossibleTypesList(ArgType type) { + List 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; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInfo.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInfo.java new file mode 100644 index 000000000..f9770f221 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInfo.java @@ -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 bounds = new HashSet<>(); + + public ArgType getType() { + return type; + } + + public void setType(ArgType type) { + this.type = type; + } + + public Set getBounds() { + return bounds; + } + + @Override + public String toString() { + return "TypeInfo{" + + "type=" + type + + ", bounds=" + bounds + + '}'; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java new file mode 100644 index 000000000..6dbbdaa14 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java @@ -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 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 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 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 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 getArgTypeComparator() { + return comparator.getComparator(); + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateInfo.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateInfo.java new file mode 100644 index 000000000..1620ae11b --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateInfo.java @@ -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 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 getUpdates() { + return updates; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateRegistry.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateRegistry.java new file mode 100644 index 000000000..b66fea233 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateRegistry.java @@ -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> listenersMap = new EnumMap<>(InsnType.class); + + public void add(InsnType insnType, ITypeListener listener) { + listenersMap.computeIfAbsent(insnType, k -> new ArrayList<>(3)).add(listener); + } + + @NotNull + public List getListenersForInsn(InsnType insnType) { + List list = listenersMap.get(insnType); + if (list == null) { + return Collections.emptyList(); + } + return list; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateResult.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateResult.java new file mode 100644 index 000000000..fbb0a56e0 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateResult.java @@ -0,0 +1,7 @@ +package jadx.core.dex.visitors.typeinference; + +public enum TypeUpdateResult { + REJECT, + SAME, + CHANGED +} diff --git a/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java b/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java index 8d6edc031..6b132b3e5 100644 --- a/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java @@ -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 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; + } } diff --git a/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java b/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java index 03f0e9f0f..6fe0e7b58 100644 --- a/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java @@ -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); diff --git a/jadx-core/src/main/java/jadx/core/utils/InstructionRemover.java b/jadx-core/src/main/java/jadx/core/utils/InstructionRemover.java index 7097bec7b..5a553e61f 100644 --- a/jadx-core/src/main/java/jadx/core/utils/InstructionRemover.java +++ b/jadx-core/src/main/java/jadx/core/utils/InstructionRemover.java @@ -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()); } } diff --git a/jadx-core/src/main/java/jadx/core/utils/Utils.java b/jadx-core/src/main/java/jadx/core/utils/Utils.java index 1532cdb83..c1e31ec8d 100644 --- a/jadx-core/src/main/java/jadx/core/utils/Utils.java +++ b/jadx-core/src/main/java/jadx/core/utils/Utils.java @@ -57,16 +57,15 @@ public class Utils { } } - public static String arrayToString(Object[] array) { - if (array == null) { + public static 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(); } diff --git a/jadx-core/src/test/java/jadx/api/JadxInternalAccess.java b/jadx-core/src/test/java/jadx/api/JadxInternalAccess.java index 8bfb46072..5ef6daf4a 100644 --- a/jadx-core/src/test/java/jadx/api/JadxInternalAccess.java +++ b/jadx-core/src/test/java/jadx/api/JadxInternalAccess.java @@ -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 getPassList(JadxDecompiler d) { + return d.getPasses(); + } } diff --git a/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java b/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java new file mode 100644 index 000000000..228d29d69 --- /dev/null +++ b/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java @@ -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)); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java index 48e718200..51c55ea9a 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -53,7 +53,7 @@ public abstract class IntegrationTest extends TestUtils { /** * Run auto check method if defined: *
-	 *     public static void check()
+	 *     public void check() {}
 	 * 
*/ public static final String CHECK_METHOD_NAME = "check"; diff --git a/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java b/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java index 90ac8de9e..a0754fbf5 100644 --- a/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java +++ b/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java @@ -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 passes = Jadx.getPassesList(jadx.getArgs()); + private void processByPatterns(JadxDecompiler jadx, String clsPattern, @Nullable String mthPattern) { + List 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 passes, ClassNode classNode) { + private boolean processCls(@Nullable String mthPattern, List 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); } } } diff --git a/jadx-core/src/test/java/jadx/tests/functional/TypeMergeTest.java b/jadx-core/src/test/java/jadx/tests/functional/TypeMergeTest.java deleted file mode 100644 index 2a9bd21ea..000000000 --- a/jadx-core/src/test/java/jadx/tests/functional/TypeMergeTest.java +++ /dev/null @@ -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; - } -} diff --git a/jadx-core/src/test/java/jadx/tests/integration/TestFloatValue.java b/jadx-core/src/test/java/jadx/tests/integration/TestFloatValue.java index 5cd7da9e6..676955dea 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/TestFloatValue.java +++ b/jadx-core/src/test/java/jadx/tests/integration/TestFloatValue.java @@ -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 diff --git a/jadx-core/src/test/java/jadx/tests/integration/TestRedundantBrackets.java b/jadx-core/src/test/java/jadx/tests/integration/TestRedundantBrackets.java index c37ab062d..91dde240b 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/TestRedundantBrackets.java +++ b/jadx-core/src/test/java/jadx/tests/integration/TestRedundantBrackets.java @@ -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)")); diff --git a/jadx-core/src/test/java/jadx/tests/integration/annotations/TestAnnotationsMix.java b/jadx-core/src/test/java/jadx/tests/integration/annotations/TestAnnotationsMix.java new file mode 100644 index 000000000..8b2f1adc9 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/annotations/TestAnnotationsMix.java @@ -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); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/arith/TestArith3.java b/jadx-core/src/test/java/jadx/tests/integration/arith/TestArith3.java index 9ab664756..2ab65cc2b 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/arith/TestArith3.java +++ b/jadx-core/src/test/java/jadx/tests/integration/arith/TestArith3.java @@ -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 (")); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrays4.java b/jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrays4.java index d2b5e7bdb..ca109c41f 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrays4.java +++ b/jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrays4.java @@ -1,34 +1,35 @@ 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; - public static class TestCls { - char[] payload; +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.junit.Assert.assertThat; - public TestCls(byte[] bytes) { - char[] a = toChars(bytes); - this.payload = new char[a.length]; - System.arraycopy(a, 0, this.payload, 0, bytes.length); - } +public class TestArrays4 extends IntegrationTest { - private static char[] toChars(byte[] bArr) { - return new char[bArr.length]; - } - } + public static class TestCls { + char[] payload; - @Test - public void testArrayTypeInference() { - noDebugInfo(); - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); + public TestCls(byte[] bytes) { + char[] a = toChars(bytes); + this.payload = new char[a.length]; + System.arraycopy(a, 0, this.payload, 0, bytes.length); + } - assertThat(code, containsOne("char[] toChars = toChars(bArr);")); - } + private static char[] toChars(byte[] bArr) { + return new char[bArr.length]; + } + } + @Test + public void testArrayTypeInference() { + noDebugInfo(); + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("char[] chars = toChars(bArr);")); + } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernary.java b/jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernary.java index 3c6c5bf02..19455e133 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernary.java +++ b/jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernary.java @@ -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;")); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestReturnSourceLine.java b/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestReturnSourceLine.java index 823376166..859d567bc 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestReturnSourceLine.java +++ b/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestReturnSourceLine.java @@ -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"); diff --git a/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestVariablesNames.java b/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestVariablesNames.java index e55066091..098c4e5c0 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestVariablesNames.java +++ b/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestVariablesNames.java @@ -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; diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenerics2.java b/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenerics2.java index 93282f037..1b717d0db 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenerics2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenerics2.java @@ -29,7 +29,10 @@ public class TestGenerics2 extends IntegrationTest { public V get(Object id) { WeakReference 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 queue) {")); assertThat(code, containsString("public V get(Object id) {")); assertThat(code, containsString("WeakReference ref = ")); - assertThat(code, containsString("return ref != null ? ref.get() : null;")); + assertThat(code, containsString("return ref.get();")); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenericsInArgs.java b/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenericsInArgs.java new file mode 100644 index 000000000..efa097da5 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenericsInArgs.java @@ -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 void test(List genericList, Set 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 void test(List genericList, Set 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 void test(List list, Set set) {")); + assertThat(code, containsString("if (list == null) {")); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestIterableForEach3.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestIterableForEach3.java index 95acff4ec..3588244a8 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/loops/TestIterableForEach3.java +++ b/jadx-core/src/test/java/jadx/tests/integration/loops/TestIterableForEach3.java @@ -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); + } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestSequentialLoops2.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestSequentialLoops2.java index 4bc68418b..894053b2e 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/loops/TestSequentialLoops2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/loops/TestSequentialLoops2.java @@ -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")); + } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchBreak.java b/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchBreak.java index 6b3db1059..0df2517f3 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchBreak.java +++ b/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchBreak.java @@ -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 + \"+\";")); diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch6.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch6.java index 886db9c5f..afc20706e 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch6.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch6.java @@ -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(); diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch7.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch7.java index b8ae43c24..b038eab90 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch7.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch7.java @@ -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 + ";")); diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver5.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver5.java new file mode 100644 index 000000000..58c53cd10 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver5.java @@ -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"))); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesGeneric.java b/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesGeneric.java new file mode 100644 index 000000000..9837a4392 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesGeneric.java @@ -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 j a(i iVar, c 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 j a(i iVar, c cVar) {")); + assertThat(code, containsString("if (iVar == null) {")); + } +} diff --git a/jadx-core/src/test/smali/types/TestTypeResolver5.smali b/jadx-core/src/test/smali/types/TestTypeResolver5.smali new file mode 100644 index 000000000..a87b0ca99 --- /dev/null +++ b/jadx-core/src/test/smali/types/TestTypeResolver5.smali @@ -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 ()V + .locals 0 + + .prologue + .line 35 + invoke-direct {p0}, Lcom/souq/app/activity/BaseContentActivity;->()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;->(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 diff --git a/jadx-core/src/test/smali/variables/TestVariablesGeneric.smali b/jadx-core/src/test/smali/variables/TestVariablesGeneric.smali new file mode 100644 index 000000000..29ae8ee07 --- /dev/null +++ b/jadx-core/src/test/smali/variables/TestVariablesGeneric.smali @@ -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 = { + "(", + "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;->(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;->(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;->(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;->(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;->(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