diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/FixMultiEntryLoops.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/FixMultiEntryLoops.java index d78f50b2c..9436437d5 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/FixMultiEntryLoops.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/FixMultiEntryLoops.java @@ -22,8 +22,7 @@ public class FixMultiEntryLoops { } List specialEdges = mth.getAll(AType.SPECIAL_EDGE); List multiEntryLoops = specialEdges.stream() - .filter(e -> e.getType() == SpecialEdgeType.BACK_EDGE) - .filter(e -> !isSingleEntryLoop(e)) + .filter(e -> e.getType() == SpecialEdgeType.BACK_EDGE && !isSingleEntryLoop(e)) .collect(Collectors.toList()); if (multiEntryLoops.isEmpty()) { return false; @@ -42,12 +41,21 @@ public class FixMultiEntryLoops { } private static boolean fixLoop(MethodNode mth, SpecialEdgeAttr backEdge, List crossEdges) { + if (isHeaderSuccessorEntry(mth, backEdge, crossEdges)) { + return true; + } + if (isEndBlockEntry(mth, backEdge, crossEdges)) { + return true; + } + mth.addWarnComment("Unsupported multi-entry loop pattern (" + backEdge + "). Please report as a decompilation issue!!!"); + return false; + } + + private static boolean isHeaderSuccessorEntry(MethodNode mth, SpecialEdgeAttr backEdge, List crossEdges) { BlockNode header = backEdge.getEnd(); BlockNode headerIDom = header.getIDom(); SpecialEdgeAttr subEntry = ListUtils.filterOnlyOne(crossEdges, e -> e.getStart() == headerIDom); - if (subEntry == null || !isSupportedPattern(header, subEntry)) { - // TODO: for now only sub entry in header successor is supported - mth.addWarnComment("Unsupported multi-entry loop pattern (" + backEdge + "). Please submit an issue!!!"); + if (subEntry == null || !ListUtils.isSingleElement(header.getSuccessors(), subEntry.getEnd())) { return false; } BlockNode loopEnd = backEdge.getStart(); @@ -55,12 +63,28 @@ public class FixMultiEntryLoops { BlockNode copyHeader = BlockSplitter.insertBlockBetween(mth, loopEnd, header); BlockSplitter.copyBlockData(header, copyHeader); BlockSplitter.replaceConnection(copyHeader, header, subEntryBlock); - mth.addDebugComment("Duplicate block to fix multi-entry loop: " + backEdge); + mth.addDebugComment("Duplicate block (" + header + ") to fix multi-entry loop: " + backEdge); return true; } - private static boolean isSupportedPattern(BlockNode header, SpecialEdgeAttr subEntry) { - return ListUtils.isSingleElement(header.getSuccessors(), subEntry.getEnd()); + private static boolean isEndBlockEntry(MethodNode mth, SpecialEdgeAttr backEdge, List crossEdges) { + BlockNode loopEnd = backEdge.getStart(); + SpecialEdgeAttr subEntry = ListUtils.filterOnlyOne(crossEdges, e -> e.getEnd() == loopEnd); + if (subEntry == null) { + return false; + } + dupPath(mth, subEntry.getStart(), loopEnd, backEdge.getEnd()); + mth.addDebugComment("Duplicate block (" + loopEnd + ") to fix multi-entry loop: " + backEdge); + return true; + } + + /** + * Duplicate 'center' block on path from 'start' to 'end' + */ + private static void dupPath(MethodNode mth, BlockNode start, BlockNode center, BlockNode end) { + BlockNode copyCenter = BlockSplitter.insertBlockBetween(mth, start, end); + BlockSplitter.copyBlockData(center, copyCenter); + BlockSplitter.removeConnection(start, center); } private static boolean isSingleEntryLoop(SpecialEdgeAttr e) { @@ -75,21 +99,18 @@ public class FixMultiEntryLoops { } private static void detectSpecialEdges(MethodNode mth) { - List blocks = mth.getBasicBlocks(); - BlockColor[] colors = new BlockColor[blocks.size()]; + BlockColor[] colors = new BlockColor[mth.getBasicBlocks().size()]; Arrays.fill(colors, BlockColor.WHITE); - colorDFS(mth, blocks, colors, mth.getEnterBlock().getId()); + colorDFS(mth, colors, mth.getEnterBlock()); } // TODO: transform to non-recursive form - private static void colorDFS(MethodNode mth, List blocks, BlockColor[] colors, int cur) { - colors[cur] = BlockColor.GRAY; - BlockNode block = blocks.get(cur); + private static void colorDFS(MethodNode mth, BlockColor[] colors, BlockNode block) { + colors[block.getId()] = BlockColor.GRAY; for (BlockNode v : block.getSuccessors()) { - int vId = v.getId(); - switch (colors[vId]) { + switch (colors[v.getId()]) { case WHITE: - colorDFS(mth, blocks, colors, vId); + colorDFS(mth, colors, v); break; case GRAY: mth.addAttr(AType.SPECIAL_EDGE, new SpecialEdgeAttr(SpecialEdgeType.BACK_EDGE, block, v)); @@ -99,6 +120,6 @@ public class FixMultiEntryLoops { break; } } - colors[cur] = BlockColor.BLACK; + colors[block.getId()] = BlockColor.BLACK; } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestMultiEntryLoop2.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestMultiEntryLoop2.java new file mode 100644 index 000000000..008217064 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/loops/TestMultiEntryLoop2.java @@ -0,0 +1,17 @@ +package jadx.tests.integration.loops; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestMultiEntryLoop2 extends SmaliTest { + + @Test + public void test() { + assertThat(getClassNodeFromSmali()) + .code() + .containsOne("while (true) {"); + } +} diff --git a/jadx-core/src/test/smali/loops/TestMultiEntryLoop2.smali b/jadx-core/src/test/smali/loops/TestMultiEntryLoop2.smali new file mode 100644 index 000000000..ce46a35aa --- /dev/null +++ b/jadx-core/src/test/smali/loops/TestMultiEntryLoop2.smali @@ -0,0 +1,92 @@ +.class public Lloops/TestMultiEntryLoop2; +.super Ljava/lang/Object; + +.field public list:Ljava/util/List; + .annotation system Ldalvik/annotation/Signature; + value = { + "Ljava/util/List<", + "Ljava/lang/String;", + ">;" + } + .end annotation +.end field + +.method private test(Ljava/lang/String;II)V + .registers 14 + + if-ne p2, p3, :cond_3 + return-void + + :cond_3 + invoke-virtual {p1, p2}, Ljava/lang/String;->charAt(I)C + + move-result v0 + const/16 v1, 0x2f + const-string v2, "" + const/4 v3, 0x1 + + if-eq v0, v1, :cond_1e + const/16 v1, 0x5c + if-ne v0, v1, :cond_13 + + goto :goto_1e + + :cond_13 + iget-object v0, p0, Lloops/TestMultiEntryLoop2;->list:Ljava/util/List; + invoke-interface {v0}, Ljava/util/List;->size()I + move-result v1 + sub-int/2addr v1, v3 + invoke-interface {v0, v1, v2}, Ljava/util/List;->set(ILjava/lang/Object;)Ljava/lang/Object; + goto :goto_29 + + :cond_1e + :goto_1e + iget-object v0, p0, Lloops/TestMultiEntryLoop2;->list:Ljava/util/List; + invoke-interface {v0}, Ljava/util/List;->clear()V + .line 4 + iget-object v0, p0, Lloops/TestMultiEntryLoop2;->list:Ljava/util/List; + invoke-interface {v0, v2}, Ljava/util/List;->add(Ljava/lang/Object;)Z + goto :goto_41 + + :cond_29 + :goto_29 + move v6, p2 + if-ge v6, p3, :cond_44 + const-string p2, "/\\" + .line 5 + invoke-static {p1, v6, p3, p2}, Lloops/TestMultiEntryLoop2;->delimiterOffset(Ljava/lang/String;IILjava/lang/String;)I + move-result p2 + if-ge p2, p3, :cond_36 + move v0, v3 + goto :goto_37 + + :cond_36 + const/4 v0, 0x0 + :goto_37 + const/4 v9, 0x1 + move-object v4, p0 + move-object v5, p1 + move v7, p2 + move v8, v0 + .line 6 + invoke-direct/range {v4 .. v9}, Lloops/TestMultiEntryLoop2;->push(Ljava/lang/String;IIZZ)V + if-eqz v0, :cond_29 + + :goto_41 + add-int/lit8 p2, p2, 0x1 + goto :goto_29 + + :cond_44 + return-void +.end method + +.method private push(Ljava/lang/String;IIZZ)V + .locals 0 + return-void +.end method + +.method private delimiterOffset(Ljava/lang/String;IILjava/lang/String;)I + .locals 1 + const/4 v0, 0x0 + return v0 +.end method