fix: cache finally extract checks on multiple paths (#1853)

This commit is contained in:
Skylot
2023-05-06 18:11:11 +01:00
parent f53dbbfebf
commit 5aaceee978
3 changed files with 141 additions and 11 deletions
@@ -1,7 +1,9 @@
package jadx.core.dex.visitors.finaly;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -32,6 +34,7 @@ import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnList;
import jadx.core.utils.ListUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.blocks.BlockPair;
@JadxVisitor(
name = "MarkFinallyVisitor",
@@ -351,9 +354,14 @@ public class MarkFinallyVisitor extends AbstractVisitor {
if (dupSlice == null) {
return null;
}
if (!dupSlice.isComplete()
&& !checkBlocksTree(dupBlock, startBlock, dupSlice, extractInfo)) {
return null;
if (!dupSlice.isComplete()) {
Map<BlockPair, Boolean> checkCache = new HashMap<>();
if (checkBlocksTree(dupBlock, startBlock, dupSlice, extractInfo, checkCache)) {
dupSlice.setComplete(true);
extractInfo.getFinallyInsnsSlice().setComplete(true);
} else {
return null;
}
}
return checkTempSlice(dupSlice);
}
@@ -470,30 +478,41 @@ public class MarkFinallyVisitor extends AbstractVisitor {
}
private static boolean checkBlocksTree(BlockNode dupBlock, BlockNode finallyBlock,
InsnsSlice dupSlice, FinallyExtractInfo extractInfo) {
InsnsSlice dupSlice, FinallyExtractInfo extractInfo,
Map<BlockPair, Boolean> checksCache) {
BlockPair checkBlocks = new BlockPair(dupBlock, finallyBlock);
Boolean checked = checksCache.get(checkBlocks);
if (checked != null) {
return checked;
}
boolean same;
InsnsSlice finallySlice = extractInfo.getFinallyInsnsSlice();
List<BlockNode> finallyCS = getSuccessorsWithoutLoop(finallyBlock);
List<BlockNode> dupCS = getSuccessorsWithoutLoop(dupBlock);
if (finallyCS.size() == dupCS.size()) {
same = true;
for (int i = 0; i < finallyCS.size(); i++) {
BlockNode finSBlock = finallyCS.get(i);
BlockNode dupSBlock = dupCS.get(i);
if (extractInfo.getAllHandlerBlocks().contains(finSBlock)) {
if (!compareBlocks(dupSBlock, finSBlock, dupSlice, extractInfo)) {
return false;
same = false;
break;
}
if (!checkBlocksTree(dupSBlock, finSBlock, dupSlice, extractInfo)) {
return false;
if (!checkBlocksTree(dupSBlock, finSBlock, dupSlice, extractInfo, checksCache)) {
same = false;
break;
}
dupSlice.addBlock(dupSBlock);
finallySlice.addBlock(finSBlock);
}
}
} else {
// stop checks at start blocks (already compared)
same = true;
}
dupSlice.setComplete(true);
finallySlice.setComplete(true);
return true;
checksCache.put(checkBlocks, same);
return same;
}
private static List<BlockNode> getSuccessorsWithoutLoop(BlockNode block) {
@@ -0,0 +1,43 @@
package jadx.core.utils.blocks;
import jadx.core.dex.nodes.BlockNode;
public class BlockPair {
private final BlockNode first;
private final BlockNode second;
public BlockPair(BlockNode first, BlockNode second) {
this.first = first;
this.second = second;
}
public BlockNode getFirst() {
return first;
}
public BlockNode getSecond() {
return second;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof BlockPair)) {
return false;
}
BlockPair other = (BlockPair) o;
return first.equals(other.first) && second.equals(other.second);
}
@Override
public int hashCode() {
return first.hashCode() + 31 * second.hashCode();
}
@Override
public String toString() {
return "(" + first + ", " + second + ')';
}
}
@@ -0,0 +1,68 @@
package jadx.core.utils.blocks;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.EmptyBitSet;
public class BlockSet {
private final MethodNode mth;
private final BitSet bs;
public BlockSet(MethodNode mth) {
this.mth = mth;
this.bs = new BitSet(mth.getBasicBlocks().size());
}
public boolean get(BlockNode block) {
return bs.get(block.getId());
}
public void set(BlockNode block) {
bs.set(block.getId());
}
public boolean checkAndSet(BlockNode block) {
int id = block.getId();
boolean state = bs.get(id);
bs.set(id);
return state;
}
public void forEach(Consumer<? super BlockNode> consumer) {
if (bs.isEmpty()) {
return;
}
List<BlockNode> blocks = mth.getBasicBlocks();
for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
consumer.accept(blocks.get(i));
}
}
public List<BlockNode> toList() {
if (bs == null || bs == EmptyBitSet.EMPTY) {
return Collections.emptyList();
}
int size = bs.cardinality();
if (size == 0) {
return Collections.emptyList();
}
List<BlockNode> mthBlocks = mth.getBasicBlocks();
List<BlockNode> blocks = new ArrayList<>(size);
for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
blocks.add(mthBlocks.get(i));
}
return blocks;
}
@Override
public String toString() {
return toList().toString();
}
}