Commit 5a6600f7 authored by Skylot's avatar Skylot

core: fix try/catch wrap logic (fix #47)

parent 14ed0c3a
...@@ -84,8 +84,14 @@ public final class TryCatchRegion extends AbstractRegion implements IBranchRegio ...@@ -84,8 +84,14 @@ public final class TryCatchRegion extends AbstractRegion implements IBranchRegio
@Override @Override
public String toString() { public String toString() {
return "Try: " + tryRegion StringBuilder sb = new StringBuilder();
+ " catches: " + Utils.listToString(catchRegions.values()) sb.append("Try: ").append(tryRegion);
+ (finallyRegion == null ? "" : " finally: " + finallyRegion); if (!catchRegions.isEmpty()) {
sb.append(" catches: ").append(Utils.listToString(catchRegions.values()));
}
if (finallyRegion != null) {
sb.append(" finally: ").append(finallyRegion);
}
return sb.toString();
} }
} }
...@@ -65,37 +65,38 @@ public class BlockExceptionHandler extends AbstractVisitor { ...@@ -65,37 +65,38 @@ public class BlockExceptionHandler extends AbstractVisitor {
private static void processExceptionHandlers(MethodNode mth, BlockNode block) { private static void processExceptionHandlers(MethodNode mth, BlockNode block) {
ExcHandlerAttr handlerAttr = block.get(AType.EXC_HANDLER); ExcHandlerAttr handlerAttr = block.get(AType.EXC_HANDLER);
if (handlerAttr != null) { if (handlerAttr == null) {
ExceptionHandler excHandler = handlerAttr.getHandler(); return;
excHandler.addBlock(block); }
for (BlockNode node : BlockUtils.collectBlocksDominatedBy(block, block)) { ExceptionHandler excHandler = handlerAttr.getHandler();
excHandler.addBlock(node); excHandler.addBlock(block);
} for (BlockNode node : BlockUtils.collectBlocksDominatedBy(block, block)) {
for (BlockNode excBlock : excHandler.getBlocks()) { excHandler.addBlock(node);
// remove 'monitor-exit' from exception handler blocks }
InstructionRemover remover = new InstructionRemover(mth, excBlock); for (BlockNode excBlock : excHandler.getBlocks()) {
for (InsnNode insn : excBlock.getInstructions()) { // remove 'monitor-exit' from exception handler blocks
if (insn.getType() == InsnType.MONITOR_ENTER) { InstructionRemover remover = new InstructionRemover(mth, excBlock);
break; for (InsnNode insn : excBlock.getInstructions()) {
} if (insn.getType() == InsnType.MONITOR_ENTER) {
if (insn.getType() == InsnType.MONITOR_EXIT) { break;
remover.add(insn); }
} if (insn.getType() == InsnType.MONITOR_EXIT) {
remover.add(insn);
} }
remover.perform(); }
remover.perform();
// if 'throw' in exception handler block have 'catch' - merge these catch blocks // if 'throw' in exception handler block have 'catch' - merge these catch blocks
for (InsnNode insn : excBlock.getInstructions()) { for (InsnNode insn : excBlock.getInstructions()) {
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK); CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
if (catchAttr == null) { if (catchAttr == null) {
continue; continue;
} }
if (insn.getType() == InsnType.THROW if (insn.getType() == InsnType.THROW
|| onlyAllHandler(catchAttr.getTryBlock())) { || onlyAllHandler(catchAttr.getTryBlock())) {
TryCatchBlock handlerBlock = handlerAttr.getTryBlock(); TryCatchBlock handlerBlock = handlerAttr.getTryBlock();
TryCatchBlock catchBlock = catchAttr.getTryBlock(); TryCatchBlock catchBlock = catchAttr.getTryBlock();
handlerBlock.merge(mth, catchBlock); handlerBlock.merge(mth, catchBlock);
}
} }
} }
} }
......
package jadx.core.dex.visitors.blocksmaker; package jadx.core.dex.visitors.blocksmaker;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.SplitterBlockAttr;
import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.BlockUtils;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BlockFinish extends AbstractVisitor { public class BlockFinish extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(BlockFinish.class);
@Override @Override
public void visit(MethodNode mth) { public void visit(MethodNode mth) {
if (mth.isNoCode()) { if (mth.isNoCode()) {
...@@ -20,6 +31,7 @@ public class BlockFinish extends AbstractVisitor { ...@@ -20,6 +31,7 @@ public class BlockFinish extends AbstractVisitor {
for (BlockNode block : mth.getBasicBlocks()) { for (BlockNode block : mth.getBasicBlocks()) {
block.updateCleanSuccessors(); block.updateCleanSuccessors();
initBlocksInIfNodes(block); initBlocksInIfNodes(block);
fixSplitterBlock(block);
} }
mth.finishBasicBlocks(); mth.finishBasicBlocks();
...@@ -37,4 +49,47 @@ public class BlockFinish extends AbstractVisitor { ...@@ -37,4 +49,47 @@ public class BlockFinish extends AbstractVisitor {
} }
} }
} }
/**
* For evey exception handler must be only one splitter block,
* select correct one and remove others if necessary.
*/
private static void fixSplitterBlock(BlockNode block) {
ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER);
if (excHandlerAttr == null) {
return;
}
BlockNode handlerBlock = excHandlerAttr.getHandler().getHandlerBlock();
if (handlerBlock.getPredecessors().size() < 2) {
return;
}
Map<BlockNode, SplitterBlockAttr> splitters = new HashMap<BlockNode, SplitterBlockAttr>();
for (BlockNode pred : handlerBlock.getPredecessors()) {
pred = BlockUtils.skipSyntheticPredecessor(pred);
SplitterBlockAttr splitterAttr = pred.get(AType.SPLITTER_BLOCK);
if (splitterAttr != null && pred == splitterAttr.getBlock()) {
splitters.put(pred, splitterAttr);
}
}
if (splitters.size() < 2) {
return;
}
BlockNode topSplitter = BlockUtils.getTopBlock(splitters.keySet());
if (topSplitter == null) {
LOG.warn("Unknown top splitter block from list: {}", splitters);
return;
}
for (Map.Entry<BlockNode, SplitterBlockAttr> entry : splitters.entrySet()) {
BlockNode pred = entry.getKey();
SplitterBlockAttr splitterAttr = entry.getValue();
if (pred == topSplitter) {
block.addAttr(splitterAttr);
} else {
pred.remove(AType.SPLITTER_BLOCK);
for (BlockNode s : pred.getCleanSuccessors()) {
s.remove(AType.SPLITTER_BLOCK);
}
}
}
}
} }
...@@ -151,26 +151,30 @@ public class BlockSplitter extends AbstractVisitor { ...@@ -151,26 +151,30 @@ public class BlockSplitter extends AbstractVisitor {
BlockNode thisBlock = getBlock(jump.getDest(), blocksMap); BlockNode thisBlock = getBlock(jump.getDest(), blocksMap);
connect(srcBlock, thisBlock); connect(srcBlock, thisBlock);
} }
connectExceptionHandlers(blocksMap, block, insn);
}
}
}
// connect exception handlers private static void connectExceptionHandlers(Map<Integer, BlockNode> blocksMap, BlockNode block, InsnNode insn) {
CatchAttr catches = insn.get(AType.CATCH_BLOCK); CatchAttr catches = insn.get(AType.CATCH_BLOCK);
// get synthetic block for handlers SplitterBlockAttr spl = block.get(AType.SPLITTER_BLOCK);
SplitterBlockAttr spl = block.get(AType.SPLITTER_BLOCK); if (catches == null || spl == null) {
if (catches != null && spl != null) { return;
BlockNode splitterBlock = spl.getBlock(); }
boolean tryEnd = insn.contains(AFlag.TRY_LEAVE); BlockNode splitterBlock = spl.getBlock();
for (ExceptionHandler h : catches.getTryBlock().getHandlers()) { boolean tryEnd = insn.contains(AFlag.TRY_LEAVE);
BlockNode handlerBlock = getBlock(h.getHandleOffset(), blocksMap); for (ExceptionHandler h : catches.getTryBlock().getHandlers()) {
// skip self loop in handler BlockNode handlerBlock = getBlock(h.getHandleOffset(), blocksMap);
if (splitterBlock != handlerBlock) { // skip self loop in handler
handlerBlock.addAttr(spl); if (splitterBlock != handlerBlock) {
connect(splitterBlock, handlerBlock); if (!handlerBlock.contains(AType.SPLITTER_BLOCK)) {
} handlerBlock.addAttr(spl);
if (tryEnd) {
connect(block, handlerBlock);
}
}
} }
connect(splitterBlock, handlerBlock);
}
if (tryEnd) {
connect(block, handlerBlock);
} }
} }
} }
......
...@@ -12,6 +12,7 @@ import jadx.core.dex.regions.TryCatchRegion; ...@@ -12,6 +12,7 @@ import jadx.core.dex.regions.TryCatchRegion;
import jadx.core.dex.regions.loops.LoopRegion; import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.SplitterBlockAttr;
import jadx.core.dex.trycatch.TryCatchBlock; import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.utils.BlockUtils; import jadx.core.utils.BlockUtils;
import jadx.core.utils.ErrorsCounter; import jadx.core.utils.ErrorsCounter;
...@@ -65,39 +66,28 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor { ...@@ -65,39 +66,28 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
// for each try block search nearest dominator block // for each try block search nearest dominator block
for (TryCatchBlock tb : tryBlocks) { for (TryCatchBlock tb : tryBlocks) {
BitSet bs = null; BitSet bs = new BitSet(mth.getBasicBlocks().size());
// build bitset with dominators of blocks covered with this try/catch block for (ExceptionHandler excHandler : tb.getHandlers()) {
for (BlockNode block : mth.getBasicBlocks()) { SplitterBlockAttr splitter = excHandler.getHandlerBlock().get(AType.SPLITTER_BLOCK);
CatchAttr c = block.get(AType.CATCH_BLOCK); if (splitter != null) {
if (c != null && c.getTryBlock() == tb) { BlockNode block = splitter.getBlock();
if (bs == null) { bs.set(block.getId());
bs = (BitSet) block.getDoms().clone();
} else {
bs.and(block.getDoms());
}
} }
} }
if (bs == null) {
LOG.debug(" Can't build try/catch dominators bitset, tb: {}, mth: {} ", tb, mth);
continue;
}
// intersect to get dominator of dominators
List<BlockNode> domBlocks = BlockUtils.bitSetToBlocks(mth, bs); List<BlockNode> domBlocks = BlockUtils.bitSetToBlocks(mth, bs);
for (BlockNode block : domBlocks) { BlockNode domBlock;
bs.andNot(block.getDoms());
}
domBlocks = BlockUtils.bitSetToBlocks(mth, bs);
if (domBlocks.size() != 1) { if (domBlocks.size() != 1) {
throw new JadxRuntimeException( domBlock = BlockUtils.getTopBlock(domBlocks);
"Exception block dominator not found, method:" + mth + ". bs: " + bs); if (domBlock == null) {
throw new JadxRuntimeException(
"Exception block dominator not found, method:" + mth + ". bs: " + domBlocks);
}
} else {
domBlock = domBlocks.get(0);
} }
BlockNode domBlock = domBlocks.get(0);
TryCatchBlock prevTB = tryBlocksMap.put(domBlock, tb); TryCatchBlock prevTB = tryBlocksMap.put(domBlock, tb);
if (prevTB != null) { if (prevTB != null) {
LOG.info("!!! TODO: merge try blocks in {}", mth); ErrorsCounter.methodError(mth, "Failed to process nested try/catch");
} }
} }
} }
...@@ -136,7 +126,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor { ...@@ -136,7 +126,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
Region tryRegion = new Region(replaceRegion); Region tryRegion = new Region(replaceRegion);
List<IContainer> subBlocks = replaceRegion.getSubBlocks(); List<IContainer> subBlocks = replaceRegion.getSubBlocks();
for (IContainer cont : subBlocks) { for (IContainer cont : subBlocks) {
if (RegionUtils.isDominatedBy(dominator, cont)) { if (RegionUtils.hasPathThroughBlock(dominator, cont)) {
if (isHandlerPath(tb, cont)) { if (isHandlerPath(tb, cont)) {
break; break;
} }
...@@ -170,7 +160,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor { ...@@ -170,7 +160,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
private static boolean isHandlerPath(TryCatchBlock tb, IContainer cont) { private static boolean isHandlerPath(TryCatchBlock tb, IContainer cont) {
for (ExceptionHandler h : tb.getHandlers()) { for (ExceptionHandler h : tb.getHandlers()) {
if (RegionUtils.hasPathThruBlock(h.getHandlerBlock(), cont)) { if (RegionUtils.hasPathThroughBlock(h.getHandlerBlock(), cont)) {
return true; return true;
} }
} }
......
...@@ -19,6 +19,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException; ...@@ -19,6 +19,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.BitSet; import java.util.BitSet;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
...@@ -342,6 +343,25 @@ public class BlockUtils { ...@@ -342,6 +343,25 @@ public class BlockUtils {
return traverseSuccessorsUntil(start, end, new BitSet()); return traverseSuccessorsUntil(start, end, new BitSet());
} }
public static BlockNode getTopBlock(Collection<BlockNode> blocks) {
if (blocks.size() == 1) {
return blocks.iterator().next();
}
for (BlockNode from : blocks) {
boolean top = true;
for (BlockNode to : blocks) {
if (from != to && !isPathExists(from, to)) {
top = false;
break;
}
}
if (top) {
return from;
}
}
return null;
}
public static boolean isOnlyOnePathExists(BlockNode start, BlockNode end) { public static boolean isOnlyOnePathExists(BlockNode start, BlockNode end) {
if (start == end) { if (start == end) {
return true; return true;
......
...@@ -59,6 +59,10 @@ public class DebugUtils { ...@@ -59,6 +59,10 @@ public class DebugUtils {
printRegions(mth, false); printRegions(mth, false);
} }
public static void printRegion(MethodNode mth, IRegion region, boolean printInsn) {
printRegion(mth, region, "", printInsn);
}
public static void printRegions(MethodNode mth, boolean printInsns) { public static void printRegions(MethodNode mth, boolean printInsns) {
LOG.debug("|{}", mth.toString()); LOG.debug("|{}", mth.toString());
printRegion(mth, mth.getRegion(), "| ", printInsns); printRegion(mth, mth.getRegion(), "| ", printInsns);
......
...@@ -271,7 +271,7 @@ public class RegionUtils { ...@@ -271,7 +271,7 @@ public class RegionUtils {
} }
} }
public static boolean hasPathThruBlock(BlockNode block, IContainer cont) { public static boolean hasPathThroughBlock(BlockNode block, IContainer cont) {
if (block == cont) { if (block == cont) {
return true; return true;
} }
...@@ -282,7 +282,7 @@ public class RegionUtils { ...@@ -282,7 +282,7 @@ public class RegionUtils {
} else if (cont instanceof IRegion) { } else if (cont instanceof IRegion) {
IRegion region = (IRegion) cont; IRegion region = (IRegion) cont;
for (IContainer c : region.getSubBlocks()) { for (IContainer c : region.getSubBlocks()) {
if (!hasPathThruBlock(block, c)) { if (!hasPathThroughBlock(block, c)) {
return false; return false;
} }
} }
......
package jadx.tests.integration.trycatch;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.junit.Test;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.junit.Assert.assertThat;
public class TestTryCatchFinally4 extends IntegrationTest {
public static class TestCls {
public void test() throws IOException {
File file = File.createTempFile("test", "txt");
OutputStream outputStream = new FileOutputStream(file);
try {
outputStream.write(1);
} finally {
try {
outputStream.close();
file.delete();
} catch (IOException e) {
}
}
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsOne("File file = File.createTempFile(\"test\", \"txt\");"));
assertThat(code, containsOne("OutputStream outputStream = new FileOutputStream(file);"));
assertThat(code, containsOne("outputStream.write(1);"));
assertThat(code, containsOne("} finally {"));
assertThat(code, containsOne("outputStream.close();"));
assertThat(code, containsOne("} catch (IOException e) {"));
}
}
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