fix: support end block entry for mutli-entry loops (#889)
This commit is contained in:
@@ -22,8 +22,7 @@ public class FixMultiEntryLoops {
|
||||
}
|
||||
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;
|
||||
@@ -42,12 +41,21 @@ public class FixMultiEntryLoops {
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -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<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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<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));
|
||||
@@ -99,6 +120,6 @@ public class FixMultiEntryLoops {
|
||||
break;
|
||||
}
|
||||
}
|
||||
colors[cur] = BlockColor.BLACK;
|
||||
colors[block.getId()] = BlockColor.BLACK;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user