diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java index ccd65b647..f0385b491 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java @@ -96,4 +96,8 @@ public abstract class AttrNode implements IAttributeNode { public String getAttributesString() { return storage.toString(); } + + public boolean isAttrStorageEmpty() { + return storage.isEmpty(); + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java index 02eb503b3..7fd32b06b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; import com.android.dx.io.instructions.DecodedInstruction; import com.rits.cloning.Cloner; @@ -251,12 +252,14 @@ public class InsnNode extends LineAttrNode { if (this == other) { return true; } - if (insnType != other.insnType - || arguments.size() != other.arguments.size()) { + if (insnType != other.insnType) { + return false; + } + int size = arguments.size(); + if (size != other.arguments.size()) { return false; } // check wrapped instructions - int size = arguments.size(); for (int i = 0; i < size; i++) { InsnArg arg = arguments.get(i); InsnArg otherArg = other.arguments.get(i); @@ -273,6 +276,16 @@ public class InsnNode extends LineAttrNode { } return true; } + /** + * 'Hard' equals, compare all arguments + */ + public boolean isDeepEquals(InsnNode other) { + if (this == other) { + return true; + } + return isSame(other) + && Objects.equals(arguments, other.arguments); + } protected T copyCommonParams(T copy) { copy.setResult(result); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java index 4627e321d..121455232 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java @@ -45,6 +45,10 @@ public class BlockProcessor extends AbstractVisitor { private static void processBlocksTree(MethodNode mth) { computeDominators(mth); + if (independentBlockTreeMod(mth)) { + clearBlocksState(mth); + computeDominators(mth); + } markReturnBlocks(mth); int i = 0; @@ -64,6 +68,109 @@ public class BlockProcessor extends AbstractVisitor { processNestedLoops(mth); } + private static boolean removeEmptyBlock(MethodNode mth, BlockNode block) { + if (block.getInstructions().isEmpty() + && !block.isSynthetic() + && block.isAttrStorageEmpty() + && block.getSuccessors().size() <= 1) { + LOG.debug("Removing empty block: {}", block); + if (block.getSuccessors().size() == 1) { + BlockNode successor = block.getSuccessors().get(0); + block.getPredecessors().forEach(pred -> { + pred.getSuccessors().remove(block); + BlockSplitter.connect(pred, successor); + BlockSplitter.replaceTarget(pred, block, successor); + pred.updateCleanSuccessors(); + }); + BlockSplitter.removeConnection(block, successor); + } else { + block.getPredecessors().forEach(pred -> { + pred.getSuccessors().remove(block); + pred.updateCleanSuccessors(); + }); + } + block.add(AFlag.REMOVE); + block.getSuccessors().clear(); + block.getPredecessors().clear(); + return true; + } + return false; + } + + private static boolean deduplicateBlockInsns(BlockNode block) { + if (block.contains(AFlag.LOOP_START) || block.contains(AFlag.LOOP_END)) { + // search for same instruction at end of all predecessors blocks + List predecessors = block.getPredecessors(); + int predsCount = predecessors.size(); + if (predsCount > 1) { + InsnNode lastInsn = BlockUtils.getLastInsn(block); + if (lastInsn != null && lastInsn.getType() == InsnType.IF) { + return false; + } + int sameInsnCount = getSameLastInsnCount(predecessors); + if (sameInsnCount > 0) { + List insns = getLastInsns(predecessors.get(0), sameInsnCount); + insertAtStart(block, insns); + predecessors.forEach(pred -> getLastInsns(pred, sameInsnCount).clear()); + LOG.debug("Move duplicate insns, count: {} to block {}", sameInsnCount, block); + return true; + } + } + } + return false; + } + + private static List getLastInsns(BlockNode blockNode, int sameInsnCount) { + List instructions = blockNode.getInstructions(); + int size = instructions.size(); + return instructions.subList(size - sameInsnCount, size); + } + + private static void insertAtStart(BlockNode block, List insns) { + List blockInsns = block.getInstructions(); + + List newInsnList = new ArrayList<>(insns.size() + blockInsns.size()); + newInsnList.addAll(insns); + newInsnList.addAll(blockInsns); + + blockInsns.clear(); + blockInsns.addAll(newInsnList); + } + + private static int getSameLastInsnCount(List predecessors) { + int sameInsnCount = 0; + while (true) { + InsnNode insn = null; + for (BlockNode pred : predecessors) { + InsnNode curInsn = getInsnsFromEnd(pred, sameInsnCount); + if (curInsn == null) { + return sameInsnCount; + } + if (insn == null) { + insn = curInsn; + } else { + if (!isSame(insn, curInsn)) { + return sameInsnCount; + } + } + } + sameInsnCount++; + } + } + + private static boolean isSame(InsnNode insn, InsnNode curInsn) { + return /*insn.getType() == InsnType.MOVE &&*/ insn.isDeepEquals(curInsn) && insn.canReorder(); + } + + private static InsnNode getInsnsFromEnd(BlockNode block, int number) { + List instructions = block.getInstructions(); + int insnCount = instructions.size(); + if (insnCount <= number) { + return null; + } + return instructions.get(insnCount - number - 1); + } + private static void computeDominators(MethodNode mth) { List basicBlocks = mth.getBasicBlocks(); int nBlocks = basicBlocks.size(); @@ -104,9 +211,7 @@ public class BlockProcessor extends AbstractVisitor { markLoops(mth); // clear self dominance - for (BlockNode block : basicBlocks) { - block.getDoms().clear(block.getId()); - } + basicBlocks.forEach(block -> block.getDoms().clear(block.getId())); // calculate immediate dominators for (BlockNode block : basicBlocks) { @@ -148,9 +253,7 @@ public class BlockProcessor extends AbstractVisitor { if (block.getDomFrontier() != null) { return; } - for (BlockNode c : block.getDominatesOn()) { - computeBlockDF(mth, c); - } + block.getDominatesOn().forEach(c -> computeBlockDF(mth, c)); List blocks = mth.getBasicBlocks(); BitSet domFrontier = null; for (BlockNode s : block.getSuccessors()) { @@ -180,39 +283,37 @@ public class BlockProcessor extends AbstractVisitor { private static void markReturnBlocks(MethodNode mth) { mth.getExitBlocks().clear(); - for (BlockNode block : mth.getBasicBlocks()) { + mth.getBasicBlocks().forEach(block -> { if (BlockUtils.checkLastInsnType(block, InsnType.RETURN)) { block.add(AFlag.RETURN); mth.getExitBlocks().add(block); } - } + }); } private static void markLoops(MethodNode mth) { - for (BlockNode block : mth.getBasicBlocks()) { - for (BlockNode succ : block.getSuccessors()) { - // Every successor that dominates its predecessor is a header of a loop, - // block -> succ is a back edge. - if (block.getDoms().get(succ.getId())) { - succ.add(AFlag.LOOP_START); + mth.getBasicBlocks().forEach(block -> { + // Every successor that dominates its predecessor is a header of a loop, + // block -> succ is a back edge. + block.getSuccessors().forEach(successor -> { + if (block.getDoms().get(successor.getId())) { + successor.add(AFlag.LOOP_START); block.add(AFlag.LOOP_END); - LoopInfo loop = new LoopInfo(succ, block); - succ.addAttr(AType.LOOP, loop); + LoopInfo loop = new LoopInfo(successor, block); + successor.addAttr(AType.LOOP, loop); block.addAttr(AType.LOOP, loop); } - } - } + }); + }); } private static void registerLoops(MethodNode mth) { - for (BlockNode block : mth.getBasicBlocks()) { + mth.getBasicBlocks().forEach(block -> { if (block.contains(AFlag.LOOP_START)) { - for (LoopInfo loop : block.getAll(AType.LOOP)) { - mth.registerLoop(loop); - } + block.getAll(AType.LOOP).forEach(mth::registerLoop); } - } + }); } private static void processNestedLoops(MethodNode mth) { @@ -242,10 +343,14 @@ public class BlockProcessor extends AbstractVisitor { } private static boolean modifyBlocksTree(MethodNode mth) { - for (BlockNode block : mth.getBasicBlocks()) { + List basicBlocks = mth.getBasicBlocks(); + for (BlockNode block : basicBlocks) { if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) { throw new JadxRuntimeException("Unreachable block: " + block); } + } + + for (BlockNode block : basicBlocks) { if (checkLoops(mth, block)) { return true; } @@ -253,6 +358,25 @@ public class BlockProcessor extends AbstractVisitor { return splitReturn(mth); } + private static boolean independentBlockTreeMod(MethodNode mth) { + List basicBlocks = mth.getBasicBlocks(); + boolean changed = false; + for (BlockNode basicBlock : basicBlocks) { + if (deduplicateBlockInsns(basicBlock)) { + changed = true; + } + } + for (BlockNode basicBlock : basicBlocks) { + if (removeEmptyBlock(mth, basicBlock)) { + changed = true; + } + } + if (BlockSplitter.removeEmptyDetachedBlocks(mth)) { + changed = true; + } + return changed; + } + private static boolean checkLoops(MethodNode mth, BlockNode block) { // check loops List loops = block.getAll(AType.LOOP); @@ -303,9 +427,7 @@ public class BlockProcessor extends AbstractVisitor { change = true; } } - if (change) { - return true; - } + return change; } } return false; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java index a9b8d6deb..b6be091ab 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java @@ -253,8 +253,8 @@ public class BlockSplitter extends AbstractVisitor { } } - static void removeEmptyDetachedBlocks(MethodNode mth) { - mth.getBasicBlocks().removeIf(block -> + static boolean removeEmptyDetachedBlocks(MethodNode mth) { + return mth.getBasicBlocks().removeIf(block -> block.getInstructions().isEmpty() && block.getPredecessors().isEmpty() && block.getSuccessors().isEmpty()