refactor: additional checks for ssa vars and registers
This commit is contained in:
@@ -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();
|
||||
|
||||
+9
-13
@@ -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<>");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user