From 87504dd2cc306fae3dab43d89539d2d4ea6ce39e Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 24 Nov 2019 20:33:55 +0300 Subject: [PATCH] refactor: additional checks for ssa vars and registers --- build.gradle | 1 + .../java/jadx/core/dex/attributes/AFlag.java | 3 + .../attributes/nodes/SkipMethodArgsAttr.java | 2 +- .../jadx/core/dex/instructions/ArithNode.java | 6 +- .../core/dex/instructions/InvokeNode.java | 8 +- .../jadx/core/dex/instructions/PhiInsn.java | 2 +- .../core/dex/instructions/args/InsnArg.java | 10 +- .../dex/instructions/args/InsnWrapArg.java | 5 +- .../dex/instructions/args/RegisterArg.java | 4 + .../instructions/mods/ConstructorInsn.java | 15 +- .../dex/instructions/mods/TernaryInsn.java | 15 +- .../java/jadx/core/dex/nodes/InsnNode.java | 117 ++++++++---- .../core/dex/regions/conditions/Compare.java | 2 + .../core/dex/regions/loops/LoopRegion.java | 4 +- .../core/dex/visitors/ConstructorVisitor.java | 11 +- .../core/dex/visitors/DeboxingVisitor.java | 2 +- .../core/dex/visitors/DepthTraversal.java | 4 + .../dex/visitors/MethodInlineVisitor.java | 23 +-- .../jadx/core/dex/visitors/ModVisitor.java | 30 ++-- .../jadx/core/dex/visitors/ReSugarCode.java | 19 +- .../core/dex/visitors/SimplifyVisitor.java | 115 ++++++++---- .../visitors/regions/LoopRegionVisitor.java | 24 ++- .../dex/visitors/regions/RegionMaker.java | 5 +- .../core/dex/visitors/regions/TernaryMod.java | 3 + .../core/dex/visitors/shrink/ArgsInfo.java | 5 +- .../visitors/shrink/CodeShrinkVisitor.java | 8 + .../main/java/jadx/core/utils/BlockUtils.java | 20 ++- .../java/jadx/core/utils/DebugChecks.java | 169 ++++++++++++++++++ .../main/java/jadx/core/utils/DebugUtils.java | 103 ++--------- .../java/jadx/core/utils/InsnRemover.java | 29 +-- .../src/main/java/jadx/core/utils/Utils.java | 51 +++++- .../java/jadx/tests/api/IntegrationTest.java | 4 + .../api/utils/assertj/JadxAssertions.java | 17 ++ .../assertj/JadxClassNodeAssertions.java | 21 +++ .../api/utils/assertj/JadxCodeAssertions.java | 54 ++++++ .../inner/TestAnonymousClass3a.java | 52 ++++++ .../inner/TestAnonymousClass5.java | 2 + .../TestVariablesUsageWithLoops.java | 22 +-- 38 files changed, 703 insertions(+), 284 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/utils/DebugChecks.java create mode 100644 jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxAssertions.java create mode 100644 jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxClassNodeAssertions.java create mode 100644 jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass3a.java diff --git a/build.gradle b/build.gradle index 11d50dad4..3fc9fe41c 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java index 44ef4573d..ea1b65daf 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java @@ -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 } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/SkipMethodArgsAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/SkipMethodArgsAttr.java index 06248ed7b..0580e4bd4 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/SkipMethodArgsAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/SkipMethodArgsAttr.java @@ -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); } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java index 8e95e2562..a2717ff2e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java @@ -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 diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java index c44f715df..f0099e76f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java @@ -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; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/PhiInsn.java b/jadx-core/src/main/java/jadx/core/dex/instructions/PhiInsn.java index 07ab017d2..caf4ede54 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/PhiInsn.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/PhiInsn.java @@ -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(); diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java index 73b070677..869a50bb1 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java @@ -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()); } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnWrapArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnWrapArg.java index 660372430..70d61333a 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnWrapArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnWrapArg.java @@ -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 + ')'; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java index 5d3d42274..6434a3f71 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/mods/ConstructorInsn.java b/jadx-core/src/main/java/jadx/core/dex/instructions/mods/ConstructorInsn.java index 3febd47b3..7a590cbc1 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/mods/ConstructorInsn.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/mods/ConstructorInsn.java @@ -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; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/mods/TernaryInsn.java b/jadx-core/src/main/java/jadx/core/dex/instructions/mods/TernaryInsn.java index 4bf55f942..a16a5b99a 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/mods/TernaryInsn.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/mods/TernaryInsn.java @@ -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); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java index 6e2d90246..703c824c5 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java @@ -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 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(); + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/regions/conditions/Compare.java b/jadx-core/src/main/java/jadx/core/dex/regions/conditions/Compare.java index cd544828f..b740a1ce5 100644 --- a/jadx-core/src/main/java/jadx/core/dex/regions/conditions/Compare.java +++ b/jadx-core/src/main/java/jadx/core/dex/regions/conditions/Compare.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/regions/loops/LoopRegion.java b/jadx-core/src/main/java/jadx/core/dex/regions/loops/LoopRegion.java index 9b42f7bd3..51d424296 100644 --- a/jadx-core/src/main/java/jadx/core/dex/regions/loops/LoopRegion.java +++ b/jadx-core/src/main/java/jadx/core/dex/regions/loops/LoopRegion.java @@ -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) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java index 793652ced..e7a7a2bf7 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java @@ -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 = "(" + (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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DeboxingVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DeboxingVisitor.java index d37c41a6b..4dd248496 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DeboxingVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DeboxingVisitor.java @@ -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; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java index ea877712b..dfe8445c8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java @@ -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) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java index 2e29e1080..c8cdb960e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java @@ -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) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java index 538311238..1a36d71bc 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java @@ -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); } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ReSugarCode.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ReSugarCode.java index 323ecedd3..1cf107ca2 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ReSugarCode.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ReSugarCode.java @@ -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 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); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java index c693f3efd..2e8ae2ddf 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java @@ -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 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); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java index 8e2b1e338..ea6dc4399 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java @@ -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 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 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; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java index d4b831947..22e331b92 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java @@ -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); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java index c9d88ca7e..6fc11c4d0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java @@ -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); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/ArgsInfo.java b/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/ArgsInfo.java index 05f2b3b11..070ac31c8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/ArgsInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/ArgsInfo.java @@ -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 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()) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java index f2fad13b1..eedb71c6c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java @@ -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; diff --git a/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java b/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java index c4b2bd124..569dd5c07 100644 --- a/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java @@ -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 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; } } diff --git a/jadx-core/src/main/java/jadx/core/utils/DebugChecks.java b/jadx-core/src/main/java/jadx/core/utils/DebugChecks.java new file mode 100644 index 000000000..409d9ea41 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/utils/DebugChecks.java @@ -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 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 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 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 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 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"); + } + } + } + } + +} diff --git a/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java b/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java index 9733fcfdd..9cb673986 100644 --- a/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java @@ -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 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 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()) { diff --git a/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java b/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java index 066416f91..97baa0e1c 100644 --- a/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java +++ b/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java @@ -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 insns, List toRemove) { + private static void removeAll(MethodNode mth, List insns, List 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) { diff --git a/jadx-core/src/main/java/jadx/core/utils/Utils.java b/jadx-core/src/main/java/jadx/core/utils/Utils.java index 85cff356b..a775df368 100644 --- a/jadx-core/src/main/java/jadx/core/utils/Utils.java +++ b/jadx-core/src/main/java/jadx/core/utils/Utils.java @@ -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 void listToString(StringBuilder sb, Iterable objects, String joiner) { + listToString(sb, objects, joiner, Objects::toString); + } + public static void listToString(StringBuilder sb, Iterable objects, String joiner, Function toStr) { if (objects == null) { return; @@ -165,7 +180,19 @@ public class Utils { return result; } - public static int indexInList(List list, T element) { + public static boolean containsInListByRef(List list, T element) { + if (isEmpty(list)) { + return false; + } + for (T t : list) { + if (t == element) { + return true; + } + } + return false; + } + + public static int indexInListByRef(List 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 boolean isEmpty(Collection col) { + return col == null || col.isEmpty(); + } + + public static boolean notEmpty(Collection col) { + return col != null && !col.isEmpty(); + } + + public static boolean isEmpty(T[] arr) { + return arr == null || arr.length == 0; + } + + public static boolean notEmpty(T[] arr) { + return arr != null && arr.length != 0; + } } diff --git a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java index c755f5f2e..288108f55 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -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 diff --git a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxAssertions.java b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxAssertions.java new file mode 100644 index 000000000..a5a20421e --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxAssertions.java @@ -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()); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxClassNodeAssertions.java b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxClassNodeAssertions.java new file mode 100644 index 000000000..6c5834de6 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxClassNodeAssertions.java @@ -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 { + public JadxClassNodeAssertions(ClassNode cls) { + super(cls, JadxClassNodeAssertions.class); + } + + public JadxCodeAssertions code() { + isNotNull(); + ICodeInfo code = actual.getCode(); + assertThat(code).isNotNull(); + return new JadxCodeAssertions(code.getCodeStr()); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java new file mode 100644 index 000000000..67836f63b --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java @@ -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 { + 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()); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass3a.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass3a.java new file mode 100644 index 000000000..d30edaa13 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass3a.java @@ -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();"); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass5.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass5.java index faf5b6f59..c239f5e00 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass5.java +++ b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass5.java @@ -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(); diff --git a/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesUsageWithLoops.java b/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesUsageWithLoops.java index d5fb80100..11d4e9137 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesUsageWithLoops.java +++ b/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesUsageWithLoops.java @@ -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 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 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<>"); } }