fix: prevent endless loop in pre header insertion mod (#2300)
This commit is contained in:
@@ -8,11 +8,14 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
|
||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
@@ -36,6 +39,8 @@ import static jadx.core.dex.visitors.blocks.BlockSplitter.connect;
|
||||
public class BlockProcessor extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BlockProcessor.class);
|
||||
|
||||
private static final boolean DEBUG_MODS = false;
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) {
|
||||
if (mth.isNoCode() || mth.getBasicBlocks().isEmpty()) {
|
||||
@@ -57,13 +62,27 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
}
|
||||
updateCleanSuccessors(mth);
|
||||
|
||||
int blocksCount = mth.getBasicBlocks().size();
|
||||
int modLimit = Math.max(100, blocksCount);
|
||||
if (DEBUG_MODS) {
|
||||
mth.addAttr(new DebugModAttr());
|
||||
}
|
||||
int i = 0;
|
||||
while (modifyBlocksTree(mth)) {
|
||||
computeDominators(mth);
|
||||
if (i++ > 100) {
|
||||
throw new JadxRuntimeException("CFG modification limit reached, blocks count: " + mth.getBasicBlocks().size());
|
||||
if (i++ > modLimit) {
|
||||
mth.addWarn("CFG modification limit reached, blocks count: " + blocksCount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (DEBUG_MODS && i != 0) {
|
||||
String stats = "CFG modifications count: " + i
|
||||
+ ", blocks count: " + blocksCount + '\n'
|
||||
+ mth.get(DebugModAttr.TYPE).formatStats() + '\n';
|
||||
mth.addDebugComment(stats);
|
||||
LOG.debug("Method: {}\n{}", mth, stats);
|
||||
mth.remove(DebugModAttr.TYPE);
|
||||
}
|
||||
checkForUnreachableBlocks(mth);
|
||||
|
||||
DominatorTree.computeDominanceFrontier(mth);
|
||||
@@ -298,6 +317,9 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
}
|
||||
if (changed) {
|
||||
removeMarkedBlocks(mth);
|
||||
if (DEBUG_MODS) {
|
||||
mth.get(DebugModAttr.TYPE).addEvent("Merge const return");
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
@@ -338,17 +360,20 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
|
||||
private static boolean simplifyLoopEnd(MethodNode mth, LoopInfo loop) {
|
||||
BlockNode loopEnd = loop.getEnd();
|
||||
if (loopEnd.getSuccessors().size() > 1) {
|
||||
// make loop end a simple path block
|
||||
BlockNode newLoopEnd = BlockSplitter.startNewBlock(mth, -1);
|
||||
newLoopEnd.add(AFlag.SYNTHETIC);
|
||||
newLoopEnd.add(AFlag.LOOP_END);
|
||||
BlockNode loopStart = loop.getStart();
|
||||
BlockSplitter.replaceConnection(loopEnd, loopStart, newLoopEnd);
|
||||
BlockSplitter.connect(newLoopEnd, loopStart);
|
||||
return true;
|
||||
if (loopEnd.getSuccessors().size() <= 1) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
// make loop end a simple path block
|
||||
BlockNode newLoopEnd = BlockSplitter.startNewBlock(mth, -1);
|
||||
newLoopEnd.add(AFlag.SYNTHETIC);
|
||||
newLoopEnd.add(AFlag.LOOP_END);
|
||||
BlockNode loopStart = loop.getStart();
|
||||
BlockSplitter.replaceConnection(loopEnd, loopStart, newLoopEnd);
|
||||
BlockSplitter.connect(newLoopEnd, loopStart);
|
||||
if (DEBUG_MODS) {
|
||||
mth.get(DebugModAttr.TYPE).addEvent("Simplify loop end");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean checkLoops(MethodNode mth, BlockNode block) {
|
||||
@@ -371,7 +396,6 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
if (loopsCount == 1) {
|
||||
LoopInfo loop = loops.get(0);
|
||||
return insertBlocksForContinue(mth, loop)
|
||||
|| insertBlockForPredecessors(mth, loop)
|
||||
|| insertPreHeader(mth, loop)
|
||||
|| simplifyLoopEnd(mth, loop);
|
||||
}
|
||||
@@ -398,15 +422,21 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
mth.setEnterBlock(newEnterBlock);
|
||||
start.remove(AFlag.MTH_ENTER_BLOCK);
|
||||
BlockSplitter.connect(newEnterBlock, start);
|
||||
return true;
|
||||
} else {
|
||||
// multiple predecessors
|
||||
BlockNode preHeader = BlockSplitter.startNewBlock(mth, -1);
|
||||
preHeader.add(AFlag.SYNTHETIC);
|
||||
BlockNode loopEnd = loop.getEnd();
|
||||
for (BlockNode pred : new ArrayList<>(preds)) {
|
||||
if (pred != loopEnd) {
|
||||
BlockSplitter.replaceConnection(pred, start, preHeader);
|
||||
}
|
||||
}
|
||||
BlockSplitter.connect(preHeader, start);
|
||||
}
|
||||
// multiple predecessors
|
||||
BlockNode preHeader = BlockSplitter.startNewBlock(mth, -1);
|
||||
preHeader.add(AFlag.SYNTHETIC);
|
||||
for (BlockNode pred : new ArrayList<>(preds)) {
|
||||
BlockSplitter.replaceConnection(pred, start, preHeader);
|
||||
if (DEBUG_MODS) {
|
||||
mth.get(DebugModAttr.TYPE).addEvent("Insert loop pre header");
|
||||
}
|
||||
BlockSplitter.connect(preHeader, start);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -426,6 +456,9 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (DEBUG_MODS && change) {
|
||||
mth.get(DebugModAttr.TYPE).addEvent("Insert loop break blocks");
|
||||
}
|
||||
return change;
|
||||
}
|
||||
|
||||
@@ -444,24 +477,10 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
return change;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert additional block if loop header has several predecessors (exclude back edges)
|
||||
*/
|
||||
private static boolean insertBlockForPredecessors(MethodNode mth, LoopInfo loop) {
|
||||
BlockNode loopHeader = loop.getStart();
|
||||
List<BlockNode> preds = loopHeader.getPredecessors();
|
||||
if (preds.size() > 2) {
|
||||
List<BlockNode> blocks = new ArrayList<>(preds);
|
||||
blocks.removeIf(block -> block.contains(AFlag.LOOP_END));
|
||||
BlockNode first = blocks.remove(0);
|
||||
BlockNode preHeader = BlockSplitter.insertBlockBetween(mth, first, loopHeader);
|
||||
blocks.forEach(block -> BlockSplitter.replaceConnection(block, loopHeader, preHeader));
|
||||
return true;
|
||||
if (DEBUG_MODS && change) {
|
||||
mth.get(DebugModAttr.TYPE).addEvent("Insert loop continue block");
|
||||
}
|
||||
return false;
|
||||
return change;
|
||||
}
|
||||
|
||||
private static boolean splitLoops(MethodNode mth, BlockNode block, List<LoopInfo> loops) {
|
||||
@@ -472,17 +491,20 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (oneHeader) {
|
||||
// several back edges connected to one loop header => make additional block
|
||||
BlockNode newLoopEnd = BlockSplitter.startNewBlock(mth, block.getStartOffset());
|
||||
newLoopEnd.add(AFlag.SYNTHETIC);
|
||||
connect(newLoopEnd, block);
|
||||
for (LoopInfo la : loops) {
|
||||
BlockSplitter.replaceConnection(la.getEnd(), block, newLoopEnd);
|
||||
}
|
||||
return true;
|
||||
if (!oneHeader) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
// several back edges connected to one loop header => make additional block
|
||||
BlockNode newLoopEnd = BlockSplitter.startNewBlock(mth, block.getStartOffset());
|
||||
newLoopEnd.add(AFlag.SYNTHETIC);
|
||||
connect(newLoopEnd, block);
|
||||
for (LoopInfo la : loops) {
|
||||
BlockSplitter.replaceConnection(la.getEnd(), block, newLoopEnd);
|
||||
}
|
||||
if (DEBUG_MODS) {
|
||||
mth.get(DebugModAttr.TYPE).addEvent("Split loops");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean splitExitBlocks(MethodNode mth) {
|
||||
@@ -496,6 +518,9 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
}
|
||||
if (changed) {
|
||||
updateExitBlockConnections(mth);
|
||||
if (DEBUG_MODS) {
|
||||
mth.get(DebugModAttr.TYPE).addEvent("Split exit block");
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
@@ -691,4 +716,25 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
block.getDominatesOn().clear();
|
||||
});
|
||||
}
|
||||
|
||||
private static final class DebugModAttr implements IJadxAttribute {
|
||||
static final IJadxAttrType<DebugModAttr> TYPE = IJadxAttrType.create("DebugModAttr");
|
||||
|
||||
private final Map<String, Integer> statMap = new HashMap<>();
|
||||
|
||||
public void addEvent(String name) {
|
||||
statMap.merge(name, 1, Integer::sum);
|
||||
}
|
||||
|
||||
public String formatStats() {
|
||||
return statMap.entrySet().stream()
|
||||
.map(entry -> " " + entry.getKey() + ": " + entry.getValue())
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IJadxAttrType<DebugModAttr> getAttrType() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user