diff --git a/jadx-core/src/main/java/jadx/core/codegen/ConditionGen.java b/jadx-core/src/main/java/jadx/core/codegen/ConditionGen.java index a8fd45d42efb82bc64a157e989ec39104b8f046a..4281719f1db05288c8bd792bebafe6f6c71751be 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ConditionGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ConditionGen.java @@ -42,7 +42,7 @@ public class ConditionGen extends InsnGen { super(insnGen.mgen, insnGen.fallback); } - void add(ICodeWriter code, IfCondition condition) throws CodegenException { + public void add(ICodeWriter code, IfCondition condition) throws CodegenException { add(code, new CondStack(), condition); } diff --git a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java index f451e2631231883d2d7dba8b90def57254451065..4184318727de2cd36081440c545d3bda239f601d 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java @@ -161,7 +161,7 @@ public class RegionGen extends InsnGen { } public void makeLoop(LoopRegion region, ICodeWriter code) throws CodegenException { - code.startLineWithNum(region.getConditionSourceLine()); + code.startLineWithNum(region.getSourceLine()); LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL); if (labelAttr != null) { code.add(mgen.getNameGen().getLoopLabel(labelAttr)).add(": "); @@ -213,7 +213,7 @@ public class RegionGen extends InsnGen { code.add("do {"); CodeGenUtils.addCodeComments(code, mth, condInsn); makeRegionIndent(code, region.getBody()); - code.startLineWithNum(region.getConditionSourceLine()); + code.startLineWithNum(region.getSourceLine()); code.add("} while ("); conditionGen.add(code, condition); code.add(");"); diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/JadxCommentsAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/JadxCommentsAttr.java index 29f2ef8c1772ef50ecd250c984712c32cda88eaf..a2eb1f22a7f77c10a0bfe449ef0ddc69987281a6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/JadxCommentsAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/JadxCommentsAttr.java @@ -11,6 +11,7 @@ import jadx.api.CommentsLevel; import jadx.api.plugins.input.data.attributes.IJadxAttrType; import jadx.api.plugins.input.data.attributes.IJadxAttribute; import jadx.core.dex.attributes.AType; +import jadx.core.utils.Utils; public class JadxCommentsAttr implements IJadxAttribute { @@ -44,4 +45,12 @@ public class JadxCommentsAttr implements IJadxAttribute { public IJadxAttrType getAttrType() { return AType.JADX_COMMENTS; } + + @Override + public String toString() { + return "JadxCommentsAttr{\n " + + Utils.listToString(comments.entrySet(), "\n ", + e -> e.getKey() + ": \n -> " + Utils.listToString(e.getValue(), "\n -> ")) + + '}'; + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/IConditionRegion.java b/jadx-core/src/main/java/jadx/core/dex/nodes/IConditionRegion.java new file mode 100644 index 0000000000000000000000000000000000000000..009a8a7b4af778283425abbc4ef9e71c996768e2 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/IConditionRegion.java @@ -0,0 +1,26 @@ +package jadx.core.dex.nodes; + +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +import jadx.core.dex.regions.conditions.IfCondition; + +public interface IConditionRegion extends IRegion { + + @Nullable + IfCondition getCondition(); + + /** + * Blocks merged into condition + * Needed for backtracking + * TODO: merge into condition object ??? + */ + List getConditionBlocks(); + + void invertCondition(); + + boolean simplifyCondition(); + + int getConditionSourceLine(); +} 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 f546ea2f655fdd5fa345bc2d109c3cf73f23e5d0..d68d3b40ae8d78b288409ed9b3940126438fe0e1 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 @@ -120,12 +120,7 @@ public class InsnNode extends LineAttrNode { if (getArgsCount() == 0) { return false; } - for (InsnArg insnArg : arguments) { - if (insnArg == arg || arg.sameRegAndSVar(insnArg)) { - return true; - } - } - return false; + return InsnUtils.containsVar(arguments, arg); } /** diff --git a/jadx-core/src/main/java/jadx/core/dex/regions/conditions/ConditionRegion.java b/jadx-core/src/main/java/jadx/core/dex/regions/conditions/ConditionRegion.java new file mode 100644 index 0000000000000000000000000000000000000000..2756f72b2df68c5bf8eeda72a0be8eb63cbf513f --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/regions/conditions/ConditionRegion.java @@ -0,0 +1,87 @@ +package jadx.core.dex.regions.conditions; + +import java.util.Collections; +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.IConditionRegion; +import jadx.core.dex.nodes.IRegion; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.regions.AbstractRegion; +import jadx.core.utils.BlockUtils; + +public abstract class ConditionRegion extends AbstractRegion implements IConditionRegion { + + @Nullable + private IfCondition condition; + private List conditionBlocks = Collections.emptyList(); + + public ConditionRegion(IRegion parent) { + super(parent); + } + + @Override + @Nullable + public IfCondition getCondition() { + return condition; + } + + @Override + public List getConditionBlocks() { + return conditionBlocks; + } + + @Override + public void invertCondition() { + if (condition != null) { + condition = IfCondition.invert(condition); + } + } + + @Override + public boolean simplifyCondition() { + if (condition == null) { + return false; + } + IfCondition updated = IfCondition.simplify(condition); + if (updated != condition) { + condition = updated; + return true; + } + return false; + } + + @Override + public int getConditionSourceLine() { + for (BlockNode block : conditionBlocks) { + InsnNode lastInsn = BlockUtils.getLastInsn(block); + if (lastInsn != null) { + int sourceLine = lastInsn.getSourceLine(); + if (sourceLine != 0) { + return sourceLine; + } + } + } + return 0; + } + + /** + * Prefer way for update condition info + */ + public void updateCondition(IfInfo info) { + this.condition = info.getCondition(); + this.conditionBlocks = info.getMergedBlocks(); + } + + public void updateCondition(IfCondition condition, List conditionBlocks) { + this.condition = condition; + this.conditionBlocks = conditionBlocks; + } + + public void updateCondition(BlockNode block) { + this.condition = IfCondition.fromIfBlock(block); + this.conditionBlocks = Collections.singletonList(block); + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfCondition.java b/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfCondition.java index 1d5d49acb24c2d00b769caa097e7c614d7bb5fe4..2e0ed00015c8a4c8bfc98411afa262857e7d3cb3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfCondition.java +++ b/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfCondition.java @@ -272,6 +272,12 @@ public final class IfCondition extends AttrNode { } } + public List collectInsns() { + List list = new ArrayList<>(); + visitInsns(list::add); + return list; + } + @Nullable public InsnNode getFirstInsn() { if (mode == Mode.COMPARE) { diff --git a/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfRegion.java b/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfRegion.java index d479fe71cee88e012ba21f1c8d8ec70b256070e2..15e800039af02ef809b0226ae622d2c04c3e139f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfRegion.java +++ b/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfRegion.java @@ -3,7 +3,6 @@ package jadx.core.dex.regions.conditions; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Set; import jadx.api.ICodeWriter; import jadx.core.codegen.RegionGen; @@ -11,16 +10,9 @@ import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.IBranchRegion; import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IRegion; -import jadx.core.dex.nodes.InsnNode; -import jadx.core.dex.regions.AbstractRegion; -import jadx.core.utils.BlockUtils; import jadx.core.utils.exceptions.CodegenException; -public final class IfRegion extends AbstractRegion implements IBranchRegion { - - private List conditionBlocks; - - private IfCondition condition; +public final class IfRegion extends ConditionRegion implements IBranchRegion { private IContainer thenRegion; private IContainer elseRegion; @@ -28,14 +20,6 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion { super(parent); } - public IfCondition getCondition() { - return condition; - } - - public void setCondition(IfCondition condition) { - this.condition = condition; - } - public IContainer getThenRegion() { return thenRegion; } @@ -52,31 +36,8 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion { this.elseRegion = elseRegion; } - public List getConditionBlocks() { - return conditionBlocks; - } - - public void setConditionBlocks(List conditionBlocks) { - this.conditionBlocks = conditionBlocks; - } - - public void setConditionBlocks(Set conditionBlocks) { - List list = new ArrayList<>(conditionBlocks); - Collections.sort(list); - this.conditionBlocks = list; - } - - public boolean simplifyCondition() { - IfCondition cond = IfCondition.simplify(condition); - if (cond != condition) { - condition = cond; - return true; - } - return false; - } - public void invert() { - condition = IfCondition.invert(condition); + invertCondition(); // swap regions IContainer tmp = thenRegion; thenRegion = elseRegion; @@ -84,20 +45,12 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion { } public int getSourceLine() { - for (BlockNode block : conditionBlocks) { - InsnNode lastInsn = BlockUtils.getLastInsn(block); - if (lastInsn != null) { - int sourceLine = lastInsn.getSourceLine(); - if (sourceLine != 0) { - return sourceLine; - } - } - } - return 0; + return getConditionSourceLine(); } @Override public List getSubBlocks() { + List conditionBlocks = getConditionBlocks(); List all = new ArrayList<>(conditionBlocks.size() + 2); all.addAll(conditionBlocks); if (thenRegion != null) { @@ -151,6 +104,6 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion { @Override public String toString() { - return "IF " + conditionBlocks + " THEN: " + thenRegion + " ELSE: " + elseRegion; + return "IF " + getConditionBlocks() + " THEN: " + thenRegion + " ELSE: " + elseRegion; } } 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 265c8a12531e921ef00239e32daf1aa6e6d21a58..fe5a94b16a8639447a7768e880a254417494c2ce 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 @@ -1,7 +1,6 @@ package jadx.core.dex.regions.loops; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import org.jetbrains.annotations.Nullable; @@ -9,55 +8,45 @@ import org.jetbrains.annotations.Nullable; import jadx.api.ICodeWriter; import jadx.core.codegen.RegionGen; import jadx.core.dex.attributes.nodes.LoopInfo; -import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.InsnNode; -import jadx.core.dex.regions.AbstractRegion; +import jadx.core.dex.regions.conditions.ConditionRegion; import jadx.core.dex.regions.conditions.IfCondition; import jadx.core.utils.BlockUtils; +import jadx.core.utils.InsnUtils; import jadx.core.utils.exceptions.CodegenException; -public final class LoopRegion extends AbstractRegion { +public final class LoopRegion extends ConditionRegion { private final LoopInfo info; - /** - * loop header contains one 'if' insn, equals null for infinite loop - */ - @Nullable - private IfCondition condition; - private final BlockNode conditionBlock; - // instruction which must be executed before condition in every loop - private BlockNode preCondition; - private IRegion body; private final boolean conditionAtEnd; + private final @Nullable BlockNode header; + // instruction which must be executed before condition in every loop + private @Nullable BlockNode preCondition; + private IRegion body; private LoopType type; public LoopRegion(IRegion parent, LoopInfo info, @Nullable BlockNode header, boolean reversed) { super(parent); this.info = info; - this.conditionBlock = header; - this.condition = IfCondition.fromIfBlock(header); + this.header = header; this.conditionAtEnd = reversed; + if (header != null) { + updateCondition(header); + } } public LoopInfo getInfo() { return info; } - public IfCondition getCondition() { - return condition; - } - - public void setCondition(IfCondition condition) { - this.condition = condition; - } - + @Nullable public BlockNode getHeader() { - return conditionBlock; + return header; } public IRegion getBody() { @@ -79,10 +68,6 @@ public final class LoopRegion extends AbstractRegion { this.preCondition = preCondition; } - private IfNode getIfInsn() { - return (IfNode) BlockUtils.getLastInsn(conditionBlock); - } - /** * Check if pre-conditions can be inlined into loop condition */ @@ -91,7 +76,14 @@ public final class LoopRegion extends AbstractRegion { if (insns.isEmpty()) { return true; } - IfNode ifInsn = getIfInsn(); + IfCondition condition = getCondition(); + if (condition == null) { + return false; + } + List conditionArgs = condition.getRegisterArgs(); + if (conditionArgs.isEmpty()) { + return false; + } int size = insns.size(); for (int i = 0; i < size; i++) { InsnNode insn = insns.get(i); @@ -110,7 +102,7 @@ public final class LoopRegion extends AbstractRegion { } } // or in if insn - if (!found && ifInsn.containsVar(res)) { + if (!found && InsnUtils.containsVar(conditionArgs, res)) { found = true; } if (!found) { @@ -124,8 +116,8 @@ public final class LoopRegion extends AbstractRegion { * Move all preCondition block instructions before conditionBlock instructions */ public void mergePreCondition() { - if (preCondition != null && conditionBlock != null) { - List condInsns = conditionBlock.getInstructions(); + if (preCondition != null && header != null) { + List condInsns = header.getInstructions(); List preCondInsns = preCondition.getInstructions(); preCondInsns.addAll(condInsns); condInsns.clear(); @@ -135,9 +127,13 @@ public final class LoopRegion extends AbstractRegion { } } - public int getConditionSourceLine() { - InsnNode lastInsn = BlockUtils.getLastInsn(conditionBlock); - return lastInsn == null ? 0 : lastInsn.getSourceLine(); + public int getSourceLine() { + InsnNode lastInsn = BlockUtils.getLastInsn(header); + int headerLine = lastInsn == null ? 0 : lastInsn.getSourceLine(); + if (headerLine != 0) { + return headerLine; + } + return getConditionSourceLine(); } public LoopType getType() { @@ -150,17 +146,15 @@ public final class LoopRegion extends AbstractRegion { @Override public List getSubBlocks() { - List all = new ArrayList<>(3); + List all = new ArrayList<>(2 + getConditionBlocks().size()); if (preCondition != null) { all.add(preCondition); } - if (conditionBlock != null) { - all.add(conditionBlock); - } + all.addAll(getConditionBlocks()); if (body != null) { all.add(body); } - return Collections.unmodifiableList(all); + return all; } @Override 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 2a715f28bc56df3de669a50109ab0863947effd7..9cfe5a80351bc1559df5997d6becdfe436540156 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 @@ -216,7 +216,7 @@ public class RegionMaker { // invert loop condition if 'then' points to exit condInfo = IfInfo.invert(condInfo); } - loopRegion.setCondition(condInfo.getCondition()); + loopRegion.updateCondition(condInfo); exitBlocks.removeAll(condInfo.getMergedBlocks()); if (!exitBlocks.isEmpty()) { @@ -720,8 +720,7 @@ public class RegionMaker { confirmMerge(currentIf); IfRegion ifRegion = new IfRegion(currentRegion); - ifRegion.setCondition(currentIf.getCondition()); - ifRegion.setConditionBlocks(currentIf.getMergedBlocks()); + ifRegion.updateCondition(currentIf); currentRegion.getSubBlocks().add(ifRegion); BlockNode outBlock = currentIf.getOutBlock(); 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 ca71199710eeea442875524a6bc4130a7e74c6e1..de6dee4d47a1a9d51250e3fd139f0d6060cad456 100644 --- a/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java @@ -18,6 +18,7 @@ import org.slf4j.LoggerFactory; import jadx.api.ICodeWriter; import jadx.api.impl.SimpleCodeWriter; +import jadx.core.codegen.ConditionGen; import jadx.core.codegen.InsnGen; import jadx.core.codegen.MethodGen; import jadx.core.dex.attributes.AType; @@ -31,6 +32,8 @@ import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; import jadx.core.dex.regions.Region; +import jadx.core.dex.regions.conditions.IfCondition; +import jadx.core.dex.regions.loops.LoopRegion; import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.DotGraphVisitor; import jadx.core.dex.visitors.IDexTreeVisitor; @@ -139,6 +142,7 @@ public class DebugUtils { private static void printRegion(MethodNode mth, IRegion region, ICodeWriter cw, String indent, boolean printInsns) { printWithAttributes(cw, indent, region.toString(), region); indent += "| "; + printRegionSpecificInfo(cw, indent, mth, region, printInsns); for (IContainer container : region.getSubBlocks()) { if (container instanceof IRegion) { printRegion(mth, (IRegion) container, cw, indent, printInsns); @@ -152,6 +156,23 @@ public class DebugUtils { } } + private static void printRegionSpecificInfo(ICodeWriter cw, String indent, + MethodNode mth, IRegion region, boolean printInsns) { + if (region instanceof LoopRegion) { + LoopRegion loop = (LoopRegion) region; + IfCondition condition = loop.getCondition(); + if (printInsns && condition != null) { + ConditionGen conditionGen = new ConditionGen(new InsnGen(MethodGen.getFallbackMethodGen(mth), true)); + cw.startLine(indent).add("|> "); + try { + conditionGen.add(cw, condition); + } catch (Exception e) { + cw.startLine(indent).add(">!! ").add(condition.toString()); + } + } + } + } + private static void printInsns(MethodNode mth, ICodeWriter cw, String indent, IBlock block) { for (InsnNode insn : block.getInstructions()) { try { diff --git a/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java b/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java index 00ce506340f866a80a74881b7b4426749831fd67..f428d75af577ba9b9c52bc0aae2b8d1b63c9380e 100644 --- a/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java @@ -206,4 +206,16 @@ public class InsnUtils { insn.add(AFlag.DONT_GENERATE); return true; } + + public static boolean containsVar(List list, RegisterArg arg) { + if (list == null || list.isEmpty()) { + return false; + } + for (InsnArg insnArg : list) { + if (insnArg == arg || arg.sameRegAndSVar(insnArg)) { + return true; + } + } + return false; + } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesInInlinedAssign.java b/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesInInlinedAssign.java new file mode 100644 index 0000000000000000000000000000000000000000..b422f0854fdf1dc64bc2de6f417589505bc0c106 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesInInlinedAssign.java @@ -0,0 +1,30 @@ +package jadx.tests.integration.variables; + +import jadx.tests.api.IntegrationTest; +import jadx.tests.api.extensions.inputs.InputPlugin; +import jadx.tests.api.extensions.inputs.TestWithInputPlugins; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestVariablesInInlinedAssign extends IntegrationTest { + + public static class TestCls { + public final int test(final char[] s) { + int i; + for (i = 0; i < s.length; i++) { + final char c = s[i]; + if (c != 'a' && c != 'b') { + break; + } + } + return i; + } + } + + @TestWithInputPlugins({ InputPlugin.DEX, InputPlugin.JAVA }) + public void test() { + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("char c"); + } +}