Skip to content

Commit

Permalink
fix: support end block entry for mutli-entry loops (#889)
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot committed Apr 6, 2024
1 parent c1de235 commit ea86182
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ public static boolean process(MethodNode mth) {
}
List<SpecialEdgeAttr> specialEdges = mth.getAll(AType.SPECIAL_EDGE);
List<SpecialEdgeAttr> 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;
Expand All @@ -42,25 +41,50 @@ public static boolean process(MethodNode mth) {
}

private static boolean fixLoop(MethodNode mth, SpecialEdgeAttr backEdge, List<SpecialEdgeAttr> 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<SpecialEdgeAttr> 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();
BlockNode subEntryBlock = subEntry.getEnd();
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 isEndBlockEntry(MethodNode mth, SpecialEdgeAttr backEdge, List<SpecialEdgeAttr> 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;
}

private static boolean isSupportedPattern(BlockNode header, SpecialEdgeAttr subEntry) {
return ListUtils.isSingleElement(header.getSuccessors(), subEntry.getEnd());
/**
* 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) {
Expand All @@ -75,21 +99,18 @@ private enum BlockColor {
}

private static void detectSpecialEdges(MethodNode mth) {
List<BlockNode> 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<BlockNode> 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));
Expand All @@ -99,6 +120,6 @@ private static void colorDFS(MethodNode mth, List<BlockNode> blocks, BlockColor[
break;
}
}
colors[cur] = BlockColor.BLACK;
colors[block.getId()] = BlockColor.BLACK;
}
}
Original file line number Diff line number Diff line change
@@ -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) {");
}
}
92 changes: 92 additions & 0 deletions jadx-core/src/test/smali/loops/TestMultiEntryLoop2.smali
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit ea86182

Please sign in to comment.