diff --git a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java index d9b71adc6..930a869be 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -440,7 +440,7 @@ public class InsnGen { makeTernary((TernaryInsn) insn, code, state); break; - case ARGS: + case ONE_ARG: addArg(code, insn.getArg(0)); break; diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnType.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnType.java index a3e530d61..c43eebd5e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnType.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnType.java @@ -1,7 +1,6 @@ package jadx.core.dex.instructions; public enum InsnType { - NOP, // replacement for removed instructions CONST, CONST_STR, @@ -48,16 +47,24 @@ public enum InsnType { INVOKE, - // additional instructions + // *** Additional instructions *** + + // replacement for removed instructions + NOP, + + TERNARY, CONSTRUCTOR, + BREAK, CONTINUE, - STR_CONCAT, // strings concatenation + // strings concatenation + STR_CONCAT, - TERNARY, - ARGS, // just generate arguments + // just generate one argument + ONE_ARG, PHI, - NEW_MULTIDIM_ARRAY // TODO: now multidimensional arrays created using Array.newInstance function + // TODO: now multidimensional arrays created using Array.newInstance function + NEW_MULTIDIM_ARRAY } 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 256b52416..f25fc62d6 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 @@ -36,7 +36,7 @@ public class InsnNode extends LineAttrNode { } public static InsnNode wrapArg(InsnArg arg) { - InsnNode insn = new InsnNode(InsnType.ARGS, 1); + InsnNode insn = new InsnNode(InsnType.ONE_ARG, 1); insn.addArg(arg); return insn; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index 661bbb635..20be14dd0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -34,6 +34,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -519,7 +520,7 @@ public class MethodNode extends LineAttrNode implements ILoadable { return debugInfoOffset; } - public SSAVar makeNewSVar(int regNum, int[] versions, RegisterArg arg) { + public SSAVar makeNewSVar(int regNum, int[] versions, @NotNull RegisterArg arg) { SSAVar var = new SSAVar(regNum, versions[regNum], arg); versions[regNum]++; if (sVars.isEmpty()) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/BlockMakerVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/BlockMakerVisitor.java index d1f5774cb..bb34da218 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/BlockMakerVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/BlockMakerVisitor.java @@ -427,9 +427,9 @@ public class BlockMakerVisitor extends AbstractVisitor { return true; } } - // insert additional blocks if loop has several exits if (loops.size() == 1) { LoopInfo loop = loops.get(0); + // insert additional blocks for possible 'break' insertion List edges = loop.getExitEdges(); if (!edges.isEmpty()) { boolean change = false; @@ -444,6 +444,21 @@ public class BlockMakerVisitor extends AbstractVisitor { return true; } } + // insert additional blocks for possible 'continue' insertion + BlockNode loopEnd = loop.getEnd(); + if (loopEnd.getPredecessors().size() > 1) { + boolean change = false; + List nodes = new ArrayList(loopEnd.getPredecessors()); + for (BlockNode pred : nodes) { + if (!pred.contains(AFlag.SYNTHETIC)) { + insertBlockBetween(mth, pred, loopEnd); + change = true; + } + } + if (change) { + return true; + } + } } } return splitReturn(mth); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/IfMakerHelper.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/IfMakerHelper.java index c74c856c1..50f261ff1 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/IfMakerHelper.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/IfMakerHelper.java @@ -239,13 +239,19 @@ public class IfMakerHelper { } static void confirmMerge(IfInfo info) { - for (BlockNode block : info.getMergedBlocks()) { - block.add(AFlag.SKIP); + if (info.getMergedBlocks().size() > 1) { + for (BlockNode block : info.getMergedBlocks()) { + if (block != info.getIfBlock()) { + block.add(AFlag.SKIP); + } + } } - for (BlockNode block : info.getSkipBlocks()) { - block.add(AFlag.SKIP); + if (!info.getSkipBlocks().isEmpty()) { + for (BlockNode block : info.getSkipBlocks()) { + block.add(AFlag.SKIP); + } + info.getSkipBlocks().clear(); } - info.getSkipBlocks().clear(); } private static IfInfo getNextIf(IfInfo info, BlockNode block) { 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 2c52b3ce2..2b3dbcfb9 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 @@ -166,7 +166,9 @@ public class RegionMaker { LoopRegion loopRegion = makeLoopRegion(curRegion, loop, exitBlocks); if (loopRegion == null) { - return makeEndlessLoop(curRegion, stack, loop, loopStart); + BlockNode exit = makeEndlessLoop(curRegion, stack, loop, loopStart); + insertContinueInsns(loop); + return exit; } curRegion.getSubBlocks().add(loopRegion); IRegion outerRegion = stack.peekRegion(); @@ -233,6 +235,7 @@ public class RegionMaker { loopRegion.setBody(body); } stack.pop(); + insertContinueInsns(loop); return out; } @@ -359,9 +362,12 @@ public class RegionMaker { if (source.contains(AType.CATCH_BLOCK) && source.getSuccessors().size() == 2) { BlockNode other = BlockUtils.selectOther(loopExit, source.getSuccessors()); - if (other != null && other.contains(AType.EXC_HANDLER)) { - insertBlock = source; - confirm = true; + if (other != null) { + other = BlockUtils.skipSyntheticSuccessor(other); + if (other.contains(AType.EXC_HANDLER)) { + insertBlock = source; + confirm = true; + } } } } @@ -405,6 +411,45 @@ public class RegionMaker { return true; } + private static void insertContinueInsns(LoopInfo loop) { + BlockNode loopEnd = loop.getEnd(); + List predecessors = loopEnd.getPredecessors(); + if (predecessors.size() <= 1) { + return; + } + for (BlockNode pred : predecessors) { + if (!pred.contains(AFlag.SYNTHETIC) + || BlockUtils.checkLastInsnType(pred, InsnType.CONTINUE)) { + continue; + } + List nodes = pred.getPredecessors(); + if (nodes.isEmpty()) { + continue; + } + BlockNode codePred = nodes.get(0); + if (codePred.contains(AFlag.SKIP)) { + continue; + } + if (!isDominatedOnBlocks(codePred, predecessors)) { + for (BlockNode blockNode : predecessors) { + if (blockNode != pred && BlockUtils.isPathExists(codePred, blockNode)) { + InsnNode cont = new InsnNode(InsnType.CONTINUE, 0); + pred.getInstructions().add(cont); + } + } + } + } + } + + private static boolean isDominatedOnBlocks(BlockNode dom, List blocks) { + for (BlockNode node : blocks) { + if (!node.isDominator(dom)) { + return false; + } + } + return true; + } + private final Set cacheSet = new HashSet(); private BlockNode processMonitorEnter(IRegion curRegion, BlockNode block, InsnNode insn, RegionStack stack) { @@ -444,7 +489,7 @@ public class RegionMaker { * Traverse from monitor-enter thru successors and collect blocks contains monitor-exit */ private static void traverseMonitorExits(SynchronizedRegion region, InsnArg arg, BlockNode block, - Set exits, Set visited) { + Set exits, Set visited) { visited.add(block); for (InsnNode insn : block.getInstructions()) { if (insn.getType() == InsnType.MONITOR_EXIT 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 2ca04ca30..c05632d04 100644 --- a/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java @@ -439,4 +439,14 @@ public class BlockUtils { } return block == end; } + + /** + * Return successor of synthetic block or same block otherwise. + */ + public static BlockNode skipSyntheticSuccessor(BlockNode block) { + if (block.isSynthetic() && !block.getSuccessors().isEmpty()) { + return block.getSuccessors().get(0); + } + return block; + } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestContinueInLoop.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestContinueInLoop.java new file mode 100644 index 000000000..6c52e11c3 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/loops/TestContinueInLoop.java @@ -0,0 +1,45 @@ +package jadx.tests.integration.loops; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import org.junit.Test; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.junit.Assert.assertThat; + +public class TestContinueInLoop extends IntegrationTest { + + public static class TestCls { + private int f; + + private void test(int[] a, int b) { + for (int i = 0; i < a.length; i++) { + int v = a[i]; + if (v < b) { + a[i]++; + } else if (v > b) { + a[i]--; + } else { + continue; + } + if (i < b) { + break; + } + } + this.f++; + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("for (int i = 0; i < a.length; i++) {")); + assertThat(code, containsOne("if (i < b) {")); + assertThat(code, containsOne("continue;")); + assertThat(code, containsOne("break;")); + assertThat(code, containsOne("this.f++;")); + } +}