Commit 1830c273 authored by Skylot's avatar Skylot

fix: handle NOP instructions in unexpected places (#666)

parent 5efe4bd8
package jadx.core.dex.instructions; package jadx.core.dex.instructions;
import java.io.EOFException; import java.io.EOFException;
import java.util.function.Predicate;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
...@@ -598,7 +599,7 @@ public class InsnDecoder { ...@@ -598,7 +599,7 @@ public class InsnDecoder {
private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) { private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) {
int payloadOffset = insn.getTarget(); int payloadOffset = insn.getTarget();
DecodedInstruction payload = insnArr[payloadOffset]; DecodedInstruction payload = getInsnByOffsetSkipNop(insnArr, payloadOffset);
Object[] keys; Object[] keys;
int[] targets; int[] targets;
if (packed) { if (packed) {
...@@ -626,7 +627,7 @@ public class InsnDecoder { ...@@ -626,7 +627,7 @@ public class InsnDecoder {
} }
private InsnNode fillArray(DecodedInstruction insn) { private InsnNode fillArray(DecodedInstruction insn) {
DecodedInstruction payload = insnArr[insn.getTarget()]; DecodedInstruction payload = getInsnByOffsetSkipNop(insnArr, insn.getTarget());
return new FillArrayNode(insn.getA(), (FillArrayDataPayloadDecodedInstruction) payload); return new FillArrayNode(insn.getA(), (FillArrayDataPayloadDecodedInstruction) payload);
} }
...@@ -738,7 +739,7 @@ public class InsnDecoder { ...@@ -738,7 +739,7 @@ public class InsnDecoder {
} }
private int getMoveResultRegister(DecodedInstruction[] insnArr, int offset) { private int getMoveResultRegister(DecodedInstruction[] insnArr, int offset) {
int nextOffset = getNextInsnOffset(insnArr, offset); int nextOffset = getNextInsnOffsetSkipNop(insnArr, offset);
if (nextOffset >= 0) { if (nextOffset >= 0) {
DecodedInstruction next = insnArr[nextOffset]; DecodedInstruction next = insnArr[nextOffset];
int opc = next.getOpcode(); int opc = next.getOpcode();
...@@ -751,21 +752,35 @@ public class InsnDecoder { ...@@ -751,21 +752,35 @@ public class InsnDecoder {
return -1; return -1;
} }
public static int getPrevInsnOffset(Object[] insnArr, int offset) { private static DecodedInstruction getInsnByOffsetSkipNop(DecodedInstruction[] insnArr, int offset) {
int i = offset - 1; DecodedInstruction payload = insnArr[offset];
while (i >= 0 && insnArr[i] == null) { if (payload.getOpcode() == Opcodes.NOP) {
i--; return insnArr[getNextInsnOffsetSkipNop(insnArr, offset)];
} }
if (i < 0) { return payload;
return -1;
} }
return i;
public static int getNextInsnOffset(DecodedInstruction[] insnArr, int offset) {
return getNextInsnOffset(insnArr, offset, null);
}
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(Object[] insnArr, int offset) { public static <T> int getNextInsnOffset(T[] insnArr, int offset, Predicate<T> skip) {
int i = offset + 1; int i = offset + 1;
while (i < insnArr.length && insnArr[i] == null) { while (i < insnArr.length) {
T insn = insnArr[i];
if (insn == null || (skip != null && skip.test(insn))) {
i++; i++;
} else {
break;
}
} }
if (i >= insnArr.length) { if (i >= insnArr.length) {
return -1; return -1;
......
...@@ -64,6 +64,9 @@ public class SwitchNode extends TargetInsnNode { ...@@ -64,6 +64,9 @@ public class SwitchNode extends TargetInsnNode {
@Override @Override
public boolean replaceTargetBlock(BlockNode origin, BlockNode replace) { public boolean replaceTargetBlock(BlockNode origin, BlockNode replace) {
if (targetBlocks == null) {
return false;
}
int count = 0; int count = 0;
int len = targetBlocks.length; int len = targetBlocks.length;
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
......
...@@ -87,38 +87,6 @@ public class BlockProcessor extends AbstractVisitor { ...@@ -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) { private static boolean deduplicateBlockInsns(BlockNode block) {
if (block.contains(AFlag.LOOP_START) || block.contains(AFlag.LOOP_END)) { if (block.contains(AFlag.LOOP_START) || block.contains(AFlag.LOOP_END)) {
// search for same instruction at end of all predecessors blocks // search for same instruction at end of all predecessors blocks
...@@ -457,7 +425,7 @@ public class BlockProcessor extends AbstractVisitor { ...@@ -457,7 +425,7 @@ public class BlockProcessor extends AbstractVisitor {
} }
} }
for (BlockNode basicBlock : basicBlocks) { for (BlockNode basicBlock : basicBlocks) {
if (removeEmptyBlock(basicBlock)) { if (BlockSplitter.removeEmptyBlock(basicBlock)) {
changed = true; changed = true;
} }
} }
...@@ -631,7 +599,7 @@ public class BlockProcessor extends AbstractVisitor { ...@@ -631,7 +599,7 @@ public class BlockProcessor extends AbstractVisitor {
ExceptionHandler excHandler = otherExcHandlerAttr.getHandler(); ExceptionHandler excHandler = otherExcHandlerAttr.getHandler();
excHandlerAttr.getHandler().addCatchTypes(excHandler.getCatchTypes()); excHandlerAttr.getHandler().addCatchTypes(excHandler.getCatchTypes());
tryBlock.removeHandler(mth, excHandler); tryBlock.removeHandler(mth, excHandler);
detachBlock(block); BlockSplitter.detachBlock(block);
} }
return true; return true;
} }
...@@ -734,20 +702,6 @@ public class BlockProcessor extends AbstractVisitor { ...@@ -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) { private static void clearBlocksState(MethodNode mth) {
mth.getBasicBlocks().forEach(block -> { mth.getBasicBlocks().forEach(block -> {
block.remove(AType.LOOP); block.remove(AType.LOOP);
......
...@@ -50,13 +50,14 @@ public class BlockSplitter extends AbstractVisitor { ...@@ -50,13 +50,14 @@ public class BlockSplitter extends AbstractVisitor {
mth.initBasicBlocks(); mth.initBasicBlocks();
splitBasicBlocks(mth); splitBasicBlocks(mth);
initBlocksInTargetNodes(mth);
removeJumpAttr(mth); removeJumpAttr(mth);
removeInsns(mth); removeInsns(mth);
removeEmptyDetachedBlocks(mth); removeEmptyDetachedBlocks(mth);
removeUnreachableBlocks(mth); removeUnreachableBlocks(mth);
initBlocksInTargetNodes(mth); mth.getBasicBlocks().removeIf(BlockSplitter::removeEmptyBlock);
removeJumpAttributes(mth.getInstructions());
mth.unloadInsnArr(); mth.unloadInsnArr();
} }
...@@ -307,7 +308,7 @@ public class BlockSplitter extends AbstractVisitor { ...@@ -307,7 +308,7 @@ public class BlockSplitter extends AbstractVisitor {
return block; return block;
} }
private void removeJumpAttr(MethodNode mth) { private static void removeJumpAttr(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) { for (BlockNode block : mth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) { for (InsnNode insn : block.getInstructions()) {
insn.remove(AType.JUMP); insn.remove(AType.JUMP);
...@@ -333,44 +334,88 @@ public class BlockSplitter extends AbstractVisitor { ...@@ -333,44 +334,88 @@ public class BlockSplitter extends AbstractVisitor {
&& block.getSuccessors().isEmpty()); && block.getSuccessors().isEmpty());
} }
private void removeJumpAttributes(InsnNode[] insnArr) { private static boolean removeUnreachableBlocks(MethodNode mth) {
for (InsnNode insn : insnArr) {
if (insn != null && insn.contains(AType.JUMP)) {
insn.remove(AType.JUMP);
}
}
}
private void removeUnreachableBlocks(MethodNode mth) {
Set<BlockNode> toRemove = new LinkedHashSet<>(); Set<BlockNode> toRemove = new LinkedHashSet<>();
for (BlockNode block : mth.getBasicBlocks()) { for (BlockNode block : mth.getBasicBlocks()) {
if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) { if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) {
toRemove.add(block);
collectSuccessors(block, toRemove); collectSuccessors(block, toRemove);
} }
} }
if (!toRemove.isEmpty()) { if (toRemove.isEmpty()) {
mth.getBasicBlocks().removeIf(toRemove::contains); 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(); 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); + ", instructions: " + insnsCount);
} }
return true;
} }
private void collectSuccessors(BlockNode startBlock, Set<BlockNode> 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<BlockNode> toRemove) {
Deque<BlockNode> stack = new ArrayDeque<>(); Deque<BlockNode> stack = new ArrayDeque<>();
stack.add(startBlock); stack.add(startBlock);
while (!stack.isEmpty()) { while (!stack.isEmpty()) {
BlockNode block = stack.pop(); BlockNode block = stack.pop();
if (!toRemove.contains(block)) { if (!toRemove.contains(block)) {
toRemove.add(block);
for (BlockNode successor : block.getSuccessors()) { for (BlockNode successor : block.getSuccessors()) {
if (toRemove.containsAll(successor.getPredecessors())) { if (toRemove.containsAll(successor.getPredecessors())) {
stack.push(successor); 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();
} }
} }
...@@ -7,6 +7,7 @@ import java.util.List; ...@@ -7,6 +7,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBranchRegion; import jadx.core.dex.nodes.IBranchRegion;
...@@ -167,6 +168,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor { ...@@ -167,6 +168,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
for (ExceptionHandler h : tb.getHandlers()) { for (ExceptionHandler h : tb.getHandlers()) {
BlockNode handlerBlock = h.getHandlerBlock(); BlockNode handlerBlock = h.getHandlerBlock();
if (handlerBlock != null if (handlerBlock != null
&& !handlerBlock.contains(AFlag.REMOVE)
&& RegionUtils.hasPathThroughBlock(handlerBlock, cont)) { && RegionUtils.hasPathThroughBlock(handlerBlock, cont)) {
return true; return true;
} }
......
...@@ -1022,6 +1022,9 @@ public class RegionMaker { ...@@ -1022,6 +1022,9 @@ public class RegionMaker {
dom = start; dom = start;
stack.addExits(exits); stack.addExits(exits);
} }
if (dom.contains(AFlag.REMOVE)) {
return;
}
BitSet domFrontier = dom.getDomFrontier(); BitSet domFrontier = dom.getDomFrontier();
List<BlockNode> handlerExits = BlockUtils.bitSetToBlocks(this.mth, domFrontier); List<BlockNode> handlerExits = BlockUtils.bitSetToBlocks(this.mth, domFrontier);
boolean inLoop = this.mth.getLoopForBlock(start) != null; boolean inLoop = this.mth.getLoopForBlock(start) != null;
......
...@@ -270,6 +270,9 @@ public class BlockUtils { ...@@ -270,6 +270,9 @@ public class BlockUtils {
} }
public static List<BlockNode> bitSetToBlocks(MethodNode mth, BitSet bs) { public static List<BlockNode> bitSetToBlocks(MethodNode mth, BitSet bs) {
if (bs == null) {
return Collections.emptyList();
}
int size = bs.cardinality(); int size = bs.cardinality();
if (size == 0) { if (size == 0) {
return Collections.emptyList(); return Collections.emptyList();
......
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();
}
}
.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;-><init>([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;-><init>()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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment