refactor: additional checks for ssa vars and registers

This commit is contained in:
Skylot
2019-11-24 20:33:55 +03:00
parent e4e6f37949
commit 87504dd2cc
38 changed files with 703 additions and 284 deletions
+1
View File
@@ -38,6 +38,7 @@ allprojects {
testCompile 'ch.qos.logback:logback-classic:1.2.3'
testCompile 'org.hamcrest:hamcrest-library:2.1'
testCompile 'org.mockito:mockito-core:3.0.0'
testCompile 'org.assertj:assertj-core:3.14.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.1'
@@ -19,6 +19,8 @@ public enum AFlag {
COMMENT_OUT, // process as usual, but comment insn in generated code
REMOVE, // can be completely removed
HIDDEN, // instruction used inside other instruction but not listed in args
RESTART_CODEGEN,
DONT_RENAME, // do not rename during deobfuscation
ADDED_TO_REGION,
@@ -58,6 +60,7 @@ public enum AFlag {
* Use constants with explicit type: cast '(byte) 1' or type letter '7L'
*/
EXPLICIT_PRIMITIVE_TYPE,
EXPLICIT_CAST,
INCONSISTENT_CODE, // warning about incorrect decompilation
}
@@ -14,7 +14,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
public class SkipMethodArgsAttr implements IAttribute {
public static void skipArg(MethodNode mth, RegisterArg arg) {
int argNum = Utils.indexInList(mth.getArgRegs(), arg);
int argNum = Utils.indexInListByRef(mth.getArgRegs(), arg);
if (argNum == -1) {
throw new JadxRuntimeException("Arg not found: " + arg);
}
@@ -97,7 +97,11 @@ public class ArithNode extends InsnNode {
@Override
public InsnNode copy() {
return copyCommonParams(new ArithNode(op, getResult(), getArg(0), getArg(1)));
ArithNode copy = new ArithNode(op,
getResult().duplicate(),
getArg(0).duplicate(),
getArg(1).duplicate());
return copyCommonParams(copy);
}
@Override
@@ -10,7 +10,6 @@ import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
public class InvokeNode extends InsnNode implements CallMthInterface {
@@ -90,11 +89,6 @@ public class InvokeNode extends InsnNode implements CallMthInterface {
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ (getResult() == null ? "" : getResult() + " = ")
+ Utils.listToString(getArguments())
+ ' ' + mth
+ " type: " + type;
return super.toString() + ' ' + mth + " type: " + type;
}
}
@@ -76,7 +76,7 @@ public final class PhiInsn extends InsnNode {
}
@Override
protected RegisterArg removeArg(int index) {
public RegisterArg removeArg(int index) {
RegisterArg reg = (RegisterArg) super.removeArg(index);
blockBinds.remove(index);
reg.getSVar().updateUsedInPhiList();
@@ -93,6 +93,7 @@ public abstract class InsnArg extends Typed {
this.parentInsn = parentInsn;
}
@Nullable("if wrap failed")
public InsnArg wrapInstruction(MethodNode mth, InsnNode insn) {
InsnNode parent = parentInsn;
if (parent == null) {
@@ -153,8 +154,10 @@ public abstract class InsnArg extends Typed {
* This method don't support MOVE and CONST insns!
*/
public static InsnArg wrapArg(InsnNode insn) {
RegisterArg resArg = insn.getResult();
InsnArg arg = wrap(insn);
insn.add(AFlag.WRAPPED);
switch (insn.getType()) {
case CONST:
case MOVE:
@@ -162,13 +165,18 @@ public abstract class InsnArg extends Typed {
case CONST_STR:
arg.setType(ArgType.STRING);
if (resArg != null) {
resArg.setType(ArgType.STRING);
}
break;
case CONST_CLASS:
arg.setType(ArgType.CLASS);
if (resArg != null) {
resArg.setType(ArgType.CLASS);
}
break;
default:
RegisterArg resArg = insn.getResult();
if (resArg != null) {
arg.setType(resArg.getType());
}
@@ -76,10 +76,9 @@ public final class InsnWrapArg extends InsnArg {
@Override
public String toString() {
if (wrappedInsn.getType() == InsnType.CONST_STR
&& Objects.equals(type, ArgType.STRING)) {
if (wrappedInsn.getType() == InsnType.CONST_STR && Objects.equals(type, ArgType.STRING)) {
return "(\"" + ((ConstStringNode) wrappedInsn).getString() + "\")";
}
return "(wrap: " + type + "\n " + wrappedInsn + ')';
return "(wrap: " + type + " : " + wrappedInsn + ')';
}
}
@@ -125,6 +125,7 @@ public class RegisterArg extends InsnArg implements Named {
public RegisterArg duplicate(int regNum, @Nullable SSAVar sVar) {
RegisterArg dup = new RegisterArg(regNum, getInitType());
if (sVar != null) {
// only 'set' here, 'assign' or 'use' will binds later
dup.setSVar(sVar);
}
return copyCommonParams(dup);
@@ -143,6 +144,9 @@ public class RegisterArg extends InsnArg implements Named {
}
public boolean sameRegAndSVar(InsnArg arg) {
if (this == arg) {
return true;
}
if (!arg.isRegister()) {
return false;
}
@@ -1,5 +1,7 @@
package jadx.core.dex.instructions.mods;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.CallMthInterface;
@@ -13,7 +15,6 @@ public final class ConstructorInsn extends InsnNode implements CallMthInterface
private final MethodInfo callMth;
private final CallType callType;
private final RegisterArg instanceArg;
public enum CallType {
CONSTRUCTOR, // just new instance
@@ -26,7 +27,7 @@ public final class ConstructorInsn extends InsnNode implements CallMthInterface
super(InsnType.CONSTRUCTOR, invoke.getArgsCount() - 1);
this.callMth = invoke.getCallMth();
ClassInfo classType = callMth.getDeclClass();
instanceArg = (RegisterArg) invoke.getArg(0);
RegisterArg instanceArg = (RegisterArg) invoke.getArg(0);
if (instanceArg.isThis()) {
if (classType.equals(mth.getParentClass().getClassInfo())) {
@@ -52,11 +53,10 @@ public final class ConstructorInsn extends InsnNode implements CallMthInterface
}
}
public ConstructorInsn(MethodInfo callMth, CallType callType, RegisterArg instanceArg) {
public ConstructorInsn(MethodInfo callMth, CallType callType) {
super(InsnType.CONSTRUCTOR, callMth.getArgsCount());
this.callMth = callMth;
this.callType = callType;
this.instanceArg = instanceArg;
}
public MethodInfo getCallMth() {
@@ -64,8 +64,9 @@ public final class ConstructorInsn extends InsnNode implements CallMthInterface
}
@Override
@Nullable
public RegisterArg getInstanceArg() {
return instanceArg;
return null;
}
public ClassInfo getClassType() {
@@ -112,11 +113,11 @@ public final class ConstructorInsn extends InsnNode implements CallMthInterface
@Override
public InsnNode copy() {
return copyCommonParams(new ConstructorInsn(callMth, callType, instanceArg));
return copyCommonParams(new ConstructorInsn(callMth, callType));
}
@Override
public String toString() {
return super.toString() + ' ' + callMth + ' ' + callType;
return super.toString() + " call: " + callMth + " type: " + callType;
}
}
@@ -9,7 +9,6 @@ import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
public final class TernaryInsn extends InsnNode {
@@ -78,10 +77,20 @@ public final class TernaryInsn extends InsnNode {
return copyCommonParams(copy);
}
@Override
public void rebindArgs() {
super.rebindArgs();
for (RegisterArg reg : condition.getRegisterArgs()) {
InsnNode parentInsn = reg.getParentInsn();
if (parentInsn != null) {
parentInsn.rebindArgs();
}
}
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": TERNARY"
+ getResult() + " = "
+ Utils.listToString(getArguments());
+ getResult() + " = (" + condition + ") ? " + getArg(0) + " : " + getArg(1);
}
}
@@ -38,6 +38,9 @@ public class InsnNode extends LineAttrNode {
this.insnType = type;
this.arguments = args;
this.offset = -1;
for (InsnArg arg : args) {
attachArg(arg);
}
}
public static InsnNode wrapArg(InsnArg arg) {
@@ -67,7 +70,7 @@ public class InsnNode extends LineAttrNode {
attachArg(arg);
}
private void attachArg(InsnArg arg) {
protected void attachArg(InsnArg arg) {
arg.setParentInsn(this);
if (arg.isRegister()) {
RegisterArg reg = (RegisterArg) arg;
@@ -98,10 +101,24 @@ public class InsnNode extends LineAttrNode {
return arguments.get(n);
}
public boolean containsArg(RegisterArg arg) {
public boolean containsArg(InsnArg arg) {
if (getArgsCount() == 0) {
return false;
}
for (InsnArg a : arguments) {
if (a == arg
|| a.isRegister() && ((RegisterArg) a).getRegNum() == arg.getRegNum()) {
if (a == arg) {
return true;
}
}
return false;
}
public boolean containsVar(RegisterArg arg) {
if (getArgsCount() == 0) {
return false;
}
for (InsnArg insnArg : arguments) {
if (insnArg == arg || arg.sameRegAndSVar(insnArg)) {
return true;
}
}
@@ -136,7 +153,7 @@ public class InsnNode extends LineAttrNode {
return true;
}
protected InsnArg removeArg(int index) {
public InsnArg removeArg(int index) {
InsnArg arg = arguments.get(index);
arguments.remove(index);
InsnRemover.unbindArgUsage(null, arg);
@@ -254,30 +271,6 @@ public class InsnNode extends LineAttrNode {
return true;
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ (result == null ? "" : result + " = ")
+ Utils.listToString(arguments);
}
/**
* Compare instruction only by identity.
*/
@Override
public final int hashCode() {
return super.hashCode();
}
/**
* Compare instruction only by identity.
*/
@Override
public final boolean equals(Object obj) {
return super.equals(obj);
}
/**
* 'Soft' equals, don't compare arguments, only instruction specific parameters.
*/
@@ -323,14 +316,14 @@ public class InsnNode extends LineAttrNode {
}
protected final <T extends InsnNode> T copyCommonParams(T copy) {
if (result != null) {
if (copy.getResult() == null && result != null) {
copy.setResult(result.duplicate());
}
if (copy.getArgsCount() == 0) {
for (InsnArg arg : this.getArguments()) {
if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
copy.addArg(InsnArg.wrapArg(wrapInsn.copy()));
copy.addArg(InsnArg.wrapInsnIntoArg(wrapInsn.copy()));
} else {
copy.addArg(arg.duplicate());
}
@@ -352,6 +345,27 @@ public class InsnNode extends LineAttrNode {
return copyCommonParams(new InsnNode(insnType, getArgsCount()));
}
/**
* Fix SSAVar info in register arguments.
* Must be used after altering instructions.
*/
public void rebindArgs() {
RegisterArg resArg = getResult();
if (resArg != null) {
resArg.getSVar().setAssign(resArg);
}
for (InsnArg arg : getArguments()) {
if (arg instanceof RegisterArg) {
RegisterArg reg = (RegisterArg) arg;
SSAVar ssaVar = reg.getSVar();
ssaVar.use(reg);
ssaVar.updateUsedInPhiList();
} else if (arg instanceof InsnWrapArg) {
((InsnWrapArg) arg).getWrapInsn().rebindArgs();
}
}
}
public boolean canThrowException() {
switch (getType()) {
case RETURN:
@@ -371,4 +385,45 @@ public class InsnNode extends LineAttrNode {
return true;
}
}
/**
* Compare instruction only by identity.
*/
@Override
public final int hashCode() {
return super.hashCode();
}
/**
* Compare instruction only by identity.
*/
@Override
public final boolean equals(Object obj) {
return super.equals(obj);
}
protected void appendArgs(StringBuilder sb) {
String argsStr = Utils.listToString(arguments);
if (argsStr.length() < 60) {
sb.append(argsStr);
} else {
// wrap args
String separator = "\n ";
sb.append(separator).append(Utils.listToString(arguments, separator));
sb.append('\n');
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(InsnUtils.formatOffset(offset));
sb.append(": ");
sb.append(InsnUtils.insnTypeToString(insnType));
if (result != null) {
sb.append(result).append(" = ");
}
appendArgs(sb);
return sb.toString();
}
}
@@ -1,5 +1,6 @@
package jadx.core.dex.regions.conditions;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IfOp;
import jadx.core.dex.instructions.args.InsnArg;
@@ -9,6 +10,7 @@ public final class Compare {
private final IfNode insn;
public Compare(IfNode insn) {
insn.add(AFlag.HIDDEN);
this.insn = insn;
}
@@ -102,12 +102,12 @@ public final class LoopRegion extends AbstractRegion {
boolean found = false;
// search result arg in other insns
for (int j = i + 1; j < size; j++) {
if (insns.get(i).containsArg(res)) {
if (insns.get(i).containsVar(res)) {
found = true;
}
}
// or in if insn
if (!found && ifInsn.containsArg(res)) {
if (!found && ifInsn.containsVar(res)) {
found = true;
}
if (!found) {
@@ -60,6 +60,7 @@ public class ConstructorVisitor extends AbstractVisitor {
}
InsnNode instArgAssignInsn = ((RegisterArg) inv.getArg(0)).getAssignInsn();
ConstructorInsn co = new ConstructorInsn(mth, inv);
co.rebindArgs();
boolean remove = false;
if (co.isSuper() && (co.getArgsCount() == 0 || parentClass.isEnum())) {
remove = true;
@@ -97,9 +98,10 @@ public class ConstructorVisitor extends AbstractVisitor {
}
ConstructorInsn replace = processConstructor(mth, co);
if (replace != null) {
remover.addAndUnbind(co);
co = replace;
}
BlockUtils.replaceInsn(block, indexInBlock, co);
BlockUtils.replaceInsn(mth, block, indexInBlock, co);
}
/**
@@ -116,14 +118,17 @@ public class ConstructorVisitor extends AbstractVisitor {
if (classNode == null) {
return null;
}
RegisterArg instanceArg = co.getInstanceArg();
RegisterArg instanceArg = co.getResult();
if (instanceArg == null) {
return null;
}
boolean passThis = instanceArg.isThis();
String ctrId = "<init>(" + (passThis ? TypeGen.signature(instanceArg.getInitType()) : "") + ")V";
MethodNode defCtr = classNode.searchMethodByShortId(ctrId);
if (defCtr == null) {
return null;
}
ConstructorInsn newInsn = new ConstructorInsn(defCtr.getMethodInfo(), co.getCallType(), instanceArg);
ConstructorInsn newInsn = new ConstructorInsn(defCtr.getMethodInfo(), co.getCallType());
newInsn.setResult(co.getResult());
return newInsn;
}
@@ -71,7 +71,7 @@ public class DeboxingVisitor extends AbstractVisitor {
if (insnNode.getType() == InsnType.INVOKE) {
InsnNode replaceInsn = checkForReplace(((InvokeNode) insnNode));
if (replaceInsn != null) {
BlockUtils.replaceInsn(blockNode, i, replaceInsn);
BlockUtils.replaceInsn(mth, blockNode, i, replaceInsn);
replaced = true;
}
}
@@ -3,6 +3,7 @@ package jadx.core.dex.visitors;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.DebugChecks;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.JadxOverflowException;
@@ -28,6 +29,9 @@ public class DepthTraversal {
}
try {
visitor.visit(mth);
if (DebugChecks.checksEnabled) {
DebugChecks.runChecksAfterVisitor(mth, visitor);
}
} catch (StackOverflowError e) {
ErrorsCounter.methodError(mth, "StackOverflow in pass: " + visitor.getClass().getSimpleName(), new JadxOverflowException(""));
} catch (Exception e) {
@@ -14,7 +14,6 @@ import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
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.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
@@ -22,7 +21,6 @@ import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
@@ -66,27 +64,8 @@ public class MethodInlineVisitor extends AbstractVisitor {
addInlineAttr(mth, insnList.get(0));
return;
}
// other field operations
if (insnList.size() == 2
&& returnBlock.getInstructions().size() == 1
&& !mth.getReturnType().equals(ArgType.VOID)) {
InsnNode get = insnList.get(0);
InsnNode put = insnList.get(1);
InsnArg retArg = returnBlock.getInstructions().get(0).getArg(0);
if (get.getType() == InsnType.IGET
&& put.getType() == InsnType.IPUT
&& retArg.isRegister()
&& get.getResult().equalRegisterAndType((RegisterArg) retArg)) {
RegisterArg retReg = (RegisterArg) retArg;
retReg.getSVar().removeUse(retReg);
CodeShrinkVisitor.shrinkMethod(mth);
insnList = firstBlock.getInstructions();
if (insnList.size() == 1) {
addInlineAttr(mth, insnList.get(0));
}
}
}
// TODO: inline field arithmetics. Disabled tests: TestAnonymousClass3a and TestAnonymousClass5
}
private static void addInlineAttr(MethodNode mth, InsnNode insn) {
@@ -94,7 +94,7 @@ public class ModVisitor extends AbstractVisitor {
case CONST:
case CONST_STR:
case CONST_CLASS:
replaceConst(parentClass, block, i, insn);
replaceConst(mth, parentClass, block, i, insn);
break;
case SWITCH:
@@ -109,14 +109,14 @@ public class ModVisitor extends AbstractVisitor {
FillArrayNode fillArrInsn = (FillArrayNode) nextInsn;
if (checkArrSizes(mth, newArrInsn, fillArrInsn)) {
InsnNode filledArr = makeFilledArrayInsn(mth, newArrInsn, fillArrInsn);
replaceInsn(block, i, filledArr);
replaceInsn(mth, block, i, filledArr);
remover.addAndUnbind(nextInsn);
}
}
break;
case MOVE_EXCEPTION:
processMoveException(block, insn, remover);
processMoveException(mth, block, insn, remover);
break;
case ARITH:
@@ -128,7 +128,7 @@ public class ModVisitor extends AbstractVisitor {
break;
case CAST:
fixPrimitiveCast(block, i, insn);
fixPrimitiveCast(mth, block, i, insn);
break;
default:
@@ -148,13 +148,12 @@ public class ModVisitor extends AbstractVisitor {
}
}
private static void fixPrimitiveCast(BlockNode block, int i, InsnNode insn) {
private static void fixPrimitiveCast(MethodNode mth, BlockNode block, int i, InsnNode insn) {
// replace boolean to (byte/char/short/long/double/float) cast with ternary
if (insn.getArg(0).getType() == ArgType.BOOLEAN) {
InsnArg castArg = insn.getArg(0);
if (castArg.getType() == ArgType.BOOLEAN) {
ArgType type = insn.getResult().getType();
if (type.isPrimitive()) {
IfNode ifNode = new IfNode(IfOp.EQ, -1, insn.getArg(0), LiteralArg.TRUE);
IfCondition condition = IfCondition.fromIfNode(ifNode);
InsnArg zero = new LiteralArg(0, type);
long litVal = 1;
if (type == ArgType.DOUBLE) {
@@ -163,13 +162,16 @@ public class ModVisitor extends AbstractVisitor {
litVal = FLOAT_TO_BITS;
}
InsnArg one = new LiteralArg(litVal, type);
IfNode ifNode = new IfNode(IfOp.EQ, -1, castArg, LiteralArg.TRUE);
IfCondition condition = IfCondition.fromIfNode(ifNode);
TernaryInsn ternary = new TernaryInsn(condition, insn.getResult(), one, zero);
replaceInsn(block, i, ternary);
replaceInsn(mth, block, i, ternary);
}
}
}
private static void replaceConst(ClassNode parentClass, BlockNode block, int i, InsnNode insn) {
private static void replaceConst(MethodNode mth, ClassNode parentClass, BlockNode block, int i, InsnNode insn) {
FieldNode f;
if (insn.getType() == InsnType.CONST_STR) {
String s = ((ConstStringNode) insn).getString();
@@ -183,7 +185,7 @@ public class ModVisitor extends AbstractVisitor {
if (f != null) {
InsnNode inode = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
inode.setResult(insn.getResult());
replaceInsn(block, i, inode);
replaceInsn(mth, block, i, inode);
}
}
@@ -220,7 +222,7 @@ public class ModVisitor extends AbstractVisitor {
InsnNode insnNode = new InsnNode(InsnType.MOVE, 1);
insnNode.setResult(insn.getResult());
insnNode.addArg(castArg);
replaceInsn(block, i, insnNode);
replaceInsn(mth, block, i, insnNode);
}
}
@@ -412,7 +414,7 @@ public class ModVisitor extends AbstractVisitor {
return filledArr;
}
private static void processMoveException(BlockNode block, InsnNode insn, InsnRemover remover) {
private static void processMoveException(MethodNode mth, BlockNode block, InsnNode insn, InsnRemover remover) {
ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER);
if (excHandlerAttr == null) {
return;
@@ -437,7 +439,7 @@ public class ModVisitor extends AbstractVisitor {
NamedArg namedArg = new NamedArg(name, type);
moveInsn.addArg(namedArg);
excHandler.setArg(namedArg);
replaceInsn(block, 0, moveInsn);
replaceInsn(mth, block, 0, moveInsn);
}
}
}
@@ -89,7 +89,7 @@ public class ReSugarCode extends AbstractVisitor {
}
/**
* Replace new array and sequence of array-put to new filled-array instruction.
* Replace new-array and sequence of array-put to new filled-array instruction.
*/
private static void processNewArray(MethodNode mth, NewArrayNode newArrayInsn,
List<InsnNode> instructions, InsnRemover remover) {
@@ -135,18 +135,15 @@ public class ReSugarCode extends AbstractVisitor {
// checks complete, apply
ArgType arrType = newArrayInsn.getArrayType();
InsnNode filledArr = new FilledNewArrayNode(arrType.getArrayElement(), len);
filledArr.setResult(arrArg);
filledArr.setResult(arrArg.duplicate());
for (InsnNode put : arrPuts) {
filledArr.addArg(put.getArg(2).duplicate());
remover.addAndUnbind(put);
}
remover.addAndUnbind(newArrayInsn);
InsnNode lastPut = Utils.last(arrPuts);
for (InsnNode put : arrPuts) {
filledArr.addArg(put.getArg(2));
if (put != lastPut) {
remover.addWithoutUnbind(put);
}
InsnRemover.unbindArgUsage(mth, put.getArg(0));
}
remover.addWithoutUnbind(newArrayInsn);
int replaceIndex = InsnList.getIndex(instructions, lastPut);
instructions.set(replaceIndex, filledArr);
}
@@ -45,6 +45,17 @@ public class SimplifyVisitor extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(SimplifyVisitor.class);
private MethodInfo stringGetBytesMth;
@Override
public void init(RootNode root) {
stringGetBytesMth = MethodInfo.externalMth(
ClassInfo.fromType(root, ArgType.STRING),
"getBytes",
Collections.emptyList(),
ArgType.array(ArgType.BYTE));
}
@Override
public void visit(MethodNode mth) {
if (mth.isNoCode()) {
@@ -61,14 +72,15 @@ public class SimplifyVisitor extends AbstractVisitor {
}
}
private static boolean simplifyBlock(MethodNode mth, BlockNode block) {
private boolean simplifyBlock(MethodNode mth, BlockNode block) {
boolean changed = false;
List<InsnNode> list = block.getInstructions();
for (int i = 0; i < list.size(); i++) {
InsnNode insn = list.get(i);
int insnCount = list.size();
InsnNode modInsn = simplifyInsn(mth, block, insn);
InsnNode modInsn = simplifyInsn(mth, insn);
if (modInsn != null) {
modInsn.rebindArgs();
if (i < list.size() && list.get(i) == insn) {
list.set(i, modInsn);
} else {
@@ -89,18 +101,29 @@ public class SimplifyVisitor extends AbstractVisitor {
return changed;
}
private static InsnNode simplifyInsn(MethodNode mth, BlockNode block, InsnNode insn) {
if (insn.contains(AFlag.DONT_GENERATE)) {
return null;
}
private void simplifyArgs(MethodNode mth, InsnNode insn) {
boolean changed = false;
for (InsnArg arg : insn.getArguments()) {
if (arg.isInsnWrap()) {
InsnNode ni = simplifyInsn(mth, block, ((InsnWrapArg) arg).getWrapInsn());
if (ni != null) {
arg.wrapInstruction(mth, ni);
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
InsnNode replaceInsn = simplifyInsn(mth, wrapInsn);
if (replaceInsn != null) {
arg.wrapInstruction(mth, replaceInsn);
InsnRemover.unbindInsn(mth, wrapInsn);
changed = true;
}
}
}
if (changed) {
insn.rebindArgs();
}
}
private InsnNode simplifyInsn(MethodNode mth, InsnNode insn) {
if (insn.contains(AFlag.DONT_GENERATE)) {
return null;
}
simplifyArgs(mth, insn);
switch (insn.getType()) {
case ARITH:
return simplifyArith((ArithNode) insn);
@@ -120,7 +143,7 @@ public class SimplifyVisitor extends AbstractVisitor {
return convertFieldArith(mth, insn);
case CHECK_CAST:
return processCast(mth, insn);
return processCast(mth, (IndexInsnNode) insn);
case MOVE:
InsnArg firstArg = insn.getArg(0);
@@ -134,8 +157,7 @@ public class SimplifyVisitor extends AbstractVisitor {
break;
case CONSTRUCTOR:
simplifyStringConstructor(mth.root(), (ConstructorInsn) insn);
break;
return simplifyStringConstructor(mth, (ConstructorInsn) insn);
default:
break;
@@ -143,7 +165,7 @@ public class SimplifyVisitor extends AbstractVisitor {
return null;
}
private static void simplifyStringConstructor(RootNode root, ConstructorInsn insn) {
private InsnNode simplifyStringConstructor(MethodNode mth, ConstructorInsn insn) {
if (insn.getCallMth().getDeclClass().getType().equals(ArgType.STRING)
&& insn.getArgsCount() != 0
&& insn.getArg(0).isInsnWrap()) {
@@ -157,7 +179,7 @@ public class SimplifyVisitor extends AbstractVisitor {
for (int i = 0; i < arr.length; i++) {
InsnArg arrArg = arrInsn.getArg(i);
if (!arrArg.isLiteral()) {
return;
return null;
}
arr[i] = (byte) ((LiteralArg) arrArg).getLiteral();
if (NameMapper.isPrintableChar(arr[i])) {
@@ -165,27 +187,32 @@ public class SimplifyVisitor extends AbstractVisitor {
}
}
if (printable >= arr.length - printable) {
InsnArg wa = InsnArg.wrapArg(new ConstStringNode(new String(arr)));
InsnNode constStr = new ConstStringNode(new String(arr));
if (insn.getArgsCount() == 1) {
insn.setArg(0, wa);
constStr.setResult(insn.getResult());
constStr.copyAttributesFrom(insn);
InsnRemover.unbindArgUsage(mth, insn.getArg(0));
return constStr;
} else {
MethodInfo mi = MethodInfo.externalMth(
ClassInfo.fromType(root, ArgType.STRING),
"getBytes",
Collections.emptyList(),
ArgType.array(ArgType.BYTE));
InvokeNode in = new InvokeNode(mi, InvokeType.VIRTUAL, 1);
in.addArg(wa);
insn.setArg(0, InsnArg.wrapArg(in));
InvokeNode in = new InvokeNode(stringGetBytesMth, InvokeType.VIRTUAL, 1);
in.addArg(InsnArg.wrapArg(constStr));
InsnArg bytesArg = InsnArg.wrapArg(in);
bytesArg.setType(stringGetBytesMth.getReturnType());
insn.setArg(0, bytesArg);
return null;
}
}
}
}
}
return null;
}
private static InsnNode processCast(MethodNode mth, InsnNode insn) {
InsnArg castArg = insn.getArg(0);
private static InsnNode processCast(MethodNode mth, IndexInsnNode castInsn) {
if (castInsn.contains(AFlag.EXPLICIT_CAST)) {
return null;
}
InsnArg castArg = castInsn.getArg(0);
ArgType argType = castArg.getType();
// Don't removes CHECK_CAST for wrapped INVOKE if invoked method returns different type
@@ -195,15 +222,32 @@ public class SimplifyVisitor extends AbstractVisitor {
argType = ((InvokeNode) wrapInsn).getCallMth().getReturnType();
}
}
ArgType castToType = (ArgType) ((IndexInsnNode) insn).getIndex();
if (ArgType.isCastNeeded(mth.dex(), argType, castToType)) {
return null;
ArgType castToType = (ArgType) castInsn.getIndex();
if (!ArgType.isCastNeeded(mth.dex(), argType, castToType)
|| isCastDuplicate(castInsn)) {
InsnNode insnNode = new InsnNode(InsnType.MOVE, 1);
insnNode.setOffset(castInsn.getOffset());
insnNode.setResult(castInsn.getResult());
insnNode.addArg(castArg);
return insnNode;
}
InsnNode insnNode = new InsnNode(InsnType.MOVE, 1);
insnNode.setOffset(insn.getOffset());
insnNode.setResult(insn.getResult());
insnNode.addArg(castArg);
return insnNode;
return null;
}
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;
}
/**
@@ -350,7 +394,10 @@ public class SimplifyVisitor extends AbstractVisitor {
InsnNode concatInsn = new InsnNode(InsnType.STR_CONCAT, args);
concatInsn.setResult(toStrInsn.getResult());
concatInsn.add(AFlag.SYNTHETIC);
concatInsn.copyAttributesFrom(toStrInsn);
concatInsn.remove(AFlag.DONT_GENERATE);
concatInsn.remove(AFlag.REMOVE);
return concatInsn;
} catch (Exception e) {
LOG.warn("Can't convert string concatenation: {} insn: {}", mth, toStrInsn, e);
@@ -50,6 +50,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
@Override
public void visit(MethodNode mth) {
// DebugUtils.checkMethod(mth);
DepthRegionTraversal.traverse(mth, this);
}
@@ -95,7 +96,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
}
PhiInsn phiInsn = phiInsnList.get(0);
if (phiInsn.getArgsCount() != 2
|| !phiInsn.containsArg(incrArg)
|| !phiInsn.containsVar(incrArg)
|| incrArg.getSVar().getUseCount() != 1) {
return false;
}
@@ -241,14 +242,17 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
}
List<RegisterArg> itUseList = sVar.getUseList();
InsnNode assignInsn = iteratorArg.getAssignInsn();
if (itUseList.size() != 2 || !checkInvoke(assignInsn, null, "iterator()Ljava/util/Iterator;", 0)) {
if (itUseList.size() != 2) {
return false;
}
if (!checkInvoke(assignInsn, null, "iterator()Ljava/util/Iterator;")) {
return false;
}
InsnArg iterableArg = assignInsn.getArg(0);
InsnNode hasNextCall = itUseList.get(0).getParentInsn();
InsnNode nextCall = itUseList.get(1).getParentInsn();
if (!checkInvoke(hasNextCall, "java.util.Iterator", "hasNext()Z", 0)
|| !checkInvoke(nextCall, "java.util.Iterator", "next()Ljava/lang/Object;", 0)) {
if (!checkInvoke(hasNextCall, "java.util.Iterator", "hasNext()Z")
|| !checkInvoke(nextCall, "java.util.Iterator", "next()Ljava/lang/Object;")) {
return false;
}
List<InsnNode> toSkip = new LinkedList<>();
@@ -353,17 +357,19 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
/**
* Check if instruction is a interface invoke with corresponding parameters.
*/
private static boolean checkInvoke(InsnNode insn, String declClsFullName, String mthId, int argsCount) {
private static boolean checkInvoke(InsnNode insn, String declClsFullName, String mthId) {
if (insn == null) {
return false;
}
if (insn.getType() == InsnType.INVOKE) {
InvokeNode inv = (InvokeNode) insn;
MethodInfo callMth = inv.getCallMth();
if (callMth.getArgsCount() == argsCount
&& callMth.getShortId().equals(mthId)
&& inv.getInvokeType() == InvokeType.INTERFACE) {
return declClsFullName == null || callMth.getDeclClass().getFullName().equals(declClsFullName);
if (inv.getInvokeType() == InvokeType.INTERFACE
&& callMth.getShortId().equals(mthId)) {
if (declClsFullName == null) {
return true;
}
return callMth.getDeclClass().getFullName().equals(declClsFullName);
}
}
return false;
@@ -41,7 +41,6 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.InsnRemover;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.JadxOverflowException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -566,9 +565,9 @@ public class RegionMaker {
if (insnBlock != null) {
insnBlock.add(AFlag.DONT_GENERATE);
}
// remove arg from MONITOR_EXIT to allow inline in MONITOR_ENTER
exitInsn.removeArg(0);
exitInsn.add(AFlag.DONT_GENERATE);
exitInsn.add(AFlag.REMOVE);
InsnRemover.unbindInsn(mth, exitInsn);
}
BlockNode body = getNextBlock(block);
@@ -113,6 +113,7 @@ public class TernaryMod implements IRegionIterativeVisitor {
// remove 'if' instruction
header.getInstructions().clear();
ternInsn.rebindArgs();
header.getInstructions().add(ternInsn);
clearConditionBlocks(conditionBlocks, header);
@@ -148,6 +149,7 @@ public class TernaryMod implements IRegionIterativeVisitor {
retInsn.addArg(arg);
header.getInstructions().clear();
retInsn.rebindArgs();
header.getInstructions().add(retInsn);
header.add(AFlag.RETURN);
@@ -284,6 +286,7 @@ public class TernaryMod implements IRegionIterativeVisitor {
InsnRemover.unbindAllArgs(mth, phiInsn);
header.getInstructions().clear();
ternInsn.rebindArgs();
header.getInstructions().add(ternInsn);
clearConditionBlocks(ifRegion.getConditionBlocks(), header);
@@ -8,7 +8,6 @@ import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.EmptyBitSet;
@@ -37,9 +36,7 @@ final class ArgsInfo {
}
private static void addArgs(InsnNode insn, List<RegisterArg> args) {
if (insn.getType() == InsnType.CONSTRUCTOR) {
args.add(((ConstructorInsn) insn).getInstanceArg());
} else if (insn.getType() == InsnType.TERNARY) {
if (insn.getType() == InsnType.TERNARY) {
args.addAll(((TernaryInsn) insn).getCondition().getRegisterArgs());
}
for (InsnArg arg : insn.getArguments()) {
@@ -147,6 +147,14 @@ public class CodeShrinkVisitor extends AbstractVisitor {
pathsBlocks.remove(assignBlock);
pathsBlocks.remove(useBlock);
for (BlockNode block : pathsBlocks) {
if (block.contains(AFlag.DONT_GENERATE)) {
if (BlockUtils.checkLastInsnType(block, InsnType.MONITOR_EXIT)) {
// don't move from synchronized block
return false;
}
// skip checks for not generated blocks
continue;
}
for (InsnNode insn : block.getInstructions()) {
if (!insn.canReorder() || ArgsInfo.usedArgAssign(insn, args)) {
return false;
@@ -20,6 +20,7 @@ import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBlock;
@@ -586,21 +587,32 @@ public class BlockUtils {
* 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) {
public static void replaceInsn(MethodNode mth, 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);
RegisterArg result = insn.getResult();
RegisterArg prevResult = prevInsn.getResult();
if (result != null && prevResult != null && result.sameRegAndSVar(prevResult)) {
// Don't unbind result for same register.
// Unbind will remove arg from PHI and not add it back on rebind.
InsnRemover.unbindAllArgs(mth, prevInsn);
} else {
InsnRemover.unbindInsn(mth, prevInsn);
}
insn.rebindArgs();
}
public static boolean replaceInsn(BlockNode block, InsnNode oldInsn, InsnNode newInsn) {
public static boolean replaceInsn(MethodNode mth, BlockNode block, InsnNode oldInsn, InsnNode newInsn) {
List<InsnNode> instructions = block.getInstructions();
int size = instructions.size();
for (int i = 0; i < size; i++) {
InsnNode instruction = instructions.get(i);
if (instruction == oldInsn) {
replaceInsn(block, i, newInsn);
replaceInsn(mth, block, i, newInsn);
return true;
}
}
@@ -609,7 +621,7 @@ public class BlockUtils {
public static boolean replaceInsn(MethodNode mth, InsnNode oldInsn, InsnNode newInsn) {
for (BlockNode block : mth.getBasicBlocks()) {
if (replaceInsn(block, oldInsn, newInsn)) {
if (replaceInsn(mth, block, oldInsn, newInsn)) {
return true;
}
}
@@ -0,0 +1,169 @@
package jadx.core.utils;
import java.util.ArrayList;
import java.util.List;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.RenameVisitor;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Check invariants and information consistency for registers and SSA variables
*/
public class DebugChecks {
public static boolean /* not final! */ checksEnabled = false;
public static void runChecksAfterVisitor(MethodNode mth, IDexTreeVisitor visitor) {
Class<? extends IDexTreeVisitor> visitorCls = visitor.getClass();
if (visitorCls == PrepareForCodeGen.class || visitorCls == RenameVisitor.class) {
return;
}
try {
checkMethod(mth);
} catch (Exception e) {
throw new JadxRuntimeException("Debug check failed after visitor: " + visitorCls.getSimpleName(), e);
}
}
public static void checkMethod(MethodNode mth) {
List<BlockNode> basicBlocks = mth.getBasicBlocks();
if (Utils.isEmpty(basicBlocks)) {
return;
}
for (BlockNode block : basicBlocks) {
for (InsnNode insn : block.getInstructions()) {
checkInsn(mth, insn);
}
}
// checkPHI(mth);
}
private static void checkInsn(MethodNode mth, InsnNode insn) {
if (insn.getResult() != null) {
checkVar(mth, insn, insn.getResult());
}
for (InsnArg arg : insn.getArguments()) {
if (arg instanceof RegisterArg) {
checkVar(mth, insn, (RegisterArg) arg);
} else if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
checkInsn(mth, wrapInsn);
}
}
if (insn instanceof TernaryInsn) {
TernaryInsn ternaryInsn = (TernaryInsn) insn;
for (RegisterArg arg : ternaryInsn.getCondition().getRegisterArgs()) {
checkVar(mth, insn, arg);
}
}
}
private static void checkVar(MethodNode mth, InsnNode insn, RegisterArg reg) {
checkRegisterArg(mth, reg);
SSAVar sVar = reg.getSVar();
if (sVar == null) {
if (Utils.notEmpty(mth.getSVars())) {
throw new JadxRuntimeException("Null SSA var in " + insn + ", mth: " + mth);
}
return;
}
List<RegisterArg> useList = sVar.getUseList();
boolean assignReg = insn.getResult() == reg;
if (!assignReg && !Utils.containsInListByRef(useList, reg)) {
throw new JadxRuntimeException("Incorrect use list in ssa var: " + sVar + ", register not listed.\n insn: " + insn);
}
for (RegisterArg useArg : useList) {
checkRegisterArg(mth, useArg);
}
}
private static void checkRegisterArg(MethodNode mth, RegisterArg reg) {
InsnNode parentInsn = reg.getParentInsn();
if (parentInsn == null) {
if (reg.contains(AFlag.METHOD_ARGUMENT)) {
return;
}
throw new JadxRuntimeException("Null parentInsn for reg: " + reg);
}
if (!parentInsn.contains(AFlag.HIDDEN)) {
if (parentInsn.getResult() != reg && !parentInsn.containsArg(reg)) {
throw new JadxRuntimeException("Incorrect parentInsn: " + parentInsn + ", must contains arg: " + reg);
}
BlockNode parentInsnBlock = BlockUtils.getBlockByInsn(mth, parentInsn);
if (parentInsnBlock == null) {
parentInsnBlock = BlockUtils.getBlockByInsn(mth, parentInsn);
throw new JadxRuntimeException("Parent insn not found in blocks tree for: " + reg + ",\n insn: " + parentInsn);
}
}
}
private static void checkPHI(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
List<PhiInsn> phis = new ArrayList<>();
for (InsnNode insn : block.getInstructions()) {
if (insn.getType() == InsnType.PHI) {
PhiInsn phi = (PhiInsn) insn;
phis.add(phi);
if (phi.getArgsCount() == 0) {
throw new JadxRuntimeException("No args and binds in PHI");
}
for (InsnArg arg : insn.getArguments()) {
if (arg instanceof RegisterArg) {
BlockNode b = phi.getBlockByArg((RegisterArg) arg);
if (b == null) {
throw new JadxRuntimeException("Predecessor block not found");
}
} else {
throw new JadxRuntimeException("Not register in phi insn");
}
}
}
}
PhiListAttr phiListAttr = block.get(AType.PHI_LIST);
if (phiListAttr == null) {
if (!phis.isEmpty()) {
throw new JadxRuntimeException("Missing PHI list attribute");
}
} else {
List<PhiInsn> phiList = phiListAttr.getList();
if (phiList.isEmpty()) {
throw new JadxRuntimeException("Empty PHI list attribute");
}
if (!phis.containsAll(phiList) || !phiList.containsAll(phis)) {
throw new JadxRuntimeException("Instructions not match");
}
}
}
for (SSAVar ssaVar : mth.getSVars()) {
for (PhiInsn usedInPhi : ssaVar.getUsedInPhi()) {
boolean found = false;
for (RegisterArg useArg : ssaVar.getUseList()) {
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn != null && parentInsn == usedInPhi) {
found = true;
}
}
if (!found) {
throw new JadxRuntimeException("Used in phi incorrect");
}
}
}
}
}
@@ -1,9 +1,7 @@
package jadx.core.utils;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -14,13 +12,6 @@ import org.slf4j.LoggerFactory;
import jadx.core.codegen.CodeWriter;
import jadx.core.codegen.InsnGen;
import jadx.core.codegen.MethodGen;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer;
@@ -34,7 +25,6 @@ import jadx.core.dex.visitors.regions.DepthRegionTraversal;
import jadx.core.dex.visitors.regions.TracedRegionVisitor;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@Deprecated
@TestOnly
@@ -45,7 +35,7 @@ public class DebugUtils {
}
public static void dump(MethodNode mth) {
dump(mth, "");
dump(mth, "dump");
}
public static void dumpRaw(MethodNode mth, String desc) {
@@ -53,6 +43,15 @@ public class DebugUtils {
DotGraphVisitor.dumpRaw().save(out, mth);
}
public static IDexTreeVisitor dumpRawVisitor(String desc) {
return new AbstractVisitor() {
@Override
public void visit(MethodNode mth) throws JadxException {
dumpRaw(mth, desc);
}
};
}
public static void dump(MethodNode mth, String desc) {
File out = new File("test-graph-" + desc + "-tmp");
DotGraphVisitor.dump().save(out, mth);
@@ -127,88 +126,6 @@ public class DebugUtils {
}
}
public static void checkSSA(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) {
if (insn.getResult() != null) {
checkSSAVar(mth, insn, insn.getResult());
}
for (InsnArg arg : insn.getArguments()) {
if (arg instanceof RegisterArg) {
checkSSAVar(mth, insn, (RegisterArg) arg);
}
}
}
}
// checkPHI(mth);
}
private static void checkSSAVar(MethodNode mth, InsnNode insn, RegisterArg reg) {
SSAVar sVar = reg.getSVar();
if (sVar == null) {
throw new JadxRuntimeException("Null SSA var in " + insn + ", mth: " + mth);
}
for (RegisterArg useArg : sVar.getUseList()) {
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn != null && !parentInsn.containsArg(useArg)) {
throw new JadxRuntimeException("Incorrect use info in PHI insn");
}
}
}
private static void checkPHI(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
List<PhiInsn> phis = new ArrayList<>();
for (InsnNode insn : block.getInstructions()) {
if (insn.getType() == InsnType.PHI) {
PhiInsn phi = (PhiInsn) insn;
phis.add(phi);
if (phi.getArgsCount() == 0) {
throw new JadxRuntimeException("No args and binds in PHI");
}
for (InsnArg arg : insn.getArguments()) {
if (arg instanceof RegisterArg) {
BlockNode b = phi.getBlockByArg((RegisterArg) arg);
if (b == null) {
throw new JadxRuntimeException("Predecessor block not found");
}
} else {
throw new JadxRuntimeException("Not register in phi insn");
}
}
}
}
PhiListAttr phiListAttr = block.get(AType.PHI_LIST);
if (phiListAttr == null) {
if (!phis.isEmpty()) {
throw new JadxRuntimeException("Missing PHI list attribute");
}
} else {
List<PhiInsn> phiList = phiListAttr.getList();
if (phiList.isEmpty()) {
throw new JadxRuntimeException("Empty PHI list attribute");
}
if (!phis.containsAll(phiList) || !phiList.containsAll(phis)) {
throw new JadxRuntimeException("Instructions not match");
}
}
}
for (SSAVar ssaVar : mth.getSVars()) {
for (PhiInsn usedInPhi : ssaVar.getUsedInPhi()) {
boolean found = false;
for (RegisterArg useArg : ssaVar.getUseList()) {
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn != null && parentInsn == usedInPhi) {
found = true;
}
}
if (!found) {
throw new JadxRuntimeException("Used in phi incorrect");
}
}
}
}
public static void printMap(String desc, Map<?, ?> map) {
LOG.debug("Map of {}, size: {}", desc, map.size());
for (Map.Entry<?, ?> entry : map.entrySet()) {
@@ -54,6 +54,8 @@ public class InsnRemover {
public void addWithoutUnbind(InsnNode insn) {
toRemove.add(insn);
insn.add(AFlag.REMOVE);
insn.add(AFlag.DONT_GENERATE);
}
public void perform() {
@@ -65,12 +67,19 @@ public class InsnRemover {
remove(mth, remInsn);
}
} else {
removeAll(instrList, toRemove);
removeAll(mth, instrList, toRemove);
}
toRemove.clear();
}
public static void unbindInsn(@Nullable MethodNode mth, InsnNode insn) {
unbindAllArgs(mth, insn);
unbindResult(mth, insn);
insn.add(AFlag.REMOVE);
insn.add(AFlag.DONT_GENERATE);
}
public static void unbindAllArgs(@Nullable MethodNode mth, InsnNode insn) {
for (InsnArg arg : insn.getArguments()) {
unbindArgUsage(mth, arg);
}
@@ -81,16 +90,17 @@ public class InsnRemover {
}
}
}
unbindResult(mth, insn);
insn.add(AFlag.REMOVE);
insn.add(AFlag.DONT_GENERATE);
}
public static void unbindResult(@Nullable MethodNode mth, InsnNode insn) {
RegisterArg r = insn.getResult();
if (r != null && r.getSVar() != null && mth != null) {
if (r != null && mth != null) {
SSAVar ssaVar = r.getSVar();
removeSsaVar(mth, ssaVar);
if (ssaVar != null && ssaVar.getAssign() == insn.getResult()) {
removeSsaVar(mth, ssaVar);
}
}
}
@@ -127,12 +137,6 @@ public class InsnRemover {
}
}
public static void unbindAllArgs(@Nullable MethodNode mth, InsnNode insn) {
for (InsnArg arg : insn.getArguments()) {
unbindArgUsage(mth, arg);
}
}
public static void unbindArgUsage(@Nullable MethodNode mth, InsnArg arg) {
if (arg instanceof RegisterArg) {
RegisterArg reg = (RegisterArg) arg;
@@ -148,7 +152,7 @@ public class InsnRemover {
// Don't use 'instrList.removeAll(toRemove)' because it will remove instructions by content
// and here can be several instructions with same content
private static void removeAll(List<InsnNode> insns, List<InsnNode> toRemove) {
private static void removeAll(MethodNode mth, List<InsnNode> insns, List<InsnNode> toRemove) {
if (toRemove == null || toRemove.isEmpty()) {
return;
}
@@ -158,6 +162,7 @@ public class InsnRemover {
for (int i = 0; i < insnsCount; i++) {
if (insns.get(i) == rem) {
insns.remove(i);
unbindInsn(mth, rem);
found = true;
break;
}
@@ -193,7 +198,7 @@ public class InsnRemover {
for (InsnNode insn : insns) {
unbindInsn(mth, insn);
}
removeAll(block.getInstructions(), insns);
removeAll(mth, block.getInstructions(), insns);
}
public static void remove(MethodNode mth, BlockNode block, int index) {
@@ -11,6 +11,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
@@ -28,9 +29,11 @@ public class Utils {
}
public static String cleanObjectName(String obj) {
int last = obj.length() - 1;
if (obj.charAt(0) == 'L' && obj.charAt(last) == ';') {
return obj.substring(1, last).replace('/', '.');
if (obj.charAt(0) == 'L') {
int last = obj.length() - 1;
if (obj.charAt(last) == ';') {
return obj.substring(1, last).replace('/', '.');
}
}
return obj;
}
@@ -39,6 +42,14 @@ public class Utils {
return 'L' + obj.replace('.', '/') + ';';
}
public static String strRepeat(String str, int count) {
StringBuilder sb = new StringBuilder(str.length() * count);
for (int i = 0; i < count; i++) {
sb.append(str);
}
return sb.toString();
}
public static String listToString(Iterable<?> objects) {
return listToString(objects, ", ");
}
@@ -60,6 +71,10 @@ public class Utils {
return sb.toString();
}
public static <T> void listToString(StringBuilder sb, Iterable<T> objects, String joiner) {
listToString(sb, objects, joiner, Objects::toString);
}
public static <T> void listToString(StringBuilder sb, Iterable<T> objects, String joiner, Function<T, String> toStr) {
if (objects == null) {
return;
@@ -165,7 +180,19 @@ public class Utils {
return result;
}
public static <T> int indexInList(List<T> list, T element) {
public static <T> boolean containsInListByRef(List<T> list, T element) {
if (isEmpty(list)) {
return false;
}
for (T t : list) {
if (t == element) {
return true;
}
}
return false;
}
public static <T> int indexInListByRef(List<T> list, T element) {
if (list == null || list.isEmpty()) {
return -1;
}
@@ -219,4 +246,20 @@ public class Utils {
}
return list.get(list.size() - 1);
}
public static <T> boolean isEmpty(Collection<T> col) {
return col == null || col.isEmpty();
}
public static <T> boolean notEmpty(Collection<T> col) {
return col != null && !col.isEmpty();
}
public static <T> boolean isEmpty(T[] arr) {
return arr == null || arr.length == 0;
}
public static <T> boolean notEmpty(T[] arr) {
return arr != null && arr.length != 0;
}
}
@@ -37,6 +37,7 @@ import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.DebugChecks;
import jadx.core.utils.files.FileUtils;
import jadx.core.xmlgen.ResourceStorage;
import jadx.core.xmlgen.entry.ResourceEntry;
@@ -96,6 +97,9 @@ public abstract class IntegrationTest extends TestUtils {
AType.JADX_ERROR,
AType.JADX_WARN,
AType.COMMENTS));
// enable debug checks
DebugChecks.checksEnabled = true;
}
@BeforeEach
@@ -0,0 +1,17 @@
package jadx.tests.api.utils.assertj;
import org.assertj.core.api.Assertions;
import jadx.api.ICodeInfo;
import jadx.core.dex.nodes.ClassNode;
public class JadxAssertions extends Assertions {
public static JadxClassNodeAssertions assertThat(ClassNode actual) {
return new JadxClassNodeAssertions(actual);
}
public static JadxCodeAssertions assertThat(ICodeInfo actual) {
return new JadxCodeAssertions(actual.getCodeStr());
}
}
@@ -0,0 +1,21 @@
package jadx.tests.api.utils.assertj;
import org.assertj.core.api.AbstractAssert;
import jadx.api.ICodeInfo;
import jadx.core.dex.nodes.ClassNode;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class JadxClassNodeAssertions extends AbstractAssert<JadxClassNodeAssertions, ClassNode> {
public JadxClassNodeAssertions(ClassNode cls) {
super(cls, JadxClassNodeAssertions.class);
}
public JadxCodeAssertions code() {
isNotNull();
ICodeInfo code = actual.getCode();
assertThat(code).isNotNull();
return new JadxCodeAssertions(code.getCodeStr());
}
}
@@ -0,0 +1,54 @@
package jadx.tests.api.utils.assertj;
import org.assertj.core.api.AbstractStringAssert;
import jadx.core.codegen.CodeWriter;
import jadx.tests.api.utils.TestUtils;
public class JadxCodeAssertions extends AbstractStringAssert<JadxCodeAssertions> {
public JadxCodeAssertions(String code) {
super(code, JadxCodeAssertions.class);
}
public JadxCodeAssertions countString(int count, String substring) {
isNotNull();
int actualCount = TestUtils.count(actual, substring);
if (actualCount != count) {
failWithMessage("Expected a substring <%s> count <%d> but was <%d>", substring, count, actualCount);
}
return this;
}
public JadxCodeAssertions notContainsLine(int indent, String line) {
return countLine(0, indent, line);
}
public JadxCodeAssertions containsLine(int indent, String line) {
return countLine(1, indent, line);
}
private JadxCodeAssertions countLine(int count, int indent, String line) {
String indentStr = TestUtils.indent(indent);
return countString(count, indentStr + line);
}
public JadxCodeAssertions containsLines(String... lines) {
return containsLines(0, lines);
}
public JadxCodeAssertions containsLines(int commonIndent, String... lines) {
if (lines.length == 1) {
return containsLine(commonIndent, lines[0]);
}
String indent = TestUtils.indent(commonIndent);
StringBuilder sb = new StringBuilder();
for (String line : lines) {
if (!line.isEmpty()) {
sb.append(indent);
sb.append(line);
}
sb.append(CodeWriter.NL);
}
return countString(1, sb.toString());
}
}
@@ -0,0 +1,52 @@
package jadx.tests.integration.inner;
import org.junit.jupiter.api.Test;
import jadx.NotYetImplemented;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestAnonymousClass3a extends IntegrationTest {
public static class TestCls {
public static class Inner {
private int f;
private int r;
public void test() {
new Runnable() {
@Override
public void run() {
int a = --Inner.this.f;
p(a);
}
public void p(int a) {
Inner.this.r = a;
}
}.run();
}
}
public void check() {
Inner inner = new Inner();
inner.f = 2;
inner.test();
assertThat(inner.f).isEqualTo(1);
assertThat(inner.r).isEqualTo(1);
}
}
@Test
@NotYetImplemented
public void test() {
assertThat(getClassNode(TestCls.class))
.code()
.doesNotContain("synthetic")
.doesNotContain("AnonymousClass_")
.doesNotContain("unused = ")
.containsLine(4, "public void run() {")
.containsLine(3, "}.run();");
}
}
@@ -6,6 +6,7 @@ import java.util.Map;
import org.junit.jupiter.api.Test;
import jadx.NotYetImplemented;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
@@ -72,6 +73,7 @@ public class TestAnonymousClass5 extends IntegrationTest {
}
@Test
@NotYetImplemented
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
@@ -5,16 +5,13 @@ import java.util.List;
import org.junit.jupiter.api.Test;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestVariablesUsageWithLoops extends IntegrationTest {
public static class TestEnhancedFor {
public void test() {
List<Object> list;
synchronized (this) {
@@ -28,14 +25,13 @@ public class TestVariablesUsageWithLoops extends IntegrationTest {
@Test
public void testEnhancedFor() {
ClassNode cls = getClassNode(TestEnhancedFor.class);
String code = cls.getCode().toString();
assertThat(code, containsString(" list = new ArrayList<>"));
assertThat(getClassNode(TestEnhancedFor.class))
.code()
.containsLine(2, "synchronized (this) {")
.containsLine(3, "list = new ArrayList<>");
}
public static class TestForLoop {
@SuppressWarnings("rawtypes")
public void test() {
List<Object> list;
@@ -50,9 +46,9 @@ public class TestVariablesUsageWithLoops extends IntegrationTest {
@Test
public void testForLoop() {
ClassNode cls = getClassNode(TestForLoop.class);
String code = cls.getCode().toString();
assertThat(code, containsString(" list = new ArrayList<>"));
assertThat(getClassNode(TestEnhancedFor.class))
.code()
.containsLine(2, "synchronized (this) {")
.containsLine(3, "list = new ArrayList<>");
}
}