diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java index eb5e49f2c819e9f7bb715b3ef72a38edae6fdfec..ace669f088daa66fb2a2b063fee6337a597e28ef 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java @@ -1,6 +1,7 @@ package jadx.core.dex.instructions; import java.io.EOFException; +import java.util.function.Predicate; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; @@ -598,7 +599,7 @@ public class InsnDecoder { private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) { int payloadOffset = insn.getTarget(); - DecodedInstruction payload = insnArr[payloadOffset]; + DecodedInstruction payload = getInsnByOffsetSkipNop(insnArr, payloadOffset); Object[] keys; int[] targets; if (packed) { @@ -626,7 +627,7 @@ public class InsnDecoder { } private InsnNode fillArray(DecodedInstruction insn) { - DecodedInstruction payload = insnArr[insn.getTarget()]; + DecodedInstruction payload = getInsnByOffsetSkipNop(insnArr, insn.getTarget()); return new FillArrayNode(insn.getA(), (FillArrayDataPayloadDecodedInstruction) payload); } @@ -738,7 +739,7 @@ public class InsnDecoder { } private int getMoveResultRegister(DecodedInstruction[] insnArr, int offset) { - int nextOffset = getNextInsnOffset(insnArr, offset); + int nextOffset = getNextInsnOffsetSkipNop(insnArr, offset); if (nextOffset >= 0) { DecodedInstruction next = insnArr[nextOffset]; int opc = next.getOpcode(); @@ -751,21 +752,35 @@ public class InsnDecoder { return -1; } - public static int getPrevInsnOffset(Object[] insnArr, int offset) { - int i = offset - 1; - while (i >= 0 && insnArr[i] == null) { - i--; - } - if (i < 0) { - return -1; + private static DecodedInstruction getInsnByOffsetSkipNop(DecodedInstruction[] insnArr, int offset) { + DecodedInstruction payload = insnArr[offset]; + if (payload.getOpcode() == Opcodes.NOP) { + return insnArr[getNextInsnOffsetSkipNop(insnArr, offset)]; } - return i; + return payload; + } + + public static int getNextInsnOffset(DecodedInstruction[] insnArr, int offset) { + return getNextInsnOffset(insnArr, offset, null); } - public static int getNextInsnOffset(Object[] insnArr, int offset) { + public static int getNextInsnOffsetSkipNop(DecodedInstruction[] insnArr, int offset) { + return getNextInsnOffset(insnArr, offset, i -> i.getOpcode() == Opcodes.NOP); + } + + public static int getNextInsnOffset(InsnNode[] insnArr, int offset) { + return getNextInsnOffset(insnArr, offset, null); + } + + public static int getNextInsnOffset(T[] insnArr, int offset, Predicate skip) { int i = offset + 1; - while (i < insnArr.length && insnArr[i] == null) { - i++; + while (i < insnArr.length) { + T insn = insnArr[i]; + if (insn == null || (skip != null && skip.test(insn))) { + i++; + } else { + break; + } } if (i >= insnArr.length) { return -1; diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java index c9273afe827d4d89f0cdc207f5e013598cdbbbd6..9400bbb658fc22c1a8f7d9e8a9a728173372b88c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java @@ -64,6 +64,9 @@ public class SwitchNode extends TargetInsnNode { @Override public boolean replaceTargetBlock(BlockNode origin, BlockNode replace) { + if (targetBlocks == null) { + return false; + } int count = 0; int len = targetBlocks.length; for (int i = 0; i < len; i++) { 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 3cfee8becd6eab49c9c8cbe69509005b18a2eb36..66383b84ee2cc32e03e0a77d6a290bf9d2a7fc10 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 @@ -87,38 +87,6 @@ public class BlockProcessor extends AbstractVisitor { }); } - private static boolean canRemoveBlock(BlockNode block) { - return block.getInstructions().isEmpty() - && block.isAttrStorageEmpty() - && block.getSuccessors().size() <= 1 - && !block.getPredecessors().isEmpty(); - } - - private static boolean removeEmptyBlock(BlockNode block) { - if (canRemoveBlock(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 @@ -457,7 +425,7 @@ public class BlockProcessor extends AbstractVisitor { } } for (BlockNode basicBlock : basicBlocks) { - if (removeEmptyBlock(basicBlock)) { + if (BlockSplitter.removeEmptyBlock(basicBlock)) { changed = true; } } @@ -631,7 +599,7 @@ public class BlockProcessor extends AbstractVisitor { ExceptionHandler excHandler = otherExcHandlerAttr.getHandler(); excHandlerAttr.getHandler().addCatchTypes(excHandler.getCatchTypes()); tryBlock.removeHandler(mth, excHandler); - detachBlock(block); + BlockSplitter.detachBlock(block); } return true; } @@ -734,20 +702,6 @@ public class BlockProcessor extends AbstractVisitor { }); } - private static void detachBlock(BlockNode block) { - for (BlockNode pred : block.getPredecessors()) { - pred.getSuccessors().remove(block); - pred.updateCleanSuccessors(); - } - for (BlockNode successor : block.getSuccessors()) { - successor.getPredecessors().remove(block); - } - block.add(AFlag.REMOVE); - block.getInstructions().clear(); - block.getPredecessors().clear(); - block.getSuccessors().clear(); - } - private static void clearBlocksState(MethodNode mth) { mth.getBasicBlocks().forEach(block -> { block.remove(AType.LOOP); 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 810becf469f198201674ab510aa7abcb41ba3fd8..56f8eb56edb5b3eca021055ee2c9cf0b2ef6de2a 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 @@ -50,13 +50,14 @@ public class BlockSplitter extends AbstractVisitor { mth.initBasicBlocks(); splitBasicBlocks(mth); + initBlocksInTargetNodes(mth); + removeJumpAttr(mth); removeInsns(mth); removeEmptyDetachedBlocks(mth); removeUnreachableBlocks(mth); - initBlocksInTargetNodes(mth); + mth.getBasicBlocks().removeIf(BlockSplitter::removeEmptyBlock); - removeJumpAttributes(mth.getInstructions()); mth.unloadInsnArr(); } @@ -307,7 +308,7 @@ public class BlockSplitter extends AbstractVisitor { return block; } - private void removeJumpAttr(MethodNode mth) { + private static void removeJumpAttr(MethodNode mth) { for (BlockNode block : mth.getBasicBlocks()) { for (InsnNode insn : block.getInstructions()) { insn.remove(AType.JUMP); @@ -333,44 +334,88 @@ public class BlockSplitter extends AbstractVisitor { && block.getSuccessors().isEmpty()); } - private void removeJumpAttributes(InsnNode[] insnArr) { - for (InsnNode insn : insnArr) { - if (insn != null && insn.contains(AType.JUMP)) { - insn.remove(AType.JUMP); - } - } - } - - private void removeUnreachableBlocks(MethodNode mth) { + private static boolean removeUnreachableBlocks(MethodNode mth) { Set toRemove = new LinkedHashSet<>(); for (BlockNode block : mth.getBasicBlocks()) { if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) { - toRemove.add(block); collectSuccessors(block, toRemove); } } - if (!toRemove.isEmpty()) { - mth.getBasicBlocks().removeIf(toRemove::contains); + if (toRemove.isEmpty()) { + return false; + } + toRemove.forEach(BlockSplitter::detachBlock); + mth.getBasicBlocks().removeAll(toRemove); + long notEmptyBlocks = toRemove.stream().filter(block -> !block.getInstructions().isEmpty()).count(); + if (notEmptyBlocks != 0) { int insnsCount = toRemove.stream().mapToInt(block -> block.getInstructions().size()).sum(); - mth.addAttr(AType.COMMENTS, "JADX INFO: unreachable blocks removed: " + toRemove.size() + mth.addAttr(AType.COMMENTS, "JADX INFO: unreachable blocks removed: " + notEmptyBlocks + ", instructions: " + insnsCount); } + return true; } - private void collectSuccessors(BlockNode startBlock, Set toRemove) { + static boolean removeEmptyBlock(BlockNode block) { + if (canRemoveBlock(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 canRemoveBlock(BlockNode block) { + return block.getInstructions().isEmpty() + && block.isAttrStorageEmpty() + && block.getSuccessors().size() <= 1 + && !block.getPredecessors().isEmpty(); + } + + private static void collectSuccessors(BlockNode startBlock, Set toRemove) { Deque stack = new ArrayDeque<>(); stack.add(startBlock); while (!stack.isEmpty()) { BlockNode block = stack.pop(); if (!toRemove.contains(block)) { + toRemove.add(block); for (BlockNode successor : block.getSuccessors()) { if (toRemove.containsAll(successor.getPredecessors())) { stack.push(successor); } } } - toRemove.add(block); + + } + } + + static void detachBlock(BlockNode block) { + for (BlockNode pred : block.getPredecessors()) { + pred.getSuccessors().remove(block); + pred.updateCleanSuccessors(); + } + for (BlockNode successor : block.getSuccessors()) { + successor.getPredecessors().remove(block); } + block.add(AFlag.REMOVE); + block.getInstructions().clear(); + block.getPredecessors().clear(); + block.getSuccessors().clear(); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java index ea04884f3dfa3c6aae35e3978200a472666f7bd5..a00b1f81f7a6cddf1c70d3cfe7fb9e3073650f7b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.IBranchRegion; @@ -167,6 +168,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor { for (ExceptionHandler h : tb.getHandlers()) { BlockNode handlerBlock = h.getHandlerBlock(); if (handlerBlock != null + && !handlerBlock.contains(AFlag.REMOVE) && RegionUtils.hasPathThroughBlock(handlerBlock, cont)) { return true; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java index 08765a5abaa31c00410f21853fbb4ea3b56f95d0..b99bf446f8fc41b8af78ff7dfab3952bb6264ee1 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java @@ -1022,6 +1022,9 @@ public class RegionMaker { dom = start; stack.addExits(exits); } + if (dom.contains(AFlag.REMOVE)) { + return; + } BitSet domFrontier = dom.getDomFrontier(); List handlerExits = BlockUtils.bitSetToBlocks(this.mth, domFrontier); boolean inLoop = this.mth.getLoopForBlock(start) != null; diff --git a/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java b/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java index 64064cc054ebf6c03391630bb8f433621c8018ee..5bac99a6b3f47872c73cb85528836342c216e922 100644 --- a/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java @@ -270,6 +270,9 @@ public class BlockUtils { } public static List bitSetToBlocks(MethodNode mth, BitSet bs) { + if (bs == null) { + return Collections.emptyList(); + } int size = bs.cardinality(); if (size == 0) { return Collections.emptyList(); diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestMultipleNOPs.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestMultipleNOPs.java new file mode 100644 index 0000000000000000000000000000000000000000..6e337bfd19d1fe40d49463df39eaa09f137bc015 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestMultipleNOPs.java @@ -0,0 +1,16 @@ +package jadx.tests.integration.others; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.SmaliTest; + +public class TestMultipleNOPs extends SmaliTest { + + @Test + public void test() { + disableCompilation(); + + // expected no errors + loadFromSmaliFiles(); + } +} diff --git a/jadx-core/src/test/smali/others/TestMultipleNOPs/test.smali b/jadx-core/src/test/smali/others/TestMultipleNOPs/test.smali new file mode 100644 index 0000000000000000000000000000000000000000..2424502b0fb57517d29a8bb68ece0e2ce09f9585 --- /dev/null +++ b/jadx-core/src/test/smali/others/TestMultipleNOPs/test.smali @@ -0,0 +1,226 @@ +.class final LseC/dujmehn/Cutyq/e; +.super Ljava/lang/Object; + +# interfaces +.implements Ljava/lang/Runnable; + + +.method public static test()Ljava/lang/String; + .registers 9 + + nop + + nop + + nop + + nop + + .prologue + nop + + const-string v5, "VA==" + + nop + + nop + + .local v5, "x_gfxj":Ljava/lang/String; + nop + + const-string v1, "54b6610e1af242f78e38a5866eaa7c41" + + nop + + nop + + .local v1, "p":Ljava/lang/String; + nop + + invoke-virtual {v5}, Ljava/lang/String;->getBytes()[B + + nop + + nop + + move-result-object v7 + + nop + + nop + + const/4 v8, 0x0 + + nop + + nop + + invoke-static {v7, v8}, Landroid/util/Base64;->decode([BI)[B + + nop + + nop + + move-result-object v3 + + nop + + nop + + .local v3, "wjxzqy":[B + nop + + new-instance v4, Ljava/lang/String; + + nop + + nop + + invoke-direct {v4, v3}, Ljava/lang/String;->([B)V + + nop + + nop + + .local v4, "x":Ljava/lang/String; + nop + + new-instance v6, Ljava/lang/StringBuilder; + + nop + + nop + + invoke-direct {v6}, Ljava/lang/StringBuilder;->()V + + nop + + nop + + .local v6, "xg":Ljava/lang/StringBuilder; + nop + + const/4 v0, 0x0 + + nop + + nop + + .local v0, "n":I + nop + + :goto_3b + nop + + invoke-virtual {v4}, Ljava/lang/String;->length()I + + nop + + nop + + move-result v7 + + nop + + nop + + if-ge v0, v7, :cond_76 + + nop + + nop + + invoke-virtual {v4, v0}, Ljava/lang/String;->charAt(I)C + + nop + + nop + + move-result v7 + + nop + + nop + + invoke-virtual {v1}, Ljava/lang/String;->length()I + + nop + + nop + + move-result v8 + + nop + + nop + + rem-int v8, v0, v8 + + nop + + nop + + invoke-virtual {v1, v8}, Ljava/lang/String;->charAt(I)C + + nop + + nop + + move-result v8 + + nop + + nop + + xor-int/2addr v7, v8 + + nop + + nop + + int-to-char v7, v7 + + nop + + nop + + invoke-virtual {v6, v7}, Ljava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder; + + nop + + nop + + add-int/lit8 v0, v0, 0x1 + + nop + + nop + + goto :goto_3b + + nop + + nop + + :cond_76 + nop + + invoke-virtual {v6}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + + nop + + nop + + move-result-object v2 + + nop + + nop + + .local v2, "w":Ljava/lang/String; + nop + + return-object v2 + + nop +.end method